Зелёные тесты и молчаливая деградация
LLMOps и продакшн · Урок 2 / 22
Почему обычные тесты не ловят падение качества
Модульный тест проверяет, что функция вернула ожидаемое значение. Для генеративного ответа ожидаемого значения нет, поэтому команда обычно пишет то, что написать можно: проверку, что ответ непустой, что JSON разбирается, что поле есть. Такие тесты остаются зелёными при любой деградации содержания.
Что реально проверяемо
- Контракт формата. Разбирается ли ответ, есть ли обязательные поля, укладывается ли в допустимые значения. Ловит поломку интеграции, не ловит бессмыслицу в правильном формате.
- Обязательные и запрещённые элементы. В ответе про возврат товара должен фигурировать срок; в ответе поддержки не должно быть выдуманной ссылки на несуществующий раздел.
- Инварианты. Если в запросе не хватает данных, система обязана сказать об этом, а не додумать. Это проверяется на специально собранных неполных входах.
- Доля успеха на наборе. Не «прошёл или нет» на одном примере, а процент по набору из десятков примеров. Единственная величина, у которой есть смысл сравнивать релизы.
Порог, а не бинарный результат
Прогон набора даёт число: например, 88 процентов ответов удовлетворяют критериям. Само по себе оно ничего не значит — значение имеет сравнение с предыдущим прогоном на том же наборе. Практичная рамка выглядит так.
падение доли успеха менее 2 п.п. -> шум, катим падение 2-5 п.п. -> разбираемся до выката падение более 5 п.п. -> не катим любое падение на критичном срезе -> не катим независимо от среднего рост доли отказов модели вдвое -> не катим
Числа у каждой команды свои, но порог должен быть записан заранее. Записанный после релиза порог всегда оказывается ровно таким, чтобы релиз прошёл.
Разброс между прогонами
Один и тот же набор на одном и том же промпте даст немного разные числа. Прежде чем реагировать на изменения, измерьте собственный шум: прогоните набор три раза без единого изменения и посмотрите разброс. Если он составляет три пункта, вы физически не можете отличить улучшение на два пункта от случайности — либо увеличивайте набор, либо снижайте температуру на прогонах оценки, либо перестаньте обсуждать двухпунктовые победы.
Тест-сьют промпта и контракт на выход подробно разбирались в курсе по промптингу; здесь важно другое — этот набор перестаёт быть разовой проверкой перед релизом и становится регулярным замером, который вы делаете и без релизов, потому что меняться может не только ваш код.
Три вида ложного спокойствия
Зелёный статус приходит из трёх мест, и каждое обманывает по-своему. Проверки формата зелены, потому что модель отлично держит структуру даже когда пишет ерунду. Ручная проверка автора зелена, потому что автор проверяет те примеры, под которые писал промпт. Обратная связь пользователей зелена, потому что недовольные молчат: до жалобы доходит примерно один из двадцати неудачных ответов, остальные просто уходят. Ни один из трёх сигналов не заменяет регулярного прогона на фиксированном наборе, где состав примеров не подстраивается под текущую версию системы.
Шпаргалка
- Обычные тесты ловят формат, а не смысл.
- Сравнивайте долю успеха на наборе, а не отдельные ответы.
- Пороги отката записываются до релиза, а не после.
- Измерьте собственный шум прогона, прежде чем радоваться улучшениям.
Возьмите свою ИИ-функцию (или любую публичную задачу — например, классификацию обращений). Соберите набор из 30 запросов: 20 типичных и 10 сложных или краевых. Опишите, по каким проверяемым критериям вы засчитываете ответ. Прогоните набор трижды подряд без единого изменения промпта и модели, зафиксируйте долю успеха в каждом прогоне и разброс между ними. Затем сформулируйте свои пороги: при каком падении вы разбираетесь, а при каком не выкатываете.