0Pricing
AI Agents · Урок

Этапы утверждения с участием человека

Шаблоны приостановки, запроса и утверждения для важных действий агента.

«Этапы утверждения с участием человека» — бесплатный урок AI Agents на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Agents, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Agents содержит 4 уроков всего.

Когда агенту требуется одобрение человека

При выполнении действий с серьезными последствиями — отправке договора, удалении данных в рабочей среде, расходовании бюджета, публикации объявления — агент не должен действовать автономно. Перед продолжением он должен приостановить выполнение и запросить одобрение человека.

Это шаблон взаимодействия человека и системы (HITL).

Определение действий с серьезными последствиями

Определите функцию классификации, которая выявляет действия, требующие одобрения человека. Порог можно настраивать для каждого развертывания с учетом допустимого уровня риска.

HIGH_STAKES_ACTIONS = {
    'send_email',
    'delete_records',
    'publish_content',
    'transfer_funds',
    'modify_production_config',
    'export_all_data',
    'send_push_notification_to_all'
}

HIGH_STAKES_THRESHOLDS = {
    'transfer_funds':    1000,    # USD — require approval above this
    'delete_records':    10,      # rows
    'send_email':        50,      # recipients
    'push_notification': 1000     # users
}

def requires_approval(action: str, parameters: dict) -> bool:
    if action not in HIGH_STAKES_ACTIONS:
        return False
    threshold = HIGH_STAKES_THRESHOLDS.get(action)
    if threshold is None:
        return True   # all instances require approval
    # Check parameter against threshold
    amount = parameters.get('amount') or parameters.get('count') or 0
    return float(amount) >= threshold

if __name__ == '__main__':
    print('Small transfer:', requires_approval('transfer_funds', {'amount': 200}))
    print('Large transfer:', requires_approval('transfer_funds', {'amount': 5000}))
    print('Publish content:', requires_approval('publish_content', {}))

Создание запроса на одобрение

Когда требуется одобрение, агент создает запись запроса на одобрение и приостанавливает выполнение. Запрос содержит описание предстоящего действия, его параметры и крайний срок.

import uuid, time

approval_requests: dict[str, dict] = {}   # approval_id -> request

def create_approval_request(agent_id: str, user_id: str, action: str,
                             parameters: dict, timeout_minutes: int = 30) -> str:
    approval_id = str(uuid.uuid4())
    approval_requests[approval_id] = {
        'approval_id':  approval_id,
        'agent_id':     agent_id,
        'user_id':      user_id,
        'action':       action,
        'parameters':   parameters,
        'status':       'pending',     # pending / approved / rejected / timed_out
        'created_at':   time.time(),
        'expires_at':   time.time() + timeout_minutes * 60,
        'decided_by':   None,
        'decided_at':   None
    }
    return approval_id

if __name__ == '__main__':
    approval_id = create_approval_request(
        'agent-1', 'user-42', 'delete_records', {'count': 50}
    )
    print('Created approval request:', approval_id)
    print('Status:', approval_requests[approval_id]['status'])

Уведомление через Slack

Отправьте запрос на одобрение утверждающему лицу через Slack. Включите краткое описание действия, которое хочет выполнить агент, ссылку для одобрения или отклонения и крайний срок, после которого истекает время ожидания.

import requests

SLACK_WEBHOOK = 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL'
APPROVAL_BASE_URL = 'https://your-agent-dashboard.com/approvals'

def notify_approver_slack(approval_id: str, approver_slack_id: str):
    req = approval_requests[approval_id]
    import time as t
    from datetime import datetime
    expires = datetime.fromtimestamp(req['expires_at']).strftime('%H:%M UTC')

    payload = {
        'text': f'<@{approver_slack_id}> Agent approval required',
        'attachments': [{
            'color': '#ff9900',
            'fields': [
                {'title': 'Action',      'value': req['action'],    'short': True},
                {'title': 'Requested by','value': req['user_id'],   'short': True},
                {'title': 'Parameters',  'value': str(req['parameters'])[:200]},
                {'title': 'Expires',     'value': expires,          'short': True}
            ],
            'actions': [
                {'type': 'button', 'text': 'Approve',
                 'url': f'{APPROVAL_BASE_URL}/{approval_id}/approve'},
                {'type': 'button', 'text': 'Reject',
                 'url': f'{APPROVAL_BASE_URL}/{approval_id}/reject'}
            ]
        }]
    }
    requests.post(SLACK_WEBHOOK, json=payload)

Уведомление по электронной почте

В качестве резервного канала (или основного для организаций без Slack) отправляйте запросы на одобрение по электронной почте с понятными ссылками для одобрения или отклонения.

import smtplib
from email.mime.text import MIMEText

SMTP_HOST  = 'smtp.yourcompany.com'
SMTP_PORT  = 587
SMTP_USER  = 'agent-noreply@yourcompany.com'
SMTP_PASS  = 'YOUR_SMTP_PASSWORD'

def notify_approver_email(approval_id: str, approver_email: str):
    req = approval_requests[approval_id]
    body = (
        f'An AI agent is requesting approval for:\n\n'
        f'Action: {req["action"]}\n'
        f'Parameters: {req["parameters"]}\n\n'
        f'Approve: {APPROVAL_BASE_URL}/{approval_id}/approve\n'
        f'Reject:  {APPROVAL_BASE_URL}/{approval_id}/reject\n\n'
        f'This request expires in 30 minutes.'
    )
    msg = MIMEText(body)
    msg['Subject'] = f'Agent Approval Required: {req["action"]}'
    msg['From']    = SMTP_USER
    msg['To']      = approver_email

    with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
        server.starttls()
        server.login(SMTP_USER, SMTP_PASS)
        server.send_message(msg)

Ожидание решения об одобрении

После отправки уведомления агент ожидает решения. Используйте цикл опроса с короткой паузой. Прекратите опрос, когда статус изменится с «ожидание» или истечет крайний срок.

import time

def wait_for_approval(approval_id: str, poll_interval: float = 5.0) -> dict:
    while True:
        req = approval_requests.get(approval_id)
        if not req:
            return {'decision': 'error', 'reason': 'Approval request not found'}

        if req['status'] == 'approved':
            return {'decision': 'approved', 'decided_by': req['decided_by']}

        if req['status'] == 'rejected':
            return {'decision': 'rejected', 'decided_by': req['decided_by']}

        if time.time() > req['expires_at']:
            req['status'] = 'timed_out'
            return {'decision': 'timed_out', 'reason': 'No decision within deadline'}

        time.sleep(poll_interval)

Запись решения об одобрении

Когда утверждающее лицо нажимает «Одобрить» или «Отклонить», запишите, кто и когда принял решение. Так формируется полная цепочка аудита одобрения: запрос создан → уведомление отправлено → решение принято → действие выполнено (или отменено).

def record_decision(approval_id: str, decision: str,
                    decided_by: str) -> dict:
    req = approval_requests.get(approval_id)
    if not req:
        return {'error': 'Approval request not found'}

    if req['status'] != 'pending':
        return {'error': f'Request already in state: {req["status"]}'}

    if time.time() > req['expires_at']:
        req['status'] = 'timed_out'
        return {'error': 'Request has expired'}

    req['status']     = decision   # 'approved' or 'rejected'
    req['decided_by'] = decided_by
    req['decided_at'] = time.time()
    return {'ok': True, 'decision': decision}

Обработка истечения времени: автоматическая отмена

Если в течение отведенного времени решение не принято, агент автоматически отменяет действие и уведомляет исходного инициатора запроса. Это не позволяет действиям навсегда блокироваться из-за отсутствия ответа от утверждающих лиц.

def handle_timeout(approval_id: str, agent_session: dict) -> str:
    req = approval_requests.get(approval_id, {})
    action  = req.get('action', 'unknown')
    user_id = req.get('user_id', 'unknown')

    # Log the timeout
    import logging
    logging.warning(
        'Approval timeout: action=%s user=%s approval_id=%s',
        action, user_id, approval_id
    )

    # Tell the user
    timeout_message = (
        f'The "{action}" action was automatically cancelled because '
        f'no approver responded within the 30-minute window. '
        f'Please request again or contact your administrator.'
    )
    return timeout_message

Полная оркестрация проверки одобрения

Объедините все шаги: проверьте, требуется ли одобрение, создайте запрос, отправьте уведомление, дождитесь решения, а затем продолжите выполнение или отмените действие в зависимости от решения.

def execute_with_approval_gate(agent_id: str, user_id: str, action: str,
                                parameters: dict, approver_email: str,
                                execute_fn) -> dict:
    # Step 1: Check if approval needed
    if not requires_approval(action, parameters):
        result = execute_fn(action, parameters)
        return {'approved': True, 'auto': True, 'result': result}

    # Step 2: Create approval request
    approval_id = create_approval_request(agent_id, user_id, action, parameters)

    # Step 3: Notify approver
    notify_approver_email(approval_id, approver_email)
    print(f'Approval requested: {approval_id}. Waiting...')

    # Step 4: Wait for decision
    decision = wait_for_approval(approval_id)

    # Step 5: Act on decision
    if decision['decision'] == 'approved':
        result = execute_fn(action, parameters)
        return {'approved': True, 'decided_by': decision['decided_by'], 'result': result}
    elif decision['decision'] == 'rejected':
        return {'approved': False, 'reason': 'Rejected by approver'}
    else:
        msg = handle_timeout(approval_id, {})
        return {'approved': False, 'reason': msg}

Аудиторский след одобрения

Каждое событие жизненного цикла одобрения необходимо записывать в журнал аудита: запрос создан, уведомление отправлено, решение принято, действие выполнено или отменено. Такая цепочка необходима для соответствия требованиям SOC 2.

import logging, json, time

approval_logger = logging.getLogger('agent.approvals')

def audit_approval_event(event: str, approval_id: str, details: dict):
    entry = {
        'timestamp':   time.time(),
        'event':       event,
        'approval_id': approval_id,
        **details
    }
    approval_logger.info(json.dumps(entry))

# Usage flow:
# audit_approval_event('request_created', approval_id, {'action': 'delete_records', 'user': 'u123'})
# audit_approval_event('notification_sent', approval_id, {'channel': 'email', 'approver': 'admin@co.com'})
# audit_approval_event('decision_received', approval_id, {'decision': 'approved', 'by': 'admin@co.com'})
# audit_approval_event('action_executed',  approval_id, {'result': 'success'})

if __name__ == '__main__':
    import sys
    approval_logger.setLevel(logging.INFO)
    approval_logger.addHandler(logging.StreamHandler(sys.stdout))
    audit_approval_event('request_created', 'appr-1', {'action': 'delete_records', 'user': 'u123'})

Эскалация при недоступности основного утверждающего лица

Если основное утверждающее лицо не отвечает в течение половины отведенного времени, передайте запрос второму утверждающему лицу. Это предотвращает блокировку всех одобрений из-за отсутствия одного человека.

def escalate_if_needed(approval_id: str, secondary_email: str,
                        escalation_at_pct: float = 0.5):
    req = approval_requests.get(approval_id)
    if not req or req['status'] != 'pending':
        return

    total_window  = req['expires_at'] - req['created_at']
    elapsed       = time.time() - req['created_at']
    escalation_at = req['created_at'] + total_window * escalation_at_pct

    if time.time() >= escalation_at and not req.get('escalated'):
        req['escalated'] = True
        notify_approver_email(approval_id, secondary_email)
        print(f'Escalated approval {approval_id} to {secondary_email}')

Что происходит с ожидающим запросом на одобрение после истечения времени ожидания

Поведение при истечении времени — важнейшая часть проектирования HITL. Неправильный выбор варианта по умолчанию по-разному влияет на безопасность и удобство использования.

Итоги: проверки одобрения с участием человека

Проверки одобрения HITL работают следующим образом: классификация действий с серьезными последствиями, создание приостановленного запроса на одобрение, уведомление утверждающих лиц через Slack или электронную почту, опрос для получения решений, автоматическая отмена при истечении времени и запись каждого события жизненного цикла в цепочку аудита.

Эскалация к вторым утверждающим лицам предотвращает блокировку одобрений из-за недоступности основных.

Часто задаваемые вопросы

Урок «Этапы утверждения с участием человека» бесплатный?

Да — полный текст урока «Этапы утверждения с участием человека» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Agents, подпишись на CoddyKit PRO. Курс AI Agents содержит 4 уроков всего.

Чему я научусь в уроке «Этапы утверждения с участием человека»?

Шаблоны приостановки, запроса и утверждения для важных действий агента. Ты практикуешь AI Agents с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать AI Agents?

Предыдущий опыт не требуется. AI Agents на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Этапы утверждения с участием человека»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке AI Agents?

Да. Каждый урок AI Agents включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Неизменяемая регистрация действий агентов
  2. Применение политик к действиям агента
  3. Соблюдение нормативных требований: GDPR и SOC2
  4. Этапы утверждения с участием человека
← Назад к AI Agents