К курсу

Цикл агента

Один шаг цикла агентаПрактика: один шаг циклаЦикл в кодеПрактика: цикл в кодеОстановка и повторыПрактика: остановка и повторыЧто решает только кодПрактика: что решает код

Надёжность в проде

Таймаут и ключ идемпотентностиПрактика: таймаут и ключ идемпотентностиСказал «готово», а ничего не произошлоПрактика: ложный успехУстойчивый агент: checkpoint и продолжениеПрактика: устойчивый агент

Устройство агентов

Workflow или агентПрактика: workflow или агентReAct и plan-and-executeПрактика: ReAct и plan-and-executePlan-and-SolveПрактика: Plan-and-SolveReflexionПрактика: ReflexionОдин агент или ройПрактика: один агент или рой

Контекст и стоимость

Почему растёт счёт за агентаПрактика: токены и кэшКонтекст-инженерияПрактика: управление окномПамять агентаПрактика: что агент хранит

Инструменты и протоколы

MCPПрактика: MCPОписания инструментов и инъекцииПрактика: описания инструментов и инъекцииКод вместо набора инструментовПрактика: код вместо набора инструментовНавыки агента и A2AПрактика: навыки агента и A2A

Фреймворки

LangGraph или обычный циклПрактика: LangGraph или обычный циклAgent SDK: что вы покупаетеПрактика: agent SDK

Агенты для кода

Агент для кода по спецификацииПрактика: агент для кода по спецификации
←→ENВойти
Агенты › Надёжность в продеDeep dive

Сказал «готово», а ничего не произошло

Опирается наТаймаут и ключ идемпотентности

Этот блок объясняет, почему «готово» от агента ничего не доказывает, как система узнаёт, что произошло на самом деле, и как измерить разницу.

Агент Northwind пишет покупателю: «Ваш возврат на $38.00 по заказу 4417 оформлен». Через пять дней покупатель сообщает, что деньги не пришли. В платёжных записях возврата по этому заказу нет, а прогон записан как успешный.

Коллега и выписка

Вернёмся к коллеге на телефоне. Он говорит: «Всё оплачено». У этой оплаты теперь три свидетеля. Слова коллеги. Клерк банка, который сказал по телефону «хорошо». И строка в банковской выписке.

Факт здесь только выписка. Коллега мог описать то, что собирался сделать. Клерк мог сказать «хорошо» про перевод, который так и не прошёл.

Последнее сообщение прогона — это слова коллеги. Ложный успех (false success) — так называют заявление о завершении, которое фактическое состояние не подтверждает.

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

Обычно ломается одно из трёх, по одному на каждом шаге:

Инструмент сообщает неправду. Сервис отвечает 200, хотя ничего не записано или запись неполная. После изменения интеграции так могут возвращаться все вызовы.

Код вокруг модели прячет ошибку. Эта обёртка превращает любой сбой в хорошую новость:

try:
    payments.refund(order_id, amount, idempotency_key=key)
except Exception:
    log.warning("refund failed")
return "Refund submitted."

Модель пересказывает намерение. Она попросила возврат или только собиралась и поэтому пишет, что возврат состоялся, даже если в результате был pending или ошибка. Никакая уверенность этой фразы не скажет, что из двух произошло.

Как часто? В одной работе с воркшопа 2026 года, у которой один автор, разобрано 9876 прогонов 8 семейств моделей на бенчмарке поддержки tau2-bench. Доля неудачных прогонов, где агент всё равно заявил о завершении, оказалась такой:

Авиа и ритейл против телекома, где состояние может проверить и симулятор покупателя. Автор отмечает, что для установления причины нужны другие такие среды.
Авиа и ритейл против телекома, где состояние может проверить и симулятор покупателя. Автор отмечает, что для установления причины нужны другие такие среды.

Читаем выписку

Сначала инструмент записи возвращает поля, которыми может пользоваться код, а не фразу:

{
  "status": "succeeded",
  "refund_id": "re_31",
  "order_id": "4417",
  "amount_cents": 3800
}

Эти поля тоже могут быть неверны, как показал 200 выше. Поэтому после записи ваш код читает сохранённые данные и сравнивает их с тем, что просил. Проверка, которая работает вне модели и решает, каков итог, называется верификатором.

Записать, затем прочитать обратно. Модель в сравнении не участвует.
Записать, затем прочитать обратно. Модель в сравнении не участвует.
def verify_refund(op):
    if op.refund_id is None:
        return "unknown"    # no confirmed reply, so the refund may still exist
    try:
        refund = payments.get_refund(op.refund_id)
    except (Timeout, ServiceError):
        return "unknown"
    if refund.amount != op.amount_cents or refund.metadata["order_id"] != op.order_id:
        return "not_landed"
    return {"succeeded": "landed", "pending": "pending", "requires_action": "pending"}.get(refund.status, "not_landed")

Сорвавшаяся проверка — это третий ответ. Если чтение зависло по таймауту, вы не знаете итога, а что делать в таком случае, разобрано в прошлом блоке.

Итог определяет, что прочтёт покупатель:

Итог

Что сообщают покупателю

landed

Возврат выполнен, с суммой

pending

Возврат в пути

unknown

Коллега проверяет

not_landed

Не прошло, делом занимается человек

Одно чтение показывает один момент времени. В Stripe статус возврата может быть pending, requires_action, succeeded, failed или canceled, и в редких случаях возврат переходит в failed, потому что банк покупателя не смог его обработать. Тогда Stripe присылает событие refund.failed, поэтому на вашей стороне кто-то должен его слушать.

У проверки есть цена: лишнее чтение и лишняя задержка на каждую запись. Для денег и прав платите её всегда, остальное проверяйте выборочно.

Считаем разрыв

Каждый прогон даёт два отдельных факта: что сказал агент и что нашла проверка.

{
  "run_id": "run_9312",
  "order_id": "4417",
  "claimed": "done",
  "effect": "not_landed"
}

Просите агента в конце прогона дать заявление в структурированном виде, чтобы не разбирать фразы. Затем считайте оба показателя на одних и тех же прогонах. Вот неделя в Northwind:

Показатель

Прогонов

Агент заявил «готово»

4120

Заявил «готово», и проверка прошла

3950

Ложный успех

170 (4,1% заявлений)

Самоотчёт агента всю неделю выглядел бы здоровым, как и график доступности. Алерт настройте на эти 170. Прогон, который всё ещё pending, учитывается, только когда итог определится.

Почему не спросить другую модель

Напрашивается LLM-судья, который прочитает переписку и решит. В той же работе ни одна конфигурация судьи не превысила AUROC 0,65 на tau2-bench, где 0,5 — это подброшенная монета, а 1 — идеал. Автор объясняет это тем, что судьи опираются на уверенные завершающие фразы, а их в прогонах с ложным успехом хватает. Для первичного отбора прогонов работа предлагает лёгкие обученные детекторы.

Руководство Anthropic по оценке агентов проводит ту же границу в общем виде. Transcript — это запись прогона, где видно, что агент сказал и сделал, а outcome — конечное состояние среды. В его примере оценки поддержки возврат проверяется по состоянию, а тон — LLM-рубрикой. Модели оставьте оценку тона. Запись пусть читает код.

Вывод

Глубже

From Confident Closing to Silent Failure (arXiv)Замеры, из которых построен график. Работа с воркшопа, автор один, так что это сигнал, а не закон.Demystifying evals for AI agents (Anthropic)Разница между transcript и outcome и виды оценщиков.Silent Success (Tian Pan)Взгляд практика на чтение состояния после записи и на то, что писать по каждому ходу.Refund and cancel payments (Stripe)Почему принятый возврат может позже сорваться и какое событие об этом скажет.
‹ Назад
Предложить улучшениеНашёл ошибку