Разработчик поправил системный промпт классификатора обращений и проверил десять примеров в playground. Ответы стали аккуратнее. Через день очередь возвратов выросла на 18%: короткое «деньги списали дважды» новая версия относила к общим вопросам, если клиент не употреблял слово «возврат». Вручную такие примеры никто не открыл.
Ручная проверка полезна для исследования. Она плохо отвечает на вопрос о релизе, потому что человек выбирает удобные входы, помнит ожидаемый ответ и оценивает формулировку целиком. Production распределён иначе: там есть опечатки, пустые поля, длинные переписки и редкие случаи с высокой ценой ошибки.
Промпт надо тестировать как поведение системы
Единицей теста является не текст промпта, а полный вызов: версия модели, параметры, шаблон, контекст, инструменты, схема ответа и постобработка. Изменение любого элемента может изменить результат.
Минимальный case хранит вход, ожидаемые инварианты и происхождение:
{
"id": "ticket-duplicate-charge-017",
"input": {"text": "деньги списали дважды"},
"expected": {"queue": "refunds", "priority": "high"},
"tags": ["short", "payments", "high_cost"],
"source": "prod_incident_2025_02"
}Детерминированные свойства проверяются кодом. JSON должен пройти схему, queue входит в разрешённое множество, ссылка принадлежит выданным источникам. Смысловые свойства оцениваются rubric, человеком или откалиброванным judge. Не поручайте модели проверять то, что сравнивается обычным ==.
@pytest.mark.parametrize("case", load_cases("support.jsonl"))
def test_classifier(case):
result = classify(case["input"], release=CANDIDATE)
assert result.queue == case["expected"]["queue"]
assert result.priority == case["expected"]["priority"]От playground к regression suite
Набор начинают не с тысячи придуманных фраз, а с реальных задач. Возьмите успешные случаи, ошибки production, пограничные входы и атаки. Сохраните теги по сегментам, иначе среднее 94% скроет 61% на платёжных обращениях.
Кандидата сравнивают с текущей версией на одних и тех же cases. Для вероятностного вызова фиксируют параметры, а критичные примеры прогоняют несколько раз. Решение о выпуске принимает gate: например, общее качество не хуже baseline более чем на один процентный пункт, на high_cost нет регрессии, schema validity равна 100%.
Результаты запуска являются артефактом: release manifest, case id, сырой ответ, нормализованный score, latency, токены и ошибка провайдера. Скриншоты из playground расследованию не помогают.
Что ручная проверка всё-таки умеет
Человек хорошо замечает новый тип ошибки, не предусмотренный rubric. Поэтому после автоматического прогона полезно просматривать changed cases, случайную выборку и ответы с низким согласием оценщиков. Найденная ошибка должна вернуться в dataset и стать regression case. Иначе команда будет находить её заново после каждого релиза.
Failure modes
Подгонка под знакомые примеры. Dataset превращается в тренировочный набор для автора промпта. Держите holdout и регулярно добавляйте свежую production-выборку.
Одна температура, один прогон, один счастливый ответ. Для нестабильных задач измеряйте pass rate на нескольких запусках.
Среднее без сегментов. Отчёт улучшается, дорогой класс ломается. Вводите отдельные gates по тегам риска.
Меняется модель, а тестируется только prompt. Записывайте полный behavioral release.
Golden answer сравнивают посимвольно. Для генерации это наказывает нормальный парафраз. Проверяйте факты, ограничения и rubric; exact match оставьте классификации и структурам.
Тестовый набор не похож на production. Следите за распределением языка, длины, каналов и типов клиентов, а не только за числом строк.
Правило главы
Ручной прогон помогает придумать изменение, но не доказывает его безопасность. Релизуйте полный behavioral release через версионируемый набор реальных cases, автоматические проверки и gates по дорогим сегментам.
Практикум
Соберите 50 cases для классификатора поддержки: 30 обычных обращений, 10 прежних ошибок и 10 пограничных или вредоносных входов. Добавьте теги payments, delivery, short, typo, high_cost. Сравните baseline и новый промпт одним runner.
Отчёт должен показывать качество по каждому тегу, список изменившихся решений, schema validity, p95 latency и стоимость. Затем намеренно улучшите общий score ценой двух ошибок в high_cost и убедитесь, что release gate блокирует такую версию.