Суть
Агент — «система принятия решений», а не «умный чат», поэтому его надо проверять как систему: воспроизводимые тест-сценарии и измеримые метрики, а не «вроде отвечает нормально».
Зачем это нужно
Без evals нельзя отличить рабочего агента от демо. DoD фиксирует минимум, ниже которого в прод нельзя; метрики ловят галлюцинации и нарушения политик (связка с Guardrails).
Как работает
Definition of Done (6 пунктов):
- Бот принимает запрос и отвечает либо через RAG (со ссылками), либо вызовом инструмента (с понятным «что сделал»).
- Два режима: «Справка» (RAG) и «Действие» (tool).
- Трассировка шагов (хотя бы JSONL):
request_id → plan → tool_call → tool_result → final_answer. - Базовая защита: tool allowlist, проверка аргументов, anti-prompt-injection для RAG, таймауты/ретраи.
- Минимум несколько тест-сценариев, включая edge-cases и prompt-injection.
- Мини-оценка качества (есть ли цитаты / основан ли ответ на контексте / нет выдуманных фактов).
Мини-метрики:
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 — почему артефакт замысла не повышается до свидетельства, а недостающее измерение не заполняется планом