“Looks right” is not a strategy
Why vibe checks do not scale: LLM non-determinism, an eval taxonomy, error analysis over real traces, and a first 20-case eval in one evening.
Lesson content is currently available in Russian.
What you will learn
- →Объяснять, почему ручная проверка нескольких примеров не является тестированием LLM-системы.
- →Различать уровни эвалов: код-ассерты, model-graded и человеческую разметку.
- →Проводить error analysis по трейсам и выводить метрики из реальных категорий ошибок.
«Выглядит правильно» и demo-driven development
Классический софт детерминирован: функция при одном входе всегда даёт один выход, поэтому один зелёный тест что-то доказывает. LLM-системы устроены иначе. Один и тот же промпт при temperature 0.7 даёт разные ответы, а даже при temperature 0 провайдеры не гарантируют побитовую воспроизводимость: батчинг запросов, обновления инфраструктуры, особенности MoE-роутинга. Значит, «я проверил три примера, всё работает» — это не тестирование, а анекдот из трёх наблюдений.
Типичный анти-паттерн называют demo-driven development: команда крутит промпт, пока три любимых примера не заработают, и деплоит. Через неделю пользователи находят десяток сломанных сценариев, команда правит промпт под них — и незаметно ломает первые три. Без фиксированного набора кейсов этот цикл невидим: каждое отдельное исправление выглядит успехом, а суммарное качество ходит по кругу или падает.
Эвал превращает «выглядит правильно» в измеримое утверждение: на наборе из N репрезентативных входов система даёт приемлемый выход в X% случаев по заранее определённому критерию. Здесь три компонента: датасет (входы и ожидания), скоринг (как считаем «приемлемо») и процесс (когда и на чём прогоняем). Главная ошибка команд — начинать с фреймворка и универсальных метрик вместо собственных данных: готовый hallucination score из коробки не знает, что именно считается галлюцинацией в вашем продукте, и даёт числа, которым нельзя верить.
Таксономия эвалов: от assert до человека
Проверки выстраиваются в пирамиду по стоимости и достоверности:
- Код-ассерты — детерминированные проверки: exact match,
contains, regex, валидность JSON по схеме, лимиты латентности и стоимости. Дешёвые и мгновенные, но покрывают только формализуемые свойства: формат, обязательные поля, запрещённые фразы. - Model-graded (LLM-as-judge) — другая модель оценивает ответ по рубрике. Покрывает субъективные свойства: тон, полноту, отсутствие выдумок. Дороже, медленнее и сама требует калибровки — этому посвящён отдельный урок.
- Человеческая разметка — золотой стандарт и одновременно самый дорогой слой. Используется для калибровки judge и спорных кейсов, а не для каждого прогона.
Две ортогональные оси. Первая: offline (фиксированный датасет, прогон до деплоя) против online (скоринг живого трафика, мониторинг после деплоя) — зрелая система держит оба контура. Вторая: reference-based (есть эталонный ответ, сравниваем с ним) против reference-free (оцениваем свойства ответа без эталона — например, «не обещает возврат денег»).
Практическое правило пирамиды: максимум кейсов покрывайте дешёвыми ассертами, judge подключайте там, где ассертов не хватает, а людей — там, где не верите judge. Обратная пирамида — сто кейсов, и все через GPT-судью по размытой шкале «оцени от 1 до 10» — даёт дорогой и шумный эвал, которому никто в команде не доверяет.
Error analysis: смотреть на данные, а не на метрики
С чего начать, если эвалов ещё нет? Не с выбора фреймворка. Hamel Husain формулирует это как правило номер один: сначала смотрите на свои данные. Возьмите 50–100 реальных трейсов — полных записей «вход → промежуточные шаги → выход» — и прочитайте их руками. Это скучно, и именно поэтому большинство команд этого не делает, оставаясь с метриками, которые ничего не значат.
Процедура заимствована из качественных исследований. Open coding: читаете каждый трейс и пишете свободную заметку о первой замеченной ошибке («перепутал валюту», «ответил на английском», «выдумал пункт политики возврата»). Axial coding: группируете заметки в 5–10 категорий и считаете частоты. Получается таблица вида: «галлюцинации фактов из базы — 24%, неверный формат даты — 18%, пропущенный шаг инструкции — 11%». Это и есть карта того, что нужно мерить.
Метрики выводятся из категорий ошибок, а не наоборот: категория «неверный формат даты» превращается в regex-ассерт, «галлюцинации» — в judge с рубрикой про опору на контекст, «пропущенный шаг» — в чек-лист обязательных элементов ответа. Каждая метрика отвечает на вопрос «сколько у нас ошибок типа X», а не абстрактное «насколько ответ хорош». Пока вы не прочитали сотню трейсов, вы не знаете, что мерить, — и любой купленный дашборд лишь маскирует это незнание.
Первый eval за вечер
Минимальный работающий eval собирается за вечер: 20 кейсов из error analysis (15 типичных + 5 граничных), файл конфига promptfoo и один запуск из терминала:
# promptfooconfig.yaml
prompts:
- file://prompts/support-answer.txt
providers:
- openai:gpt-4o-mini
tests:
- vars:
question: "Как отменить подписку?"
assert:
- type: contains
value: "Настройки"
- type: not-contains
value: "не могу помочь"
- vars:
question: "Верните деньги за прошлый месяц"
assert:
- type: llm-rubric
value: "Ответ не обещает возврат средств, а направляет запрос в поддержку"
Команда npx promptfoo eval прогоняет матрицу «промпты × провайдеры × кейсы» и показывает таблицу pass/fail; npx promptfoo view открывает веб-отчёт с диффами. С этого момента любое изменение промпта проверяется одной командой против всех 20 кейсов сразу — цикл «починил один, сломал три» становится видимым.
Важно удержать масштаб: 20 кейсов не докажут, что система хороша, но уже поймают грубые регрессии. Дальше набор растёт по правилу из следующего урока: каждый продакшен-инцидент становится новым кейсом. Eval — не разовый артефакт, а копилка выученных ошибок.
Practice
Error analysis и смоук-набор
Возьмите 30–50 реальных трейсов своего LLM-проекта (или сгенерируйте диалоги с ботом вручную). Проведите open coding и axial coding, постройте таблицу категорий ошибок с частотами. Из категорий соберите 20 кейсов в promptfoo: минимум 10 код-ассертов и 3 llm-rubric.
Deliverable
Таблица «категория ошибки → частота → тип проверки» и файл promptfooconfig.yaml с 20 кейсами, который проходит `npx promptfoo eval` и ловит хотя бы одну реальную ошибку текущего промпта.
Checkpoint
Объясните коллеге, почему прогон трёх примеров руками не является тестированием LLM-системы и с чего правильно начинать построение эвалов.
- ✓ Назван недетерминизм LLM и цикл «починил один кейс — сломал три» как причины, почему ручная проверка не работает.
- ✓ Описана пирамида: код-ассерты, model-graded, человеческая разметка — и когда какой слой уместен.
- ✓ Сформулировано, что метрики выводятся из error analysis реальных трейсов, а не берутся из фреймворка.
Check yourself
Sources