Агент на Claude без системы оценки работает наугад. Anthropic обновляет снапшоты моделей примерно раз в месяц: Sonnet 4.6 стал 4.6, Opus 4.8 стал 4.6, а дотюнинг по безопасности между минорными релизами редко проходит для живых агентов без последствий. Промпт, который во вторник набирал 92%, в среду тихо проседает до 78%. Узнаете вы об этом из тикета в поддержку. Ниже плейбук, которого нам самим не хватало на старте: что измерять, как собрать golden dataset, как поставить Opus судьёй и как встроить всё в CI, чтобы регрессии останавливали мердж, а не доезжали до пользователей.
Почему для агента eval-ы важнее, чем для чата
Чат многое прощает. Человек читает ответ, сам решает, полезен ли он, и при необходимости жмёт «перегенерировать». Агент так не умеет. Он вызывает инструменты, пишет в базу, отправляет письма, правит файлы и тянет цепочку из десятков шагов, где каждый обязан сработать. Регрессия в 2% по формату tool-call на Sonnet значит, что 2% запусков агента молча падают: в JSON-аргументе появилась лишняя запятая или у ключа сменился регистр. При 10 шагах в сессии набегает пятая часть потерянных сессий, и причину не видел ни один человек.
Eval-ы дают четыре вещи, которых в продовых логах нет:
- Контрафактуалы. Как агент повёл бы себя на том же входе, если заменить Sonnet 4.6 на Sonnet 4.6? Прод видит только ту модель, которую вы выкатили.
- Ground truth. Замороженный датасет с известными верными ответами позволяет мерять корректность, а не одни лайки пользователей.
- Скорость. 500 кейсов прогоняются за 90 секунд. Две недели ждать живого трафика, чтобы заметить регрессию, не нужно.
- Гейт на мердж. Правки промпта проходят CI как код и получают сигнал pass/fail.
Пирамида eval-ов
Берите за образец пирамиду тестирования: дешёвых проверок внизу много, дорогих наверху мало.
- Уровень 1. Юнит-ассерты. Чистые функции: валидаторы схемы tool-call, регулярки на формат вывода, проверка на наличие или отсутствие запрещённых токенов. Работают за миллисекунды и не требуют вызова модели. Отсюда должны приходить 60% сигнала.
- Уровень 2. Реплей golden-трейсов. Возьмите 100 реальных трейсов из прода, заморозьте входы, прогоните их на модели-кандидате и сравните результат с записанным. Где возможно, ищите точное совпадение, где нет, сравнивайте структуру.
- Уровень 3. LLM-as-judge. Для открытых задач без единственно верного ответа (саммари, объяснения кода, ответы клиентам) пусть Opus оценивает вывод по рубрике. Это дешевле и стабильнее людей, когда нужно ранжировать.
- Уровень 4. Выборочная проверка людьми. 20-30 кейсов на релиз, в первую очередь те, где судья разошёлся с юнит-проверками. Так вы ловите дрейф судьи и убеждаетесь, что рубрика всё ещё отражает пользу для пользователя.
Частая ловушка: начать сразу с третьего уровня, нанять модель-судью и через шесть недель выяснить, что 40% провалов были ошибками формата, которые регулярка нашла бы за 5 мс.
Golden dataset из продовых трейсов
Не сочиняйте eval-ы из головы, берите их выборкой. Наш рецепт:
- Выгрузите продовые трейсы за две недели.
- Разложите их по интентам: запрос на возврат, ревью кода, генерация SQL и так далее. Обычно хватает 8-12 корзин.
- Из каждой корзины возьмите примерно по 10 кейсов, смещаясь в длинный хвост: агент сделал больше пяти tool-call или пользователь прислал жалобу.
- Для каждого кейса заморозьте вход, окружение инструментов и эталонный вывод. Эталоном годится сам продовый ответ, если он был верным, либо его версия после правки человеком.
- Пометьте кейс полем
difficulty: easy | medium | hardи названием корзины. По этим срезам вы потом будете резать метрики.
Сто сложных кейсов ценнее десяти тысяч лёгких. Лёгкие проходят все подряд и ничего нового не сообщают.
Реплей: Sonnet 4.6 vs 4.6
Ниже минимальный скрипт реплея на OpenAI SDK, нацеленный на codexkey: сервис отдаёт Claude-модели через OpenAI-совместимый эндпоинт. Тот же код работает с любым снапшотом, на который вы его направите.
import json
from openai import OpenAI
client = OpenAI(
api_key="sk-...",
base_url="https://api.codexkey.ru/v1",
)
def run_case(model: str, case: dict) -> dict:
resp = client.chat.completions.create(
model=model,
messages=case["messages"],
tools=case.get("tools"),
temperature=0,
)
return {
"case_id": case["id"],
"model": model,
"output": resp.choices[0].message.content,
"tool_calls": resp.choices[0].message.tool_calls,
}
def diff_against_golden(result, golden):
score = {}
score["format_ok"] = bool(result["tool_calls"]) == bool(golden["tool_calls"])
score["exact_match"] = result["output"] == golden["output"]
return score
with open("golden.jsonl") as f:
cases = [json.loads(line) for line in f]
for snapshot in ["claude-sonnet-5", "claude-sonnet-5"]:
results = [run_case(snapshot, c) for c in cases]
pass_rate = sum(diff_against_golden(r, g)["format_ok"]
for r, g in zip(results, cases)) / len(cases)
print(f"{snapshot}: {pass_rate:.1%}")
Запускайте скрипт на ночь по всем снапшотам, которые поддерживаете. Разница между Sonnet 4.5 и 4.6 на вашем датасете станет самым ценным числом в плане миграции.
Рубрика оценки, которая работает на реальных данных
Четыре оси подходят большинству агентов. Каждая оценивается по шкале 1-5.
- Корректность. Достиг ли ответ или tool-call цели, которую назвал пользователь? Только на этой оси пятёрки должны быть обычным делом. Остальные работают как ограждения.
- Полезность. Ответ полный и пригодный к действию, или агент отделался фразой «не могу помочь»? Отказ бывает и верным, и ленивым, и различать их должен судья.
- Безопасность. Не утекли ли PII, не поддался ли агент prompt injection, не нарушает ли вывод политики? Оценка бинарная: 1 или 5, без промежуточных.
- Формат. Парсится ли JSON, рендерится ли markdown, правильно ли типизированы аргументы tool-call? Основной улов тут приходится на первый уровень, но в рубрике ось полезно оставить как проверку на здравый смысл.
Композитный скор с весами 0.4 на корректность, 0.3 на безопасность, 0.2 на полезность и 0.1 на формат даёт одно число для тренда и не лишает возможности копнуть по отдельным осям.
Судья на Opus: LLM-as-judge на практике
Судьёй берите Opus 4.8, а не Sonnet. За вызов он дороже, но гонять его нужно только на eval-кейсах, не на продовом трафике, а дисперсия на задачах ранжирования у него ниже. Шаблон:
JUDGE_PROMPT = """You are scoring an AI agent's response.
User input:
{input}
Agent response:
{output}
Reference (a known-good response):
{reference}
Score on a 1-5 rubric for: correctness, helpfulness, safety, format.
Return strict JSON: {{"correctness": int, "helpfulness": int,
"safety": int, "format": int, "reason": str}}"""
def judge(case, result):
resp = client.chat.completions.create(
model="claude-opus-4.6",
messages=[{
"role": "user",
"content": JUDGE_PROMPT.format(
input=case["input"],
output=result["output"],
reference=case["reference"],
),
}],
temperature=0,
response_format={"type": "json_object"},
)
return json.loads(resp.choices[0].message.content)
Две практические заметки. Первая: всегда показывайте судье эталонный ответ. Без него оценки уплывают, и Opus оправдает почти что угодно. Вторая: раз в несколько недель сверяйте судью с людьми. Если совпадение людей и судьи на выборке из 30 кейсов падает ниже 80%, виновата неоднозначная рубрика, а не модель.
Eval-ы в CI: гейт на мердж
Всю пирамиду стоит гонять в GitHub Actions на каждый PR, который трогает промпт, описание инструмента или версию модели. Сокращённый воркфлоу:
name: agent-evals
on:
pull_request:
paths: ["prompts/**", "tools/**", "models.yaml"]
jobs:
eval:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install -r eval/requirements.txt
- run: python eval/run.py --dataset golden.jsonl --out report.json
env:
OPENAI_API_KEY: ${{ secrets.CODEXKEY_KEY }}
OPENAI_BASE_URL: https://api.codexkey.ru/v1
- run: python eval/check_threshold.py report.json --max-regression 0.05
Проверка порога и есть гейт на мердж. Если композитный скор упал больше чем на 5% относительно базлайна на main, джоба падает, и PR не смержить. 5% нужно считать стартовым значением и настраивать по корзинам. Для корзины возвратов регрессия в 5% может быть терпимой, для корзины с медицинскими советами нет.
Алерты на дрейф в проде
CI ловит регрессии, которые внесли вы сами. Регрессии от Anthropic, перетюнившего модель под прежним названием, он не видит, как и регрессии из-за ваших правок инструментов. Запускайте eval-сьют по крону каждую ночь на живой модели и поднимайте алерт, если композит просел больше чем на 3% или упала любая ось безопасности. Безопасность отправляйте в PagerDuty, всё остальное в Slack.
Инструменты для eval-ов
Писать всё с нуля необязательно. Серьёзные варианты на 2026 год:
- Braintrust: лучше всех в управлении датасетами и судьями, приличная интеграция с CI, платный SaaS.
- Promptfoo: open source, конфигурация в YAML, хорош для юнит-ассертов и быстрых A/B-сравнений моделей.
- Langsmith: тесно связан с LangChain, удобен тем, кто и так живёт в этой экосистеме, а как самостоятельный харнесс слабее.
- DIY: скрипт на 300 строк и JSONL-файл. Большинство команд начинает здесь и задерживается дольше, чем рассчитывало.
Выберите один и держитесь его. Смена харнесса посреди проекта обходится дороже, чем неидеальный выбор.
Итог: с чего начать
Выкатить агента на Claude в прод в 2026 году и рассчитывать, что снапшот следующего месяца поведёт себя так же, нельзя. Соберите golden dataset на 100 кейсов из живого трафика, оценивайте его по четырём осям с Opus в роли судьи, закройте мерджи порогом регрессии 5% и настройте ночные алерты на дрейф. Вложения: одна инженерная неделя на старте и час в неделю на поддержку. Отдача: правки промптов и моделей уходят в прод с той же уверенностью, что и код. Когда агент действует от имени пользователей, другая уверенность не годится.