Evals 2.0 — это следующее поколение практик оценки LLM-приложений, сформировавшееся в 2025-2026 годах. Если первое поколение eval научило нас оценивать отдельные ответы модели (LLM-as-judge, автометрики, Promptfoo/Braintrust), то Evals 2.0 решает задачи, которые стали критичными с ростом сложности AI-систем: оценка агентных workflow, многоходовых диалогов, автоматическая генерация eval-датасетов и eval-driven development как новая парадигма разработки.
Если вы ещё не знакомы с базовыми концепциями eval (LLM-as-judge, автометрики, eval-наборы), начните со статьи Eval: оценка качества LLM-приложений. Evals 2.0 строится поверх этих основ.
Evals 1.0 (2023-2024) решали задачу «правильно ли модель ответила на вопрос». Evals 2.0 решают задачу «правильно ли AI-система выполнила сложную задачу».
Без техники
Evals 1.0: статичный набор из 50 вопросов, один промпт → один ответ, ручная разметка эталонов, LLM-as-judge по шкале 1-5. Eval запускается вручную перед деплоем.
С техникой
Evals 2.0: динамические eval-наборы, сгенерированные LLM из production-логов. Оценка агентных цепочек (10+ шагов, tool calls, recovery). Eval встроен в CI/CD и блокирует PR при регрессии. Автоматический мониторинг cost-quality tradeoff.
К 2025 году LLM-приложения перестали быть простыми «вопрос-ответ» системами. Три тренда сделали Evals 1.0 недостаточными:
1. Агентные системы. Приложения стали выполнять многошаговые задачи: агент ищет информацию, вызывает API, принимает решения, корректирует план. Оценивать только финальный ответ — как оценивать шахматиста только по результату партии, не глядя на ходы.
2. Масштаб production. Когда приложение обрабатывает тысячи запросов в день, статичный eval-набор из 50 примеров не покрывает реальное распределение запросов. Нужна автоматическая генерация eval-кейсов из production-данных.
3. Cost-quality tradeoff. С появлением десятков моделей разного размера и стоимости, eval должен помогать выбирать оптимальную модель для каждой задачи: когда хватит мини-модели, а когда нужен Opus.
Агентные системы — самая сложная задача для eval. Агент выполняет последовательность действий: планирование, вызов инструментов, обработка ошибок, адаптация стратегии. Нужно оценивать не только финальный результат, но и качество пути.
Отношение минимально необходимых шагов к фактическим
Optimal steps / Actual steps
Tool accuracy
Правильность выбора и параметров инструментов
Correct tool calls / Total tool calls
Recovery rate
Доля успешных восстановлений после ошибок
Recovered / Total errors
Cost per task
Стоимость выполнения задачи
Total tokens * price
Time to completion
Время от начала до завершения
End time - Start time
Не оценивайте агента только по финальному результату. Агент, который «гуляет» по 20 tool calls и случайно находит ответ — ненадёжен. Завтра он на похожем вопросе уйдёт в бесконечный цикл. Trajectory eval ловит такие проблемы.
Eval-Driven Development — это подход, при котором eval-наборы пишутся до промптов, как TDD (Test-Driven Development) в классическом программировании. EDD стал стандартной практикой в командах, работающих с LLM в production.
Без техники
Обычная разработка: написал промпт → попробовал 5 вопросов вручную → «вроде работает» → задеплоил → через неделю нашли баг → починил → сломал другое.
С техникой
EDD: описал требования → написал 30 eval-кейсов → написал промпт → прогнал eval → увидел 72% → улучшил промпт → 89% → добавил edge cases → 85% → итерировал до 92% → задеплоил с уверенностью.
EDD особенно эффективен при работе в команде. Eval-набор становится «контрактом»: PM описывает требования, разработчик переводит их в eval-кейсы, и все видят объективную метрику качества вместо субъективных «вроде работает».
Оценка многоходовых диалогов — одна из самых сложных задач Evals 2.0. Пользователь задаёт вопрос, уточняет, меняет тему, возвращается — и каждый ответ модели зависит от всей предыдущей истории.
Вместо отдельных вопросов вы описываете сценарии — полные диалоги из 5-10 реплик с ожидаемым поведением на каждом шаге:
ПромптMulti-turn eval scenario
Scenario: «Клиент хочет вернуть товар, потом передумал»
Turn 1 (user): Хочу вернуть ноутбук
Expected: спросить причину и номер заказа
Turn 2 (user): Заказ #12345, не включается
Expected: предложить диагностику ИЛИ оформить возврат
Turn 3 (user): А, подождите, он включился! Не нужен возврат
Expected: подтвердить отмену возврата, предложить помощь
Turn 4 (user): Но экран мерцает
Expected: предложить гарантийный ремонт (не возврат — клиент уже отказался)
Ответ модели
Eval judges each turn:
Turn 1: 5/5 — спросил причину и номер
Turn 2: 4/5 — предложил возврат, но не спросил про диагностику
Turn 3: 5/5 — корректно подтвердил отмену
Turn 4: 5/5 — предложил ремонт, учёл контекст
Ручное создание eval-наборов — бутылочное горлышко. В Evals 2.0 LLM используются для генерации eval-датасетов: из production-логов, из документации, из описания требований.
1. Из production-логов. Самый ценный источник. LLM анализирует реальные запросы пользователей и генерирует eval-кейсы с эталонными ответами.
2. Из документации. LLM читает вашу документацию (FAQ, инструкции, политики) и генерирует вопросы, которые пользователи могут задать, с правильными ответами из документа.
3. Из описания требований. Вы описываете, что должно делать приложение — LLM генерирует edge cases, adversarial inputs, пограничные ситуации.
Автогенерация ускоряет создание eval-наборов в 10-20 раз, но не заменяет ручную проверку. Всегда ревьюьте сгенерированные кейсы: LLM может придумать нереалистичные сценарии или некорректные эталонные ответы. Правило: генерируйте 100, ревьюьте и оставляйте 50.
Ключевая идея Evals 2.0: в PR отображается не просто «eval passed/failed», а diff — на каких кейсах стало лучше, на каких хуже. Разработчик видит трейдоффы и принимает осознанное решение.
С появлением десятков моделей (от GPT-4.1 nano до Claude Opus) eval должен помогать выбирать оптимальную модель. Cost-quality eval сравнивает модели на вашем eval-наборе по трём осям: качество, стоимость, латентность.
Quality vs Cost: модели на типичном eval-наборе (условные единицы)
GPT-4.1 nano — качество68%
GPT-4.1 nano — стоимость5%
Claude Sonnet — качество88%
Claude Sonnet — стоимость30%
GPT-4.1 — качество90%
GPT-4.1 — стоимость50%
Claude Opus — качество95%
Claude Opus — стоимость100%
Вывод часто неочевиден: на 80% запросов мини-модель справляется не хуже, и можно сэкономить 90% бюджета, маршрутизируя только сложные запросы на большую модель. Eval позволяет найти эту границу.
Универсальные метрики (correctness, helpfulness) недостаточны для специализированных приложений. В 2025-2026 появились domain-specific eval-фреймворки:
Если вы строите приложение для конкретного домена — инвестируйте в domain-specific eval раньше, чем в UI. Даже идеальный интерфейс не спасёт, если модель даёт неточные медицинские или юридические советы.
LLM-as-judge не заменяет человека, но масштабируется. Зрелые команды комбинируют оба подхода: LLM оценивает 100% запросов, люди — выборку для калибровки.
EVAL_GEN_PROMPT = """You are generating eval test cases for an AI application.
Application description: {app_description}
Generate {count} diverse test cases. For each test case provide:
1. input: the user's message
2. expected: what a correct response should contain
3. category: type of test (typical, edge_case, adversarial, out_of_scope)
4. difficulty: easy, medium, hard
Rules:
- 40% typical cases, 30% edge cases, 20% adversarial, 10% out of scope
- Include multilingual inputs if the app supports it
- Include ambiguous inputs that require clarification
- Include inputs that try to manipulate the system
Return JSON array:
[{{"input": "...", "expected": "...",
"category": "...", "difficulty": "..."}}]"""
def generate_eval_dataset(
app_description: str,
count: int = 50,
model: str = "claude-opus-4-20250514",
) -> list[dict]:
"""Generate eval dataset from app description."""
response = client.messages.create(
model=model,
max_tokens=8192,
messages=[{
"role": "user",
"content": EVAL_GEN_PROMPT.format(
app_description=app_description,
count=count,
),
}],
)
text = response.content[0].text
start = text.index("[")
end = text.rindex("]") + 1
dataset = json.loads(text[start:end])
return dataset
def generate_from_logs(
production_logs: list[dict],
model: str = "claude-opus-4-20250514",
) -> list[dict]:
"""Generate eval cases from production logs."""
prompt = f"""Analyze these production logs and generate eval test cases.
Focus on:
- Queries that received low user ratings
- Queries where the model refused to answer
- Queries with unusual patterns
- Common query types that should always work
Logs (sample):
{json.dumps(production_logs[:20], ensure_ascii=False, indent=2)}
Generate 20 eval test cases based on patterns you see.
Return JSON array with input, expected, category, difficulty."""
response = client.messages.create(
model=model,
max_tokens=8192,
messages=[{"role": "user", "content": prompt}],
)
text = response.content[0].text
start = text.index("[")
end = text.rindex("]") + 1
return json.loads(text[start:end])
# promptfooconfig.yaml — agent eval
description: "Agent eval: customer support with tools"
providers:
- id: anthropic:messages:claude-sonnet-4-20250514
config:
max_tokens: 2048
tools:
- name: search_orders
description: "Search customer orders by email or order ID"
parameters:
type: object
properties:
query:
type: string
required: [query]
- name: create_refund
description: "Create a refund for an order"
parameters:
type: object
properties:
order_id:
type: string
amount:
type: number
reason:
type: string
required: [order_id, reason]
prompts:
- id: agent-v2
raw: |
You are a customer support agent. Use tools to look up orders
and process refunds. Always verify the order exists before
processing a refund. If the customer is upset, acknowledge
their frustration before proceeding.
tests:
# Outcome eval
- vars:
question: "I want a refund for order #12345"
assert:
- type: is-valid-openai-tools-call
- type: llm-rubric
value: "Agent searched for the order before processing refund"
- type: llm-rubric
value: "Agent asked for refund reason"
- type: cost
threshold: 0.05
# Recovery eval
- vars:
question: "Refund my order #99999"
provider:
config:
toolOverrides:
search_orders:
output: '{"error": "Order not found"}'
assert:
- type: llm-rubric
value: "Agent gracefully handled the missing order"
- type: not-contains
value: "error"
# Trajectory eval
- vars:
question: "I bought a laptop last week and it's broken"
assert:
- type: javascript
value: |
// Check that agent used tools efficiently
const toolCalls = output.tool_calls || [];
return toolCalls.length <= 3;
- type: llm-rubric
value: "Agent gathered necessary info before suggesting solution"
Храните eval-baseline в репозитории (eval/baseline.json). При каждом merge в main автоматически обновляйте baseline. Так каждый PR сравнивается с актуальной production-версией, а не с устаревшим снимком.