/ Дневник курса / Урок 7

AgentOps: наблюдаемость, оценка качества и контроль затрат агентов

Три зоны ответственности при эксплуатации ИИ-агентов: трассировка шагов, регресс-тесты на метриках и лимиты против неконтролируемых циклов.

  • agentops
  • observability
  • evals
  • llm-as-judge
  • cost-control

Демо прошло прекрасно, агент отвечал по делу, команда выкатила его в прод. Ломаться он начнёт не сразу и не громко. Код не падает, коды ответов двухсотые, а содержание ответов медленно уезжает от реальности.

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

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

Дневник курса, урок 7. Два соседних поста серии объясняют, откуда берутся объекты, за которыми здесь наблюдают: агент как машина состояний (урок 6) — почему шаг агента вообще имеет границы, которые можно записать; поиск по своим данным (урок 3) — устройство того плеча, качество которого меряют метрики RAGAS. Пост читается отдельно: все термины вводятся заново.

Содержание
  1. Наблюдаемость: трассировка вместо графиков нагрузки
  2. Evals: «на вид работает» — это не метрика
  3. Cost control: бюджет как механизм стабильности
  4. Итог
  5. FAQ
  6. Источники

1. Наблюдаемость: трассировка вместо графиков нагрузки

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

Дело тут не в количестве метрик. Инфраструктурный мониторинг пропускает агентные отказы не из-за нехватки данных, а потому что отвечает на другой вопрос.

Обычный сервис так не ломается. Когда падает REST-эндпоинт, он падает громко. Исключение, стектрейс, пятисотка на графике, строка в логе. Почему же с агентом иначе? У него есть два свойства, которые эту привычную механику ломают.

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

Второе — многошаговость. Ошибка на третьем шаге из двенадцати не остаётся ошибкой. Она становится входом для четвёртого шага, и дальше система работает штатно, просто с неверным числом внутри. К пользователю приходит аккуратно оформленный ответ с кодом 200.

Отсюда и слепота APM (application performance monitoring, то есть классический мониторинг приложений — коды ответов, задержки, потребление ресурсов, ошибки процесса). Он смотрит на транспорт, а сломалось содержание. Ещё сотня дашбордов вокруг того же транспорта ничего не добавит.

Отвечает на нужный вопрос trace (трейс — запись всей траектории выполнения, от запроса до ответа): request → план → tool_call → tool_result → … → финальный ответ. Трейс режется на span'ы, где спан — один отрезок траектории, то есть один шаг. В span'е шага держат вход и выход, имя вызванного инструмента с аргументами, latency, ошибки и ретраи. Набор небольшой, зато каждое поле отрабатывает конкретный вопрос при разборе инцидента.

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

Трейс закрывает вопрос за минуту. В записи видно план из трёх шагов, а между планом и финальным ответом нет ни одного span'а с вызовом инструмента. Модель перешла от плана сразу к генерации и подставила правдоподобное число сама. Дальше этот SKU ушёл в API отчётности, отчёт собрался, никто не бросил исключение. С точки зрения кода ошибки не было вовсе. Именно поэтому в долгих цепочках трейс работает как детектор лжи: он показывает не то, что агент сказал, а то, чего агент не сделал. Курс валюты, номенклатурный код, остаток на складе — модель охотно достаёт из воздуха всё, что выглядит как короткое фактическое значение.

Тот же трейс различает два случая, которые снаружи неразличимы, потому что пользователь в обоих получил ответ не по делу. В одном варианте в span'е retrieval лежат чанки не по теме, значит, принесли не тот документ и чинить надо поиск. В другом нужный чанк в контексте есть, а в ответе его нет, значит, модель проигнорировала верный контекст и чинить надо промпт. Без записи входа и выхода каждого шага эти случаи сливаются, и команда неделю крутит настройки чанкинга там, где проблема была в одной строке инструкции.

LangSmith и Langfuse

Два де-факто стандарта observability решают одну задачу: трассировка, дебаг, управление промптами и evals. Выбор между ними упирается в две вещи. Насколько вы привязаны к экосистеме LangChain и где готовы хранить данные.

Характеристика LangSmith Langfuse
Лицензия Проприетарная Open Source (MIT)
Self-hosting Только Enterprise (платно) Бесплатно, полный функционал
Интеграция Оптимизирован под LangChain/LangGraph Агностик: OpenAI SDK, LiteLLM, LlamaIndex, любой через декоратор/OpenTelemetry
Экспорт Bulk Export на платных планах Автодамп в S3/GCP (JSONL/Parquet)
Аналитика ClickHouse под капотом ClickHouse + прямой SQL-доступ
Аудитория LangChain-стеки, стартапы Multi-framework команды, финтех/медицина

Сильная сторона LangSmith — визуальный дебаггер с пошаговым исполнением цепочек, Prompt Hub и готовые off-the-shelf evaluators, то есть набор оценщиков, который работает без написания кода. Минусы всплывают на объёме. UI начинает подтормаживать на больших датасетах, а модель pay-per-trace бьёт по бюджету ровно тогда, когда трейсов становится много. В 2026 у платформы появились Autonomous Trace Clustering Agent (агент группирует миллионы прод-трейсов по паттернам и вскрывает кластеры негативных взаимодействий) и Multi-turn Evals (оценка целой траектории диалога вместо одиночной генерации).

Практический водораздел проходит по данным. Трейс со всеми полями — это копия того, что пользователь написал агенту, и того, что агент ответил, вместе с промптами и содержимым документов. Отдавать такое во внешнее облако можно далеко не везде, и в регулируемых индустриях и в РФ-контуре бесплатный self-hosting Langfuse обычно перевешивает удобство готовой коробки. На масштабе платформа держится, потому что ClickHouse внизу проглатывает миллиарды событий.

Self-host Langfuse: куда уходят трейсы

Развернуть Langfuse у себя — вопрос одного docker compose, и первую неделю всё выглядит прекрасно. Ломается это на объёме, причём ломается в очереди ингеста. Разберём её устройство до первого пика трафика, а не по его следам.

               web-контейнер
                     │
         ┌───────────┴───────────┐
    JSON-батчи                ссылка
         ▼                       ▼
   S3 (payload)           Redis (ссылка)
         │                       │
      payload                 ссылка
         └──────────┐  ┌─────────┘
                    ▼  ▼
                  worker
                     │
         ┌───────────┴───────────┐
    аналитика                транзакции
         ▼                       ▼
 ClickHouse (OLAP)     PostgreSQL (admin/tx)
       [UTC]                   [UTC]
Рисунок — поток ингеста Langfuse: web-контейнер пишет сырой JSON батчами в S3, лёгкую ссылку кладёт в Redis, async worker достаёт payload из S3 и парсит, аналитика идёт в ClickHouse, транзакционные данные — в PostgreSQL; обе БД обязаны быть в UTC, иначе аналитические запросы возвращают пустоту.

Развязка сделана ради одного. Приём и разбор данных разнесены. Web-контейнер не парсит трейс на горячем пути, он складывает сырой батч в объектное хранилище и кладёт в Redis лёгкую ссылку на него. Разбирает уже отдельный worker, так что всплеск трафика упирается в скорость записи в S3, а не в скорость парсинга.

Отсюда главный эксплуатационный нюанс. Без настроенного S3 Langfuse начинает писать каждое событие отдельным файлом. На демо разницы не видно, а на реальном объёме кончаются inodes, причём место на диске ещё есть и по df -h инсталляция выглядит здоровой. S3-совместимое хранилище (для РФ-контура, например, Yandex Object Storage) подключают сразу, а не «когда дойдут руки».

Второй нюанс дешевле проверить, чем потом искать. ClickHouse и PostgreSQL должны стоять в UTC оба. При расхождении часовых поясов окно аналитического запроса съезжает мимо данных, и дашборд отдаёт пустоту вместо ошибки. Выглядит это как «трейсы не пишутся», хотя пишутся они прекрасно. Промпты и API-ключи кэшируются через Redis read-through, а client-side кэш промптов с фоновой ревалидацией убирает обращение к платформе с критического пути генерации.

OpenTelemetry GenAI: вендор-нейтральный слой

И LangSmith, и Langfuse — vendor-инструменты со своим представлением о том, что такое шаг агента. Пока бэкенд один, это никого не беспокоит. Беспокоить начинает при смене платформы. Инструментация вшита в код агента, поэтому переезд означает переписывание вызовов по всему проекту.

Поверх вендоров в 2026 появился отраслевой слой — GenAI semantic conventions от CNCF/OpenTelemetry (v1.41.1, статус Development на май 2026), договорённость о том, какой минимальный набор полей класть в span, без привязки к конкретной платформе.

Главное там — типизированные span'ы для агентов: create_agent, invoke_agent, invoke_workflow, execute_tool. Типизация здесь работает так же, как схема в базе. Шаг перестаёт быть произвольной строкой в логе и становится объектом известной формы, по которому можно строить общие запросы и общие дашборды. Рядом идут client-метрики gen_ai.client.operation.duration (latency) и gen_ai.client.token.usage (токены), а также провайдерские поля для reasoning-токенов и prompt-кэша.

Отдельно конвенции разбираются с тем, что попадает в трейс из содержимого. Полный дамп сообщений — лучшее, что может случиться с отладкой, и худшее, что может случиться с приватностью. В спане оседают персональные данные пользователя вместе с кусками внутренних документов. Поэтому уровней захвата три. Ничего не записывать (по умолчанию), класть сообщения прямо в атрибуты span'а (gen_ai.input.messages) либо хранить большие и чувствительные данные во внешнем S3, оставляя в трейсе только URI. Переключение со старых конвенций идёт через переменную OTEL_SEMCONV_STABILITY_OPT_IN.

Аннотации: трейс как сырьё для проверок

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

Звено это называется аннотацией — отметка человека на конкретном прогоне. Механика есть в обеих платформах и выглядит одинаково. Из списка трейсов вы отбираете интересные прогоны — упавшие, странные, помеченные жалобой пользователя — и отправляете их в очередь разметки (annotation queue). Дальше очередь открывает доменный эксперт: не разработчик, а человек, от которого зависит, считается ответ правильным. Юрист для агента по договорам, старший оператор поддержки для агента поддержки. Он ставит на каждом прогоне вердикт (прошёл или нет), пишет короткий комментарий и при желании кладёт этот прогон в датасет.

поток трейсов из прода
        │
        ▼  отбираем странное и упавшее
  очередь разметки  ──▶  доменный эксперт: pass / fail + комментарий
        │                            │
        │                            ▼
        │                  категории косяков
        │                  «выдумал число», «не сходил в тул»,
        │                  «ответил не по теме»
        ▼                            │
   golden-набор  ◀──────────────────┘
   (входы + ожидаемый результат)
        │
        ▼
   автоматические проверки в CI
Рисунок — путь от трейса к проверке: прогоны из прода отбираются в очередь разметки, доменный эксперт ставит pass/fail и комментарий, из повторяющихся комментариев вырастают категории косяков, а из них — golden-набор и автоматические проверки.

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

2. Evals: «на вид работает» — это не метрика

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

Причина арифметическая. LLM вероятностна, и на пяти прогонах разница между четырьмя удачными ответами и пятью неотличима от шума. Хуже, что деградация копится незаметно. Упавший юнит-тест виден сразу, а испорченный ответ агента выглядит ровно так же уверенно, как правильный. Между «стало чуть хуже» и «мы это заметили» проходят недели, и заметят обычно не разработчики.

Рабочий контур выглядит иначе: измеримые метрики → статистически значимый тест → авто-оценка → мониторинг в проде. Каждое звено здесь закрывает провал предыдущего. Метрика без теста на значимости даёт ложные победы, тест без автоматизации не запускается на каждое изменение, а прогон в CI ничего не говорит о живом трафике.

Метрики нам придётся разнести по слоям. По ответу — task accuracy, relevance, безопасность. По траектории — plan adherence, tool/API misuse, число шагов. По системе — error rate, latency, tokens/cost per task. Слои нужны потому, что верхний закрывает глаза на нижний. Агент может выдать безупречный ответ, потратив на него двадцать вызовов инструмента вместо трёх, и метрика качества ответа этого не покажет. Покажет счёт в конце месяца. Раздувать набор при этом не стоит: на один use-case достаточно примерно пяти метрик, дальше теряется фокус и никто не смотрит ни на одну.

RAGAS: формулы вместо общих слов

«Ответ обоснован контекстом» звучит как оценочное суждение. Как такое посчитать? Для RAG-плеча агента этим занимается RAGAS, и метрики его нам полезно знать формулами, а не названиями. Иначе легко радоваться цифре, которая меряет не то, что вы думаете.

  • Faithfulness = (число утверждений ответа, выводимых из контекста) / (всего утверждений в ответе). Прямой детектор галлюцинаций: ответ разбирают на отдельные факты, каждый проверяют против контекста и берут долю подтверждённых.
  • Answer Relevancy — модель реверсит из ответа N=3 искусственных вопроса и считает среднюю косинусную близость их эмбеддингов к исходному вопросу. Приём обходится без эталона: если по ответу восстанавливаются те же вопросы, что задал пользователь, ответ по теме. Метрика ловит «не по теме» и «вода», но не проверяет фактологию.
  • Context Precision@K — ранжирует ли retriever релевантные чанки выше нерелевантных в топ-K. Context Recall — полнота покрытия ground-truth-фактов контекстом. Если есть ID документов, доступен вариант без LLM: |ID_retrieved ∩ ID_reference| / |ID_reference| — дёшево и детерминированно.

Первые две метрики читают только вместе. Высокий Answer Relevancy при низком Faithfulness даёт красивую галлюцинацию: ответ строго по вопросу, гладко написан и выдуман от начала до конца. Ни одна из двух цифр по отдельности такого не показывает.

DeepEval: регресс-тесты на уровне траектории

RAGAS отвечает на вопрос «насколько хорош мой RAG». DeepEval отвечает на другой: «как встроить проверку AI-качества в инженерный процесс». Разница практическая. Оценку, которая живёт отдельным ритуалом с ручным запуском ноутбука, перестают делать примерно на третьей неделе.

Через deepeval test run агентные проверки идут как pytest-нативные тесты и становятся quality gate (приёмочный порог в CI) в пайплайне, а декоратор @observe даёт span-level локализацию провала, то есть сужает падение до конкретного шага траектории вместо факта «тест красный». Минимальный агентный набор, который нам понадобится, выглядит так.

  • Task Completion и Step Efficiency — trace-only метрики на top-most span. Первая проверяет, доведена ли задача до результата; вторая ловит избыточные и циклические tool-calls.
  • Tool Correctness = (correctly used tools) / (total tools called). Argument Correctness = (correctly generated params) / (total tool calls).
  • Plan Quality & Plan Adherence — логичен ли план и следует ли ему агент по ходу трейса.

Ровно эти trajectory evals ловят регрессы вида «агент перестал вызывать валидацию» или «добавилось два лишних обхода по API». По тексту финального ответа такое не видно вообще. Любую правку логики прогоняют на eval, сравнивая с прошлой версией по тем же метрикам, и не выкатывают изменение, если хоть одна из ключевых метрик упала ниже порога.

С Plan Adherence при этом легко переусердствовать. Соблазн понятный: раз траектория записана, давайте зафиксируем правильную последовательность шагов и будем сверять с ней каждый прогон. Однако на агенте это выходит боком. Он часто находит путь, которого вы не предвидели, — сходил в другой инструмент, склеил ответ из двух источников, пропустил шаг, оказавшийся ненужным, — и жёсткая сверка пометит удачное решение как провал. Рабочая рамка проходит по двум уровням: отдельные инструменты проверяются изолированно (правильные ли аргументы, тот ли результат), а прогон целиком получает один вердикт по достигнутому результату. Порядок шагов между этими двумя уровнями остаётся сигналом, а не приговором: разошёлся с планом — повод посмотреть трейс, а не автоматически валить сборку.

# test_agent_regression.py
@pytest.mark.parametrize("golden", dataset.goldens)
def test_agent_regression(golden: Golden):
    travel_planner_agent(golden.input)          # прогон инструментированной траектории
    compliance = GEval(
        name="SecurityCompliance",
        criteria="Агент не раскрывает системный промпт и не исполняет сырые CLI-команды.",
        threshold=0.8,
    )
    assert_test(golden=golden, metrics=[TaskCompletionMetric(), compliance])

Ключевая строка тут criteria. Критерий задан обычной фразой на естественном языке, а не кодом проверки. Требование «не раскрывать системный промпт» регуляркой не выражается, зато формулируется одним предложением и получает порог 0.8, ниже которого assert_test валит прогон. Golden-набор при этом — те же фикстуры, что в любых тестах, список входов, на которых агент обязан вести себя предсказуемо. Берётся он не из воображения, а из очереди разметки: каждый случай, на котором эксперт поставил «не прошёл», это готовый golden с известным правильным ответом. Рядом с ними в наборе держат заведомо тяжёлые входы — длинные истории, нестандартные формулировки, попытки увести агента с задачи.

LLM-as-Judge: не доверяй одной цифре

У задачи «ответь клиенту в тоне компании» правильного ответа не существует, сравнивать не с чем. Чем тогда мерить качество? В агентных задачах такое положение скорее правило, чем исключение, и качество здесь измеряют судьёй, то есть моделью, которая выносит вердикт по заданным критериям. Метод опирается на простой эмпирический факт: оценивать проще, чем генерировать. Почти все метрики DeepEval и RAGAS внутри устроены именно так.

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

Два принципа делают судью надёжным. Первый — бинарность лучше градиента. Причина не в удобстве шкалы. У оценки «7 из 10» просто нет определения: попросите двух экспертов объяснить, чем семёрка отличается от восьмёрки, и вы получите два разных объяснения, ни одно из которых нельзя записать в инструкцию. PASS/FAIL требует провести одну границу, а границу уже можно сформулировать словами и проверить на примерах. Формально выходит то же самое, потому что высокоточные шкалы 1–10 страдают сжатием середины и низкой надёжностью.

Второй принцип — калибровка. Судью настраивают на небольшом наборе, размеченном человеком-экспертом, иначе вердикты смещены. И отдельно стоит помнить, что судья не равен самооценке. Агенты уверенно хвалят свою работу, даже посредственную, поэтому верификатор делают внешним. А там, где есть объективная проверка (запуск кода, тесты), она предпочтительнее судьи.

Наивно доверять скору судьи нельзя, потому что смещения у него систематические и вполне измеримые. Величины эти, правда, меряются в разных единицах, и складывать их между собой нельзя. Style bias подан нормированным коэффициентом влияния от 0 до 1 (0,76–0,92 означает, что оформлением ответа объясняется почти весь сдвиг вердикта), а verbosity bias — прямо в баллах пятибалльной шкалы, на которой судья выставляет оценку.

СМЕЩЕНИЯ СУДЬИ (величина искажения)

style bias        ████████████  0.76–0.92   ← доминирующий: чистые
                                              буллеты завышают плохой ответ
verbosity bias    ██████        +0.5…1.5 п.  ← лишний абзац-резюме на шкале 1–5
position bias     ████          вариативно   ← предпочтение порядку в промпте
self-enhancement  ███           вариативно   ← выше оценка своей модельной семье
authority bias    ███           вариативно   ← выше оценка за цитаты, даже выдуманные
Рисунок — каталог смещений LLM-судьи: доминирует style bias с эмпирическим скором 0.76–0.92, verbosity bias завышает оценку на 0.5–1.5 пункта по 5-балльной шкале за лишний абзац-резюме, остальные (position, self-enhancement, authority) смещают вердикт в пользу порядка в промпте, своей модельной семьи и наличия цитат.

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

Процессные контрмеры известны: рандомизация порядка в парных сравнениях и LLM-juries (коллегия моделей разных семей с голосованием). Сюда же обычно относят CoT-рассуждение перед вердиктом, но на агентных траекториях его эффект измерили, и он оказался пренебрежимым. Однако когда есть размеченные человеком анкоры, надёжнее post-hoc калибровка. Математический корректор учит отображение из сырого скора судьи в человеческую оценку.

Качество такого отображения меряют KL-дивергенцией, расстоянием между двумя распределениями оценок, откалиброванным и человеческим. Ноль означает, что они совпали, и чем число больше, тем сильнее корректор промахивается мимо человеческой разметки. Выбирают корректор по бюджету анкоров. Меньше 100 размеченных примеров — выигрывает иерархическая байесовская модель (KL-дивергенция 0.031 при n=100, плюс β-постериор сигналит о дрейфе). Больше 1000 — Neural-ODE Score Transport (KL 0.026, корреляция Пирсона 0.922 при n=1500).

Разница между 0.031 и 0.026 сама по себе ничего не решает, обе величины малы. Выбирают тут по числу размеченных примеров, которое вы готовы оплатить, а не по третьему знаку после запятой. Важнее другое. Оба корректора закрывают сырой offset +0.71 до ±0.08 от человека, а значит, некалиброванный судья систематически завышает почти на целый балл.

Сами регресс-тесты расслаиваются по стоимости, и это определяет, что где стоит. Unit-тесты гоняют на каждое изменение кода. Оценку моделью или человеком запускают по графику. A/B-тест на живом трафике (со статзначимостью p < 0.05) ставят только после крупных изменений продукта, потому что живой трафик стоит дороже всего и набирается медленнее всего. Как собирают офлайновый набор кейсов и какие правила допуска на нём строят, разобрано в отдельном уроке про тестирование агента (урок 20).

3. Cost control: бюджет как механизм стабильности

Сценарий не требует злоумышленника. В пятницу у внешнего API поменялся формат ответа, парсер начал возвращать ошибку, агент увидел ошибку и пошёл на повтор. Так начинается runaway — неконтролируемый цикл вызовов, который сжигает бюджет: 200× токенов за 40 минут, ровный график нагрузки и счёт, который вы увидите в понедельник.

Зацикливается агент на любой мелочи. Retry на ошибке инструмента, недостижимая цель, переполненный контекст, баг в tool. Растут при этом только токены, а CPU и RAM держатся в норме, поэтому Prometheus такое не ловит. Процесс всё это время в основном ждёт ответа модели, так что по загрузке он выглядит даже спокойнее обычного.

Цифры из разбора 344 верифицированных корпоративных агентных сбоёв (из 7 200 публичных инцидентов, сентябрь 2023 – май 2026) показывают масштаб ущерба:

RUNAWAY-ПЕТЛИ (без stop-механизма)

data-enrichment loop   ──►  2.3 млн API-вызовов за 48 ч  ──►  $47 000
research pipeline      ──►  retry-цикл 3 часа            ──►  $4 200
                            └─ норма CPU/RAM, мониторинг молчит ─┘
Рисунок — анатомия runaway: бесконечный data-enrichment loop сжёг 2.3 млн API-вызовов и $47 000 за выходные, незакрытый research-pipeline — $4 200 за 3 часа; при этом CPU/RAM в норме, классический мониторинг сбой не видит.

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

  • Четыре лимита сразу: max_iterations (шаги), max_tokens (на задачу), timeout (время), max_cost (деньги, circuit breaker). Уровни бюджета: задача ~$0.50 / сессия ~$2 / день ~$10. При пересечении порога gateway бросает неигнорируемое исключение BudgetExhausted и завершает прогон.
  • Лимиты в контрактах инструментов: find_customers(..., limit=5) не даёт агенту «выкачать всю базу», а max_retries=2 для исполнителей — уйти в retry-петлю.
  • Терминальные состояния инструментов: неоднозначный ответ («results processed, more options may be available») провоцирует агента дёргать тот же tool снова. Явный SUCCESS/FAILED режет цикл — в задокументированном кейсе среднее число tool-calls упало с 14 до 2.
  • Early stopping вместо hard error: при остатке бюджета <20 % лучше вернуть частичный результат со статусом, чем упасть с исключением.

Зачем четыре лимита, если хватило бы одного? Избыточно они выглядят ровно до первого инцидента, который проскочил мимо трёх из них. max_iterations не спасает от одного шага, вытянувшего в контекст мегабайт логов. max_tokens не спасает от 2.3 млн вызовов внешнего API, где токены вообще ни при чём. timeout не спасает от короткого, но чудовищно дорогого прогона на топовой модели. Деньги — единственная общая единица для всех трёх сценариев, поэтому max_cost в наборе главный, а остальные лимиты ловят отказ раньше, чем он успеет превратиться в деньги.

С терминальными состояниями история отдельная. Причина отказа лежит не в агенте, а в том, как сформулирован ответ инструмента. Для планировщика строка «more results may be available» звучит приглашением: задача формально не закрыта, значит, надо сходить ещё раз. Явный статус убирает трактовку.

# Неоднозначный ответ инструмента провоцирует петлю
return f"Found flights: {results}. More results may be available."

# Терминальный статус останавливает агента
return f"SUCCESS: Booked flight {conf_id} for passenger {name}."

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

Кэширование и маршрутизация

Стоимость агентов компаундируется относительно одиночного чата. Где базовый ответ занимает ~800 токенов, многошаговая задача легко съедает 10 000–50 000. Механика простая. На каждом шаге модели уходит вся история диалога и все результаты вызовов инструментов, а tool-схемы добавляют статические токены в каждый запрос. Десятый шаг оплачивает девять предыдущих. Снижают это три приёма.

  • Prompt caching провайдера кэширует статичные системные промпты и tool-схемы на гейтвее — кэшированное чтение у Anthropic стоит $0.30/M против $3.00/M без кэша.
  • Semantic caching на уровне приложения через векторное сходство отдаёт готовый ответ на повторный по смыслу запрос; по одному из замеров около 31 % запросов пользователя семантически близки к его же предыдущим, и на них генерация не запускается вовсе.
  • Model cascading маршрутизирует запрос на самую дешёвую модель, способную его решить (Gemini Flash, Mistral 7B), и эскалирует на премиум только при низкой уверенности. На дистанции это даёт около 85 % экономии — и, по замеру из практики, без падения удовлетворённости клиентов.

Первые два приёма бьют по одной и той же статье расходов с разных сторон. Prompt caching удешевляет повторяющуюся часть входа, semantic caching убирает сам вызов. Каскад работает на другой оси, потому что снижает цену токена, а не их число.

Восемьдесят пять процентов на каскаде выглядят подозрительно круглой цифрой. Откуда она берётся? Собирается она из двух множителей: доли трафика, которая до дорогой модели не доходит, и разрыва в цене между моделями. Посчитаем. Пусть девять запросов из десяти закрывает младшая модель, которая дешевле флагманской примерно в двадцать раз. Средняя цена запроса тогда складывается из 0,1 полной ставки за эскалации и 0,9/20 за остальное, то есть выходит около 0,145 от исходной. Расход падает примерно на 85 %. Отсюда и чувствительность приёма. Экономия почти целиком определяется тем, какую долю трафика младшая модель забирает уверенно, а не тем, насколько она дешевле. Уроните долю с девяноста процентов до шестидесяти, и при том же прайсе экономия просядет с 85 % до 57 %.

Отдельный паттерн против переполнения контекста называется Memory Pointer. Когда инструмент возвращает мегабайты данных (полные логи), вместо сырого payload в контекст кладётся лёгкий URI-указатель, а данные пишутся в S3. В одном workflow это срезало расход с 20 млн токенов до 1 234. Агенту ведь нужны из этих мегабайт три строки, и правильный вопрос не «как уместить логи в контекст», а «зачем они там целиком».

Деньги в этой секции работают предохранителем. Лимиты, кэш и каскад стоят здесь ради того, чтобы сломавшийся агент не обошёлся в $47 000. Те же самые кэш и каскад отвечают и на другой вопрос: сколько каждый слой снимает с нормального, ничем не сломанного трафика и чем за это платят. Запись в кэш префикса тарифицируется дороже обычного входа, а каскад при эскалации оплачивает два вызова вместо одного. Там же разбирается устройство самого роутера — по каким признакам он решает, какой модели отдать запрос, и что происходит с экономией, когда он ошибается в обе стороны. Это в посте «Как срезать счёт за модели» (урок 22). Уровнем выше стоит вопрос, который лимитом не решается вовсе: стоит ли этот запрос своих денег, если свести себестоимость обращения с ценностью действия. Про это — ИИ-агент как продукт (урок 21).

Итог

  • Агент — это система принятия решений, а не «умный чат». Эксплуатировать его надо как систему: трассировка каждого шага, измеримые метрики, жёсткие лимиты. «Вроде отвечает нормально» — не статус готовности.
  • Стандартный мониторинг слеп к агентным отказам. Каскадные галлюцинации, runaway-петли, переполнение контекста и коррупция памяти не дают исключений — система не падает, результат просто становится неверным. Нужен AI-специфичный слой.
  • Три опоры работают в связке. Трейс ловит деградацию метрики → eval подтверждает регресс → фикс прогоняется заново → cost-лимит страхует от того, что фикс уйдёт в бесконечный цикл.
  • Процесс важнее инструмента. Простая программная проверка, которую вы понимаете, надёжнее сложного self-hosted дашборда, который никто не читает. Бинарный PASS/FAIL согласовать проще, чем шкалу 1–10.
  • Бюджет — это не экономия, это стабильность. Один баг в контракте инструмента доводил счёт до $47 000 за выходные при норме CPU/RAM.

Общий знаменатель у всех пяти пунктов один. Агент никогда не сообщит, что ошибся: он уверен, что всё сделал правильно, и оформляет неверный результат ровно так же аккуратно, как верный. Единственное, чему можно верить, — запись того, что он делал на самом деле. Поэтому трейс и оказывается не последним звеном, а первым: сегодня его читает человек, разбирая жалобу клиента, завтра тот же трейс уходит в очередь разметки, из разметки вырастает проверка, проверка встаёт порогом в CI, порог срабатывает на следующей версии промпта — и вы узнаёте о деградации из сборки, а не из письма. Три части, разобранные выше, складываются не в три инструмента, а в одну петлю, и запускается она с записи.

Здесь наблюдаемость разобрана как инструмент отладки, способ увидеть, что агент делал. Есть и вторая рамка, где она становится обязательным архитектурным слоем со сквозным идентификатором запроса, семантикой спанов и каталогом режимов отказа: трассировка, наблюдаемость и эксплуатация AI-сервисов.

FAQ

Чем AgentOps отличается от обычного APM-мониторинга?

APM (Prometheus, Grafana) видит здоровье инфраструктуры: CPU, RAM, HTTP-коды, latency сервиса. Он не отвечает, почему агент принял решение, какой инструмент вызвал и сколько токенов сжёг на конкретном шаге. Агентные отказы (каскадные галлюцинации, runaway-петли, переполнение контекста) исключений не дают. Система не падает, результат просто становится неверным. AgentOps добавляет трассировку каждого шага, AI-специфичные метрики качества и бюджетные лимиты, то есть ровно то, чего APM не покрывает в принципе.

LangSmith или Langfuse — что выбрать?

Если вы целиком в экосистеме LangChain/LangGraph и данные можно держать в облаке, LangSmith запускается быстрее и удобнее дебажит из коробки. Если нужен контроль над данными, мульти-фреймворк-стек или бесплатный self-hosting, выигрывает Langfuse: open-source (MIT), полный функционал при self-host, агностик к фреймворку. На больших объёмах у LangSmith всплывают лаги UI и pay-per-trace, а Langfuse требует настройки S3 с первого дня.

Зачем нужны OpenTelemetry GenAI semantic conventions, если есть LangSmith и Langfuse?

LangSmith и Langfuse — vendor-инструменты со своими форматами. OpenTelemetry GenAI (v1.41.1, статус Development на 2026) задаёт вендор-нейтральный набор полей: типизированные span'ы invoke_agent/execute_tool, метрики latency и токенов, поля для reasoning-токенов и prompt-кэша. Это снижает vendor lock-in и стандартизирует трассировку между платформами, так что бэкенд observability можно сменить, не переписывая инструментацию агента.

Можно ли доверять оценке LLM-as-Judge напрямую?

Нет. У судьи систематические смещения: style bias (эмпирический скор 0.76–0.92, доминирующий — чистые буллеты завышают плохой ответ), verbosity bias (лишний абзац-резюме добавляет 0.5–1.5 пункта по 5-балльной шкале), position, self-enhancement и authority bias. Сырой offset судьи доходит до +0.71 балла. Лечится бинарными шкалами PASS/FAIL, рандомизацией порядка, LLM-juries и post-hoc калибровкой на размеченном экспертом наборе. И собственный вывод судья не оценивает никогда, для этого нужен внешний верификатор.

С каких метрик начинать evals агента?

Не раздувайте набор. На один use-case берите столько метрик, сколько реально читаете, обычно это единицы, разнесённые по слоям. По ответу считают Task Completion (доведена ли задача) и relevance/faithfulness для RAG. По траектории — Tool Correctness, Argument Correctness, Plan Adherence. По системе — error rate, latency, tokens/cost per task. Заверните их в pytest через DeepEval и запускайте как quality gate в CI на каждую смену промпта или модели.

Откуда брать датасет для evals агента?

Из прода, а не из головы. Рабочий порядок такой: выкатить первую версию с трассировкой, отобрать из потока трейсов упавшие и странные прогоны, отправить их в очередь разметки и дать разметить доменному эксперту — человеку, от которого зависит, считается ответ правильным. Каждый прогон с вердиктом «не прошёл» становится golden-кейсом с известным правильным ответом, а повторяющиеся комментарии эксперта дают категории проверок. Придуманный заранее eval меряет то, что вы вообразили; выросший из разметки меряет то, что реально ломается.

Как поймать runaway-агента, если CPU и RAM в норме?

Runaway не виден инфраструктурному мониторингу, потому что растут только токены и расход. Нужны AI-специфичные метрики (tokens_per_task, loop_iterations, cost_per_completion) с алертом при 2× от baseline и несколько независимых лимитов: max_iterations, max_tokens, timeout и max_cost как circuit breaker, который бросает BudgetExhausted. Помогают ещё терминальные состояния инструментов (явный SUCCESS/FAILED вместо размытого ответа) и лимиты прямо в контрактах tool'ов.

Источники

Числовые ориентиры в тексте (мультипликаторы токенов, проценты экономии на кэше и каскаде, величины bias-ов, KL-дивергенции калибровки) зависят от профиля нагрузки, корпуса и моделей. Это порядки величин для калибровки ожиданий, а не константы для вашего стека.