Разработка ИИ-агентов · Модуль 2 · Урок 2.5
Ошибки, ретраи, программные gate'ы, guardrails
Ошибки неизбежны — отличайте их виды
Агент ходит в сеть, дёргает внешние API и доверяет недетерминированной модели — сбои будут. Ключ к надёжности — различать виды ошибок и реагировать по-разному.
- Временные (transient): таймауты, 429 (rate limit), 5xx, обрыв сети. Их имеет смысл повторить.
- Постоянные (permanent): 400 (плохой запрос), 401/403 (доступ), невалидные аргументы. Повтор не поможет — нужно чинить запрос или сообщать об ошибке.
- Ошибки инструментов: инструмент вернул ошибку (нет такого заказа). Часто это не сбой агента, а нормальный результат, который надо вернуть модели — пусть учтёт.
Отдельно: ошибку инструмента лучше вернуть модели текстом, а не «ронять» цикл — тогда агент сможет переспросить или попробовать иначе.
Ретраи с экспоненциальной задержкой
Временные ошибки повторяют с экспоненциальной задержкой (backoff) и небольшим случайным разбросом (jitter), чтобы не «долбить» сервис синхронно. Обязательно ограничьте число попыток и общий таймаут — бесконечные ретраи опаснее одной ошибки. Уважайте заголовок Retry-After, если провайдер его прислал.
Повторять стоит только идемпотентные операции или те, где повтор безопасен. Если инструмент списывает деньги или отправляет письмо — слепой ретрай может сделать действие дважды; такие операции защищают ключами идемпотентности.
Программные gate'ы и guardrails
Нельзя позволять модели делать что угодно. Между «модель решила» и «действие выполнено» ставят программные gate'ы — проверки в коде, которые модель не может обойти. Примеры: подтверждать необратимые действия (удаление, платёж), ограничивать суммы, проверять права доступа, белый список (whitelist) допустимых операций. Это детерминированная защита: что бы модель ни «захотела», код решает, можно ли.
Guardrails — шире: проверки входа и выхода. На входе — фильтрация недопустимых запросов и попыток prompt injection. На выходе — проверка, что ответ не содержит запретного, что структура верна, что агент не вышел за рамки. Принцип прост: критичные правила навязывайте программно, а не надейтесь, что модель «сама не станет». Доверяй, но проверяй — в коде.
func withRetry(ctx context.Context, op func() error) error {
const maxAttempts = 4
delay := 500 * time.Millisecond
var err error
for attempt := 1; attempt <= maxAttempts; attempt++ {
err = op()
if err == nil {
return nil
}
if !isTransient(err) { // постоянные ошибки не повторяем
return err
}
if attempt == maxAttempts {
break
}
select {
case <-time.After(delay + jitter()):
delay *= 2 // экспоненциальный backoff
case <-ctx.Done():
return ctx.Err()
}
}
return fmt.Errorf("операция не удалась после %d попыток: %w", maxAttempts, err)
}// Модель может ЗАПРОСИТь удаление, но выполняем его только через gate.
func dispatchDelete(argsJSON string, confirmed bool) string {
var a struct{ ID string }
if err := json.Unmarshal([]byte(argsJSON), &a); err != nil {
return "ошибка аргументов: " + err.Error()
}
if !confirmed {
// не выполняем — возвращаем модели требование подтверждения
return "ТРЕБУЕТСЯ ПОДТВЕРЖДЕНИЕ: удаление " + a.ID +
" необратимо. Переспроси пользователя и вызови снова с confirm=true."
}
if err := store.Delete(a.ID); err != nil {
return "ошибка удаления: " + err.Error()
}
return "удалено: " + a.ID
}Anti-patterns
| Анти-паттерн | Почему плохо | Как правильно |
|---|---|---|
| Повторять любую ошибку | Ретрай 400/403 бесполезен, маскирует баг | Повторять только временные (429/5xx/таймаут) |
| Бесконечные ретраи без задержки | DDoS своего же провайдера, зависание | Backoff + jitter + лимит попыток и общий таймаут |
| Ронять цикл на ошибке инструмента | Агент не может восстановиться | Вернуть ошибку модели текстом — пусть переспросит/обойдёт |
| Надеяться, что модель «сама не сделает плохого» | Необратимые действия без защиты | Программные gate'ы и guardrails в коде, а не в промпте |
| Слепой ретрай платежа/отправки | Двойное списание/дубликаты | Идемпотентность для неидемпотентных операций |
Практическое задание
- Классифицируйте ошибки вашего агента на временные и постоянные; напишите
isTransient. - Оберните сетевые вызовы в ретрай с экспоненциальной задержкой, jitter и лимитом попыток.
- Сделайте так, чтобы ошибка инструмента возвращалась модели текстом, а не роняла цикл.
- Поставьте программный gate на необратимое действие (требование подтверждения перед выполнением).
- Добавьте простой выходной guardrail: проверку, что ответ соответствует ожидаемому формату/рамкам.
Проверка знаний
Какие ошибки имеет смысл повторять с backoff?
Верный ответ: B
B верно. Повтор помогает при временных сбоях. 400/403 (A) постоянны — повтор бесполезен. «Любые подряд» (C) опасно. Ошибки парсинга чинят ретраем к модели, а не сетевым backoff.
Где должны жить критичные правила (например, запрет необратимого действия без подтверждения)?
Верный ответ: B
B верно. Критичные правила навязывают программно: что бы модель ни «решила», код проверяет и разрешает/блокирует. Промпт и описание (A, D) помогают, но на них одних полагаться нельзя.
Инструмент вернул «заказ не найден». Как лучше поступить?
Верный ответ: B
B верно. «Не найдено» — нормальный результат инструмента, а не фатальный сбой. Возврат его модели позволяет агенту корректно отреагировать. A и C избыточны, D — обман пользователя.