Agent Evals

Как понять, что агент «готов»: Definition of Done (DoD) + простые метрики качества (has_citations, answer_grounded, tool_called_when_needed, no_policy_violation) + набор тест-сценариев (включая edge-cases и prompt-injection). Это онлайн-оценка под свою задачу — в отличие от офлайн-бенчмарков (Benchmarks Agents).

Суть

Агент — «система принятия решений», а не «умный чат», поэтому его надо проверять как систему: воспроизводимые тест-сценарии и измеримые метрики, а не «вроде отвечает нормально».

Зачем это нужно

Без evals нельзя отличить рабочего агента от демо. DoD фиксирует минимум, ниже которого в прод нельзя; метрики ловят галлюцинации и нарушения политик (связка с Guardrails).

Как работает

Definition of Done (6 пунктов):

  1. Бот принимает запрос и отвечает либо через RAG (со ссылками), либо вызовом инструмента (с понятным «что сделал»).
  2. Два режима: «Справка» (RAG) и «Действие» (tool).
  3. Трассировка шагов (хотя бы JSONL): request_id → plan → tool_call → tool_result → final_answer.
  4. Базовая защита: tool allowlist, проверка аргументов, anti-prompt-injection для RAG, таймауты/ретраи.
  5. Минимум несколько тест-сценариев, включая edge-cases и prompt-injection.
  6. Мини-оценка качества (есть ли цитаты / основан ли ответ на контексте / нет выдуманных фактов).

Мини-метрики:

  • has_citations — есть ссылки на источники (для RAG).
  • answer_grounded — эвристика: доля n-грамм ответа из контекста / наличие «не знаю».
  • tool_called_when_needed — инструмент вызван по ожидаемому сценарию.
  • no_policy_violation — инструмент НЕ вызван при инъекции.

Debugging (частые ошибки): LLM выдумывает → ужесточить промпт («только из контекста»); tool не вовремя → усилить правила planner / ввести «clarify first»; RAG возвращает мусор → проверить chunking/top_k, добавить dedup; injection прошёл → allowlist + фильтр контента + правило «docs not instructions».

Масштабирование для RAG-проектов: эти мини-метрики — стартовый уровень для первого агента. Для продакшен-RAG их сменяет полноценный дашборд RAG Metrics (Hit Rate, MRR, NDCG, Faithfulness, Noise Robustness, P95 Latency) с автоматизацией через RAGAS (LLM-as-judge без ground truth, автогенерация eval-датасета) и DeepEval/RAGChecker. Именно RAGAS/LLM-судья — практический ответ на вопрос «как автоматизировать answer_grounded».

Метрики уровня агента (а не только ответа): для агентов, ходящих в инструменты, к мини-метрикам добавляют агентные — Task Completion, Tool Correctness, Argument Correctness (см. DeepEval Agentic Metrics). Сама оценка обычно делается судьёй (LLM as Judge) по двум принципам: бинарность (PASS/FAIL проще согласовать, чем шкалу 1–10) и калибровка судьи на небольшом наборе, размеченном человеком-экспертом. Всё это — часть дисциплины AgentOps.

Метрики типизированного агента (PydanticAI): для агентов со структурным выводом (Structured Output) и циклами валидации (Validation Loops) добавляют свой набор: validation_errors_per_run, retry_count_per_run (аномалия >1), retry_overhead_tokens (токены, потраченные на исправления) и unexpected_behavior_rate (доля случаев, где лимит ретраев исчерпан, а валидный ответ не достигнут). Алерт при fallback_rate >5%.

Trust Gate / Executable Spec Layer: где есть объективная проверка, оценка = автотесты (Pytest), которые агент не может изменить; при провале он получает лог ошибки и переписывает решение. Это усиливает DoD: модели не верят «на слово», потому что при самооценке агенты «уверенно хвалят свою работу, даже посредственную». Поэтому верификатор делают внешним и сильным — подробнее в Generator Evaluator.

Security-эвалюация (no_policy_violation детально): для защитных классификаторов размытая оценка 1-5 заменяется метрикой F1 (баланс Precision и Recall) и бинарным сигналом safe/injection для мгновенной остановки пайплайна — см. Injection Detection. Практический кейс защиты корпоративного AI-ассистента банка: LLM-judge по 4 критериям + A/B-тест 3 версий промпта на 500 запросах → через 4 недели 0 инцидентов утечки (было 3/мес), 100% injection-попыток заблокировано, +23% качества по judge-score, ценой +80 мс latency.

Искажения LLM-судьи и калибровка

Судья на базе модели наследует её склонности, и три из них систематические, то есть повторяются от запуска к запуску:

  • Verbosity bias — предпочтение более длинного ответа, даже если длина набрана водой.
  • Self-enhancement — модель выше оценивает тексты, похожие на её собственные.
  • Positional bias — при сравнении двух вариантов первый получает преимущество просто за позицию.

Правила калибровки, которые выводятся из этого списка:

  • Отказаться от попарных сравнений в пользу монадической оценки: не «какой из двух лучше», а «этот проходит или нет» по каждому критерию. Позиционное искажение при этом исчезает вместе с самой позицией.
  • Требовать рассуждение до вердикта — сначала пошаговый разбор фактов, потом булев ответ. Порядок полей в схеме здесь имеет значение: вердикт, выданный до рассуждения, рассуждением уже не изменится.
  • Температура строго 0.0 — оценка должна быть воспроизводимой.
import instructor
from pydantic import BaseModel, Field

class GroundingVerdict(BaseModel):
    chain_of_thought: str = Field(description="Пошаговый анализ фактов в источнике.")
    is_faithful: bool = Field(description="True, если нет галлюцинаций.")

client = instructor.from_openai(OpenAI())

Вердикт судьи тоже проходит валидацию

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

Это межполевой инвариант, и выражается он там же, где остальные (Validation Loops):

class GroundingVerdict(BaseModel):
    chain_of_thought: str
    is_faithful: bool
    violation_detected: str | None = None

    @model_validator(mode="after")
    def check_violation_report(self):
        if not self.is_faithful and not self.violation_detected:
            raise ValueError("is_faithful=False обязывает заполнить violation_detected")
        return self

Обратная сторона тоже проверяема: заполненное violation_detected при is_faithful=True означает, что судья нашёл расхождение и всё равно признал ответ обоснованным — то есть противоречит сам себе, и такой вердикт надёжнее переспросить, чем засчитать.

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

Выбор метрики: чем платим за согласие с человеком

Метод Скорость Согласие с человеком Ограничения
ROUGE / BLEU < 1 мс Крайне низкое Штрафуют за синонимы
BERTScore 10–30 мс Среднее Слепы к логике и отрицанию
G-Eval (на GPT-4o) 1.5–3 с Высокое Требует калибровки, дорого
Prometheus-2 1–2 с Высокое Нужен свой GPU, дешевле на длинной дистанции

Разрыв в скорости здесь на три порядка, и это определяет, где какую метрику ставить: n-граммные — в юнит-тестах на каждый коммит, судью — на golden set перед релизом.

Пример

Trust Gate / executable spec: вывод агента проверяется не самооценкой, а реальным запуском кода в песочнице. Успех → возвращаем результат; ошибка → traceback уходит обратно модели на самоисправление (self-correction).

def code_execution_loop(question, max_attempts=3):
    repl = PythonREPL()                              # песочница для exec()
    history = [{"role": "system", "content": SYSTEM_PROMPT},
               {"role": "user", "content": question}]
    for _ in range(max_attempts):
        reply = client.chat.completions.create(
            model=MODEL_FAST, messages=history).choices[0].message.content
        history.append({"role": "assistant", "content": reply})
        result = repl.run(extract_code(reply))       # объективная проверка: запускаем код
        if result["success"]:
            return result["output"]
        # ошибка -> traceback обратно модели на самоисправление
        history.append({"role": "user", "content": f"Код упал:\n{result['error']}\nИсправь."})
    return None

Пирамида наборов: чем именно наполнять eval

Один «набор тестов» — это недоделанная конструкция: регрессия, атаки и краевые случаи ловят разное и живут по разным правилам. Рабочее разделение по объёму:

Набор Доля Что в нём Что ловит
Golden Set ~80% реальные обращения, очищенные от персональных данных, с эталонными ответами экспертов деградацию качества после изменений
Adversarial Set ~15% промпт-инъекции, джейлбрейки, стресс для guardrail-слоя пробитие защиты
Edge Cases ~5% пустые запросы, упор в лимит контекста, пограничные форматы падения на нештатном входе

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

Два правила, без которых пирамида не работает. Golden Set версионируется вместе с кодом: набор, который меняется молча, не показывает деградацию, он её маскирует. И одна ось за раз — меняя промпт и модель одновременно, вы получаете дельту метрики, но не знаете, чья она.

Отдельно стоит держать в голове, что проверки бывают двух природ. Детерминированные — формат JSON, наличие товара в базе, соблюдение лимита цены — это код, и он всегда предпочтительнее. Недетерминированные — обоснованность подбора, полезность ответа, отсутствие выдумок — это LLM as Judge, и его место именно здесь, в оффлайн-прогоне на наборе, а не в горячем пути.

Quality Gate — политика из нескольких условий, а не одно число

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

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

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

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

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

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

Что класть в описание кейса

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

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

Синтетические профили: как отлаживать сам стенд

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

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

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

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

Лечится разделением на три выборки с разной видимостью, а не просто на три файла:

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

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

Цена нарушения измерена. В замере автоматической оптимизации обвязки снятие именно этого механизма — проверки на отложенном наборе вместе с разбором последствий — дало самое сильное падение среди всех проверенных компонентов: с 62,0 до 50,6 по доле решённых задач на тестовом наборе. Дороже, чем поверхностная диагностика причины провала, и дороже, чем отсутствие рамки у правок (Harness Optimization).

Дисциплина карантина держится не на честности, а на запрете

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

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

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

Отсюда же — артефакты замысла не являются свидетельствами. Разбор кандидатов, критика, выбранный контракт и сводка проектирования складываются рядом с результатами и легко читаются как результаты — особенно агентом, который собирает контекст поиском. Проверка контракта отвечает ровно на один вопрос: связен ли замысел. Измеренной величины она не утверждает никогда, и наследоваться от неё нельзя (Unknown Not A Value — тот же запрет подставлять плановое вместо измеренного).

Сколько кейсов нужно

Абсолютного минимума нет, и число — не та величина, которую стоит планировать. Планировать надо покрытие классов риска: набор считается достаточным, когда в нём представлен каждый способ, которым система может навредить, а не когда в нём набралось N строк.

Из этого следует состав, а не размер: пропорция Golden / Adversarial / Edge примерно 80 / 15 / 5, и каждый кейс помечен уровнем риска, чтобы условие «ноль критичных провалов» было проверяемым. Практический ориентир нижней границы — от полусотни кейсов, чтобы доля пройденных вообще что-то значила; в кейсе банка A/B-тест трёх версий промпта гонялся на 500 запросах. Но это ориентиры стоимости прогона, а не критерий готовности.

Ориентир на старте другой и с ним не спорит: 20–50 задач, собранных из настоящих отказов, — уже хорошее начало. Величины отвечают на разные вопросы. Пятьдесят — порог, ниже которого доля пройденных перестаёт быть измерением: на двадцати задачах один провал весит пять процентных пунктов. Двадцать — порог, ниже которого набор перестаёт быть набором. Ранний агент меряется не долей, а списком: какие из известных отказов он теперь не повторяет.

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

Из чего собран стенд: задачи, оценщики, транскрипты

Задача обязана быть однозначной. Проверка простая: два человека, знающих предметную область, независимо приходят к одному вердикту «прошло / не прошло». Если приходят к разным — дефект в формулировке задачи, а не в агенте. У задачи должно быть эталонное решение, доказывающее её решаемость: иначе провал ничего не сообщает.

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

Три вида оценщиков, и у каждого своё место:

Оценщик Чем берёт Чем платит Где применять
Кодовый быстрый, дешёвый, воспроизводимый ломается на допустимых вариациях, не видит оттенков сравнение строк, проверка вызовов инструментов, статический анализ, проверка результата действия
Модельный масштабируется, ловит субъективное недетерминирован, требует калибровки оценка по рубрике, утверждения на естественном языке, попарное сравнение
Человеческий эталон качества дорого и медленно калибровка модельного и разбор субъективного, а не массовый прогон

Порядок здесь не декоративный: человеческая разметка тратится не на прогон набора, а на то, чтобы модельному оценщику вообще можно было верить (LLM as Judge).

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

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

Недетерминизм: pass@k и pass^k — разные вещи

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

  • pass@k — успех хотя бы в одной попытке из k. Годится там, где пользователь может переспросить.
  • pass^k — успех во всех k попытках. Нужен там, где важна повторяемость: платежи, изменение данных, любой необратимый шаг.

Подмена одного другим — тихая ошибка отчётности: система с pass@5 = 100% может иметь pass^5 = 20%, и в отчёте это выглядит одинаково хорошо.

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

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

Чем оценка GenAI отличается от классического QA

Пять измерений, в которых привычный подход перестаёт работать:

  • Недетерминизм. Бинарная проверка равенства строк неприменима. Практический ответ — фиксация temperature=0, многократные прогоны (N ≥ 3) и переход от проверки текста к оценке семантических инвариантов: что в ответе обязано сохраниться, а что вправе меняться от прогона к прогону.
  • Текстовый вывод против строгих контрактов. Контрактные тесты по схеме и механизмы автопочинки парсинга (Structured Output).
  • Зависимость от конвейера поиска. Ретривер оценивается изолированно от генератора, иначе непонятно, чинить поиск или промпт (RAG Metrics).
  • Специфичные риски безопасности. Инъекция промпта, утечка персональных данных, воспроизведение фрагментов обучающей выборки — покрываются автоматизированным состязательным тестированием (Continuous Red Teaming).
  • Эксплуатационные метрики как часть качества. Время до первого токена, расход токенов и стоимость запроса влияют на пригодность модели наравне с точностью.

Канареечный набор как отдельный шлюз безопасности

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

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

Альтернативный взгляд: оценка не заканчивается на Quality Gate

В одной подаче evals — статический барьер в CI/CD: golden dataset, судья, порог, блокировка мёржа. Релизная оптика смотрит на ту же задачу как на непрерывный процесс на живом трафике, где офлайн-набор лишь первый из четырёх этапов.

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

Продолжение конвейера после гейта: зеркалирование живого трафика (Shadow Mode) → канареечная выкатка со статистической проверкой (Canary Release LLM) → постоянный мониторинг в проде.

Оценка при этом ведётся по четырём сигналам одновременно, а не по одной метрике качества: eval pass rate, латентность (p99 и время до первого токена), стоимость запроса и safety-срабатывания. Стоимость в этом списке — не бухгалтерия: новый промпт дорожает молча, без единой ошибки в логах, и только сравнение версий это ловит.

Связано с

  • Benchmarks Agents — офлайн-бенчмарки vs свои онлайн-evals
  • Guardrails — no_policy_violation проверяет именно guardrails
  • RAG — has_citations / answer_grounded оценивают RAG-ответы
  • RAG Metrics — продакшен-дашборд метрик RAG (следующий уровень после мини-метрик)
  • Harness Optimization — что происходит, когда по эталонному набору начинают настраивать, и во сколько это обошлось в замере
  • Verifiable Eval — проверяемая часть набора: как устроена проверка, когда ответ пришёл прозой
  • Unknown Not A Value — почему артефакт замысла не повышается до свидетельства, а недостающее измерение не заполняется планом