DanLevy.net

Боритесь со злом с помощью эвалов!

Бенчмарки измеряют бенчмарки. Вашей системе нужны собственные метрики.

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

MMLU: 92,4%. HumanEval: 87,2%. LLeMU: 88,7%. MATH: 73,6%. AGI: 127%!

Но для 99% компаний, строящих процессы и продукты на ИИ, всё это не имеет значения.

Что действительно важно? Как справляются ВАШИ задачи? Становится лучше или хуже? Единственный разумный способ узнать — написать эвалы (тесты для LLM), которые отражают конкретные задачи, данные и сценарии отказов именно вашей системы.

Бенчмарки не врут. Они отвечают на чужой вопрос.


Во что на самом деле обходится «оценка по ощущениям»

Стандартный подход: выкатили изменение модели, посмотрели на каналы жалоб, откатили, если комната зашумела.

При этом упускается почти всё интересное:

Вы ловите только громкие отказы. Пользователи, получившие уверенно неверный ответ и не осознавшие этого? Молчат. Пользователи, получившие худший ответ и бросившие фичу? Молчат. Тикеты в поддержку и метрики ошибок фиксируют лишь малую часть регрессии качества.

Вы не можете отличить регрессии от улучшений. Если новая модель лучше справляется с задачей A и хуже с задачей B, жалобы на B выглядят идентично общим жалобам «ИИ стал хуже». Вы не знаете, что чинить.

Вы используете своих пользователей как тестовую инфраструктуру. Они на это не подписывались.


Спектр эвалов (и где большинство команд ошибается)

Подходы к оценке располагаются на спектре от «быстро, но зыбко» до «дорого, но валидно».

Спектральная диаграмма, сравнивающая детерминированные проверки, LLM-судью и человеческую оценку по скорости, стоимости и валидности.

Используйте самый дешёвый метод оценки, который способен честно ловить ошибки.

LLM-as-judge — нынешний фаворит: попросите мощную модель оценить выводы другой модели. Быстро, масштабируемо, дёшево. Проблема: это закрепляет предвзятость модели-судьи, поддаётся манипуляциям и создаёт циклическую зависимость. Если вы используете GPT-5 для оценки выводов GPT-5, вы измеряете что-то вроде «насколько GPT-5 согласен с GPT-5». Это не ничто, но это не то, что вы думаете.

Человеческая оценка — золотой стандарт, который все пытаются пропустить. Заставить людей оценивать выводы — дорого, медленно, непостоянно между оценщиками и сложно планировать. Но это единственное, что проверяет, полезна ли ваша система реальным людям.

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


Что действительно работает

1. Определите отказ до того, как выкатите

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

Не «вывод должен быть точным». Это не тест. Скорее:

Это можно проверить программно. Без модели-судьи.

Инфраструктура для эвалов: детерминированные проверки

type EvalResult = { passed: boolean; reason?: string };
const evals: Record<string, (output: string, context: EvalContext) => EvalResult> = {
// JSON должен парситься
validJson: (output) => {
try {
JSON.parse(output);
return { passed: true };
} catch (e) {
return { passed: false, reason: `Невалидный JSON: ${e.message}` };
}
},
// Без галлюцинирующих ссылок — каждое утверждение должно быть в контексте
groundedCitations: (output, { retrievedChunks }) => {
const claims = extractCitations(output);
const ungrounded = claims.filter(
(claim) => !retrievedChunks.some((chunk) => chunk.includes(claim))
);
return ungrounded.length === 0
? { passed: true }
: { passed: false, reason: `Неподтверждённые утверждения: ${ungrounded.join(', ')}` };
},
// Проверка длины ответа — ловим обрезку или runaway-генерацию
reasonableLength: (output) => {
const words = output.split(/\s+/).length;
return words >= 10 && words <= 2000
? { passed: true }
: { passed: false, reason: `Количество слов ${words} выходит за границы` };
},
};

2. Соберите золотой набор из ваших худших дней

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

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

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

Диаграмма рабочего процесса, показывающая, как плохие продакшен-инциденты становятся золотыми кейсами, затем запускаются CI-эвалы, затем блокируются регрессии или одобряются релизы.

Золотой набор превращает провальные случаи в регрессионный набор.

Форма золотого кейса

interface GoldenCase {
id: string;
input: string;
context: Record<string, unknown>;
expectedBehavior: {
mustContain?: string[];
mustNotContain?: string[];
structureCheck?: (output: string) => boolean;
minSimilarityToReference?: number; // косинусное сходство с эталонным ответом
sourceIncident?: string; // ссылка на баг-репорт или тикет
};
}

3. Регрессионное тестирование, а не только приёмочное

Большинство команд запускают эвалы только при рассмотрении смены модели. Это приёмочное тестирование: «достаточно ли хороша новая штука?»

Вам также нужно регрессионное тестирование: «не сломало ли это то, что работало раньше?»

Запускайте золотой набор при каждом изменении промпта, а не только модели. Промпт, который работал нормально, может незаметно ухудшиться, когда вы добавите новый инструмент, измените стратегию извлечения RAG или обновите шаблон контекста. Вы не узнаете без базовой линии. Инструменты вроде Langfuse прикрепляют оценки эвалов к продакшен-трейсам, чтобы регрессия появлялась в дашбордах, а не только в отчётах об инцидентах.

Инфраструктура для эвалов: сравнение базовой линии и кандидата
async function compareModelVersions(
goldenCases: GoldenCase[],
baselinePipeline: Pipeline,
candidatePipeline: Pipeline
) {
const results = await Promise.all(
goldenCases.map(async (tc) => {
const [baseline, candidate] = await Promise.all([
baselinePipeline.run(tc.input, tc.context),
candidatePipeline.run(tc.input, tc.context),
]);
return {
id: tc.id,
baselinePassed: runEvals(baseline, tc.expectedBehavior),
candidatePassed: runEvals(candidate, tc.expectedBehavior),
regression: /* базовая линия прошла */ && /* кандидат не прошёл */,
improvement: /* базовая линия не прошла */ && /* кандидат прошёл */,
};
})
);
const regressions = results.filter((r) => r.regression);
const improvements = results.filter((r) => r.improvement);
console.log(`Регрессии: ${regressions.length} / ${goldenCases.length}`);
console.log(`Улучшения: ${improvements.length} / ${goldenCases.length}`);
if (regressions.length > 0) {
console.error('Найдены блокирующие регрессии:');
regressions.forEach((r) => console.error(` - ${r.id}`));
}
return { regressions, improvements };
}

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

4. Используйте LLM-as-judge ровно для одной задачи

LLM-as-judge полезен для открытых выводов, где нет детерминированно правильного ответа: «полезен ли этот ответ?», «сохранило ли это резюме ключевые моменты?», «подходит ли это объяснение для новичка?»

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

Судья на основе рубрик

async function judgeHelpfulness(
userQuery: string,
modelResponse: string
): Promise<{ score: number; reasoning: string }> {
const judgePrompt = `
Вы оцениваете ответ поддержки клиенту.
Вопрос пользователя: ${userQuery}
Ответ: ${modelResponse}
Оцените ответ по шкале от 1 до 5:
5 = Прямо отвечает на вопрос точной и применимой информацией
4 = Отвечает на вопрос, но могло бы быть конкретнее или применимее
3 = Частично затрагивает вопрос; ключевая информация отсутствует
2 = Имеет отношение к теме, но не отвечает на вопрос
1 = Не по теме, фактически неверно или вредно
Ответьте JSON: {"score": <число>, "reasoning": "<одно предложение>"}
`;
const result = await judgeModel.generate(judgePrompt);
return JSON.parse(result);
}

Явная рубрика снижает дисперсию оценщика, даёт интерпретируемый вывод и упрощает аудит, когда судья ошибается. Библиотеки вроде Autoevals и Braintrust поставляют готовые рубрики для типичных задач — стоит позаимствовать, прежде чем писать свои с нуля.


Инструменты, которые стоит знать

Вам не обязательно строить всё это с нуля. Несколько инструментов серьёзно продвинулись в решении проблемы инфраструктуры эвалов:

Braintrust — полноценная платформа эвалов с отслеживанием экспериментов, управлением наборами данных и функциями оценки. Организует прогоны эвалов по промптам, моделям и деплоям, чтобы можно было сравнивать качество во времени, а не только между релизами. Хорошо сочетается с их open-source-библиотекой Autoevals, которая поставляет готовые скоринговые функции для типичных задач (фактическая точность, полезность, токсичность, семантическое сходство).

Langfuse — open-source-наблюдаемость для LLM, которая сидит между вашим приложением и моделями. Трейсит каждый вызов, прикрепляет оценки эвалов (человеческие или автоматические) к отдельным спанам и показывает тренды качества по продакшен-трафику. Хороший выбор, если вы хотите наблюдаемость и эвалы в одном инструменте, а не в отдельном харнесе.

Evalite — TypeScript-нативный фреймворк эвалов от Мэтта Покока. Минимум церемоний: определите задачу, определите скорер, запустите в существующем тестовом окружении. Нацелен на команды, которые хотят эвалы, похожие на юнит-тесты, а не на отдельную ML-платформу экспериментов.

promptfoo — CLI-ориентированный раннер эвалов, сфокусированный на сравнении промптов и ред-тиминге. Легко конфигурируется через YAML, интегрируется с большинством провайдеров моделей и имеет встроенную поддержку обнаружения промпт-инъекций и других враждебных входных данных.

deepeval — Python-фреймворк эвалов с большой библиотекой встроенных метрик (G-Eval, RAG faithfulness, релевантность ответов, обнаружение галлюцинаций). Полезен для RAG-пайплайнов, где нужна специфичная оценка качества извлечения, а не только генерации.

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


Неудобная часть

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

Это действительно сложно для новой AI-фичи. Но это не опционально, если вы заботитесь о надёжности. Команды, которые поставляют работающий ИИ, делают то же самое, что и для любого критичного пути кода: определяют ожидаемое поведение, тестируют его и запускают эти тесты непрерывно.

Бенчмарки не врут. Они отвечают на чужой вопрос. Перестаньте читать их как дорожную карту продукта и начните писать тесты, соответствующие вашей системе.

Ваши пользователи заметят раньше, чем ваши дашборды. Сначала постройте тестовый набор.