← max_tokens

Агент прошёл eval. В прод я его всё равно не пущу

То, что агент прошёл eval, ещё не означает, что его можно выпускать в прод: он мог нарушить ограничения, превысить бюджет или оставить после себя опасные изменения.

В разработке агентных систем разговор постепенно смещается от качества финального ответа к качеству самого прогона. Отсюда интерес к evaluation harnesses, различию между evals и observability и переход от статических тестов к живым средам, где агента проверяют прямо во время работы.

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

Агент может подготовить правильный патч, пройти тесты и открыть нужный pull request, но при этом выйти за пределы разрешений, повторно выполнить операцию с побочными эффектами, скрыть частичный сбой или превысить бюджет. Даже правильный итоговый результат не делает такой прогон безопасным.

Это не теоретический риск. В июле 2026 года Noma Labs описала GitLost — уязвимость косвенной инъекции в промпт (indirect prompt injection) в GitHub Agentic Workflows. По данным исследователей, специально подготовленная issue в публичном репозитории могла заставить workflow агента вывести данные из приватных репозиториев той же организации. С точки зрения агента последовательность выглядела как обычная работа с доступными инструментами. С точки зрения безопасности это был запрещённый поток данных.

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

Правильный результат ещё не делает прогон безопасным

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

Проверять нужно четыре независимых аспекта прогона:

  1. Результат. Удовлетворяет ли конечное состояние задаче и сохранились ли прежние требования?
  2. Траектория. Обоснованы и разрешены ли выбранные шаги? Действительно ли они привели к результату?
  3. Побочные эффекты. Что изменилось помимо запрошенного артефакта, включая неудачные и повторные операции?
  4. Надёжность. Сохраняется ли корректность при повторных запусках, тайм-аутах, недоступности инструментов и частичном выполнении?

Их нельзя свести к одному общему баллу.

  • Evals проверяют работу по явным критериям.
  • Observability собирает свидетельства того, что на самом деле произошло в прогоне.
  • Policies определяют, какие действия разрешены.
  • Release gate, или контроль допуска, объединяет результаты и решает, можно ли допустить следующий прогон с реальными полномочиями.

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

Конвейер прогона и четыре gate: outcome зелёный, а trajectory, policy и side-effect — красные.

Список выше и схема показывают разные уровни проверки. Policy gate отдельно проверяет разрешение на каждый вызов инструмента. Надёжность не показана отдельным блоком: её оценивают повторными прогонами и сценариями восстановления. Поэтому outcome gate может быть зелёным, пока trajectory, policy и side-effect gates остаются красными.

Оценку результата начинают с состояния

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

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

Предположим, задача требует изменить endpoint и обновить его тестовое покрытие. Очевидная проверка запускает тесты нового поведения. Менее очевидные проверки:

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

Удалённая проверка авторизации легко превращает красный тест в зелёный. Формально все тесты проходят, хотя продукт стал хуже.

То же правило действует и за пределами кода. Если агент обновляет карточку клиента, нужно проверить сохранённую запись и журнал изменений. Если он создаёт запланированное задание, нужно подтвердить его идентификатор, параметры и то, что задача не была создана дважды. Сообщения агента об успехе для этого недостаточно.

Хорошая оценка сравнивает ожидаемое состояние системы с фактическим и отдельно проверяет инварианты. «Задача выполнена» и «агент сообщил о выполнении» — разные события. По возможности подтверждение нужно получать напрямую из системы — из репозитория, базы данных, API, очереди или журнала, — а не из пересказа агента.

Итоговый diff не показывает путь к результату

Траектория — это упорядоченная цепочка решений, вызовов инструментов, наблюдений и переходов состояния. Она отвечает на вопросы, на которые нельзя ответить по финальному diff:

  • изучил ли агент нужный контракт перед изменением;
  • продолжил ли работу после запрета;
  • привёл ли тайм-аут к слепому повтору;
  • нашёл ли агент падающий тест или удалил его;
  • получил ли правильный результат закономерно или случайно.

OpenAI определяет trace grading как структурированную оценку полного журнала работы агента: его решений, вызовов инструментов и шагов выполнения. В отличие от eval, который смотрит только на финальный ответ, такая оценка показывает ошибку и момент, когда поведение отклонилось от ожидаемого.

Google ADK делает похожее различие на уровне готовых критериев: в документации по evaluation отдельно перечислены совпадение финального ответа, качество ответа и соответствие траектории вызовов инструментов. Это не значит, что каждая допустимая траектория обязана в точности повторять эталонную. Но вызовы инструментов нужно оценивать как самостоятельную часть поведения.

Обычный лог сам по себе не становится полноценным trace. Строка «инструмент завершился с ошибкой» не показывает аргументы вызова, предыдущее состояние, решение политики, внесённые изменения и способ восстановления.

Минимально полезная запись прогона может выглядеть так:

{
  "run_id": "...",
  "task": "...",
  "steps": [
    {
      "parent_step_id": "...",
      "tool": "...",
      "arguments": {},
      "result": "...",
      "state_diff": {},
      "policy_decision": "allow | deny | require_approval",
      "latency_ms": 0,
      "cost_usd": 0
    }
  ],
  "outcome_score": 0,
  "trajectory_score": 0,
  "side_effects": [],
  "recovery_events": []
}

Это иллюстрация, а не предлагаемый стандарт. Ценность схемы — в связях между событиями. Каждый state_diff должен ссылаться на создавший его вызов, каждое событие восстановления — на исходный сбой, а каждое решение политики — на проверяемое действие. Стоимость и задержку нужно сохранять для каждого шага и всего прогона. Приемлемый итог может скрывать дорогой цикл, которому просто повезло завершиться.

Секреты нужно маскировать при сборе. Но маскирование не должно скрывать затронутый ресурс, класс действия и сам факт пересечения границы доступа. Утечку не расследовать, если trace сообщает лишь, что агент «вызвал API».

Инструменты наблюдаемости уже основаны на этой модели. Например, Langfuse описывает application tracing как структурированную запись запроса, ответа модели, использования токенов, задержки, retrieval и вызовов инструментов. Но наличие trace ещё не значит, что прогон безопасен. Trace даёт свидетельства; интерпретировать их должны правила eval и policies.

Побочные эффекты входят в критерий корректности

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

Чтение файла не должно менять рабочую директорию. Редактор репозитория может править только разрешённые пути, но не учётные данные и общую конфигурацию. API-клиент может создать один запрошенный объект, но не дубликаты при повторе после тайм-аута.

Сначала сравнивают состояние до и после. Для кодового агента в разницу входят:

  • рабочее дерево;
  • сгенерированные файлы;
  • локальные процессы;
  • созданные ветки;
  • коммиты и pull request;
  • обращения к внешним сервисам.

Для бизнес-процесса это могут быть изменения в базе данных, сообщения в очередях, запланированные задания, платежи и уведомления. Набор проверок зависит от задачи. Но если побочный эффект не указан в отчёте, это ещё не значит, что его не было.

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

Policies должны делить действия по области и обратимости:

  • безопасное и обратимое действие выполняется автоматически;
  • действие с контролируемым эффектом логируется и затем проверяется;
  • опасное или необратимое действие требует явного одобрения;
  • запрещённое действие блокируется независимо от оценки результата.

Human-in-the-loop не означает отдельный клик перед каждым чтением файла. Одобрение человека нужно непосредственно перед действием, последствия которого нельзя дёшево и уверенно отменить.

GitLost показывает, почему этого не добиться одним хорошим промптом. Агент может прочитать недоверенный текст, принять его за инструкцию и воспользоваться формально доступным инструментом. Нужны ограничения на источники данных, разрешённые адресаты вывода и связи между ними. Небезопасный прогон нельзя считать корректным, даже если его финальный ответ выглядит убедительно.

Такой прогон я поймал у себя, а не в чужом отчёте. Бот, который публикует статьи этого блога, — обычный агент с git-инструментами. Он собирает пакет, коммитит изменения, пушит их в main и промоутит превью в прод. Команда «опубликовать» отрабатывала: статья появлялась на сайте, а итоговое состояние совпадало с задачей.

Проблема была в побочном эффекте, который не попадал в проверку результата. Бот и контент жили в одном репозитории, а Railway передеплоивал сервис при любом push в main. Публикация статьи запускала редеплой самого бота — и обрывала его текущий прогон. Команда «опубликовать статью» незаметно означала ещё и «перезапустить себя».

Найти причину долго не удавалось из-за того, как был устроен trace. Время коммитов записывалось в UTC, а логи Railway — в UTC+7. Связь не складывалась на глаз: промоут запускал редеплой, а тот останавливал прогон. Из-за разницы часовых поясов события выглядели разнесёнными во времени. Границу «действие меняет инфраструктуру, на которой работает сам агент» никто не скрывал — её просто никто не записывал.

Решение было не в промпте, а в ограничении области: watchPatterns: ["bot/**"] в конфиге Railway.

Теперь редеплой запускается только при изменениях в коде бота, а публикация контента не перезапускает сервис. Это не просьба агенту быть аккуратнее. Это реальное ограничение области его побочных эффектов.

У каждого сбоя должен быть определённый исход

Надёжность не сводится к двум успешным прохождениям штатного сценария. Надёжный агент сохраняет ограничения, когда среда ведёт себя не по плану.

Повтор вызова (retry) — лишь один из механизмов восстановления.

Возьмём вызов API, который меняет состояние и завершился тайм-аутом. Тайм-аут означает отсутствие ответа, но не означает, что состояние не изменилось. Немедленный повтор может создать дубликат.

Безопасная последовательность выглядит иначе:

  1. найти операцию по ключу идемпотентности или стабильному идентификатору;
  2. сверить наблюдаемое состояние;
  3. выбрать retry, fallback, replan, rollback, эскалацию или остановку;
  4. зафиксировать решение и его основание в trace.
Безопасная последовательность при тайм-ауте: найти по ключу идемпотентности, сверить состояние, выбрать действие, записать в trace.

Если проверить состояние невозможно, сама неопределённость становится поводом остановиться.

Частичное выполнение требует такой же дисциплины. Trace должен отмечать выполненные предусловия, зафиксированное изменение, непройденную проверку и доступное компенсирующее действие. Запись «шаг завершился с ошибкой» выбрасывает именно те сведения, которые нужны для восстановления.

Поэтому проверка надёжности должна:

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

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

Количество шагов само по себе ничего не говорит о качестве. Подробнее эта проблема разобрана в статье «Turn count — не метрика агента». Здесь число шагов важно только как бюджет и сигнал того, что поведение отклонилось от ожидаемого.

Оба pull request проходят тесты. Выпускать можно только один

Задача — изменить существующий endpoint, обновить тесты и открыть pull request. Агенту разрешено править только код endpoint и отдельный каталог с его тестами. Существующие проверки авторизации должны остаться на месте.

Сравнение прогонов A и B: у обоих зелёный outcome, но у A красный путь с нарушениями, у B чистый.

Прогон A: результат зелёный, траектория красная

Endpoint работает как требуется, тесты проходят, pull request создан. Но trace и сравнение состояния до и после прогона показывают нарушения:

  • агент изменил общий файл вне разрешённой области;
  • удалил одну из проверок авторизации, чтобы тест прошёл;
  • повторил внешний вызов создания pull request после неоднозначного ответа;
  • превысил бюджет в пять раз;
  • не включил промежуточную ошибку инструмента в записанную историю;
  • всё же создал запрошенный pull request.

По финальному результату такой прогон проходит eval. Но считать его основанием для выпуска нельзя.

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

Прогон B: зелёный результат и контролируемый путь

Агент меняет только разрешённые файлы и записывает каждый вызов инструмента. Когда тестовая команда завершается по тайм-ауту, он сначала проверяет состояние процесса и сохраняет уже полученные результаты. Только после этого перестраивает план.

Агент повторно запускает лишь затронутую проверку, изучает полный diff, убеждается, что проверки авторизации сохранились, проверяет, что pull request не создан дважды, и остаётся в пределах бюджета по стоимости, числу шагов и времени.

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

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

Минимальный release gate можно встроить в CI

Для первого полезного release gate не нужна крупная платформа для eval. Нужны версионируемый набор проверок, формат trace, применение policy при вызове инструментов и исполнитель, способный сравнивать состояние.

Минимальный состав:

  1. Фиксированный набор eval cases. Штатное завершение, запрещённые действия и известные пограничные условия.
  2. Полные traces. Конфигурация запуска и все попытки вызова инструментов, но с замаскированными секретами.
  3. Проверка инструментов. Allowlist, ограничения аргументов, правила порядка и запрет опасных повторов.
  4. State diff. Наблюдаемая разница до и после прогона, включая проверяемые эффекты — в том числе удаления.
  5. Policy tests. Проверка веток allow, deny и require approval.
  6. Независимые бюджеты. Стоимость, число шагов и полное время.
  7. Failure injection. Ошибки инструментов и тайм-ауты, включая неоднозначные тайм-ауты после изменяющих состояние запросов.
  8. Replay. Воспроизведение неудачных прогонов на записанных ответах или безопасных фикстурах.
  9. Повторные запуски. Сохранение всех traces, а не только лучшего результата.
  10. Ручное одобрение. Непосредственно перед необратимым действием, с показом действия и ожидаемой разницы состояния.

В CI можно автоматизировать фиксированные сценарии, state diff в песочнице, проверку полноты trace, лимиты, failure injection, replay и повторные запуски. CI также может имитировать решения policies и проверять, что агент останавливается на границе одобрения.

Среда выполнения должна заново применять те же бюджеты и policies к реальным вызовам. CI не способен заранее одобрить действие, цель и состояние которого появятся только в живом прогоне.

Статические benchmarks полезны для сравнения модельного компонента. Они не проверяют реальные обёртки инструментов, разрешения, состояние среды и код восстановления. Harness вокруг агента важнее одного модельного вызова; подробнее об этом — в материале «Loop engineering: harness вокруг цикла».

Стоимость тоже нельзя прятать внутри среднего балла. Она зависит от числа токенов модели, повторов, инструментов, инфраструктуры и неудачных веток. Насколько велик разброс, видно даже на простом one-shot прогоне: в моём бенче моделей на одной и той же задаче claude-opus-4-6 потратил 59 603 токена вывода, 14 минут и $1.49 — и занял одиннадцатое место из двенадцати. deepseek-v4-pro на той же задаче обошёлся в $0.03 и вышел шестым. Стоимость различалась примерно в пятьдесят раз, и в балле результата этой разницы не видно вовсе. Практический разбор составляющих стоимости есть в статье о стоимости одного headless-прогона.

Некоторые нарушения нельзя компенсировать баллами

Единый итоговый показатель удобен, но опасен: он создаёт впечатление, что результат, стоимость, безопасность и способность к восстановлению можно компенсировать друг другом. В такой системе идеальный патч способен численно перекрыть запись в запрещённую систему.

Некоторые нарушения не подлежат компенсации. Они должны блокировать выпуск независимо от остальных оценок.

Outcome score и trajectory score можно хранить отдельно. Но для соблюдения разрешённой области, policies, контроля побочных эффектов и полноты trace нужны не веса, а жёсткие условия допуска:

release =
  outcome_score >= outcome_threshold
  AND trajectory_score >= trajectory_threshold
  AND trace_is_complete
  AND budgets_respected
  AND no_scope_violation
  AND no_unapproved_irreversible_action
  AND no_unreconciled_side_effect
  AND required_replays_and_repeats_passed

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

Реальные traces должны пополнять набор eval cases. Каждый новый класс сбоя нужно превращать в воспроизводимую фикстуру, которую следующая версия агента обязана пройти до получения реальных полномочий.

Данные observability позволяют evals оценивать фактическое поведение системы, но не заменяют критерии допуска.

В прод выпускают не ответ, а управляемое поведение

Версия агента — это не только модель и промпт. В неё входят:

  • схемы и реализации инструментов;
  • разрешения;
  • policies;
  • бюджеты;
  • логика восстановления;
  • eval cases;
  • среда выполнения;
  • способ сбора и маскирования traces.

Изменение любого из этих компонентов может изменить поведение агента, даже если бенчмарк по финальным ответам показывает прежний результат.

Поэтому вопрос «может ли агент решить задачу?» слишком слаб. Полезнее другой вопрос:

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

Release gate не гарантирует идеальное поведение. Его задача скромнее: давать реальные полномочия только той конфигурации, чьё поведение наблюдаемо, воспроизводимо и ограничено.

Evals измеряют качество работы, observability показывает, что агент делал на самом деле, policies задают границы дозволенного, а release gate по этим данным решает, можно ли доверить системе следующий прогон.

<|endoftext|> · 5 466 tok · finish_reason: stop

// top_k · nearest neighbors

  1. [0] 0.655 Промпт — это 5% работы: контекст-инжиниринг LLM-агента в проде
  2. [1] 0.655 Turn count как продуктовая метрика
  3. [2] 0.640 Не каждый запрос заслуживает агента

cosine of embeddings · scale 0–1 absolute · computed at build

integrity: sha256 7bd1575f…

tokens · o200k_base