Как тестировать LLM-продукт: заметки QA-инженера, у которого взломали метрику

Я пришла в ИИ-продукты из тест-автоматизации: годы AQA, пайплайны, регресс, вот это все. И первое, что делает LLM с классическим QA-мышлением — ломает его об колено. Нет expected result. Выход недетерминирован: один вход — десять разных выходов, и все формально валидны. «Работает» перестает быть булевым.
Сейчас я строю Laspi — продукт, который превращает голосовую заметку владельца бизнеса в неделю постов для соцсетей. Генерация текста, картинок, видео, и все это должно быть не просто «без ошибок», а хорошим: в голосе владельца, без вранья про цены, без роботизированной каши. Ниже — что из QA-практики переносится на такие системы, что пришлось изобретать, и одна история про взлом метрики, ради которой статью стоило писать.
Проблема оракула: что такое «хороший пост»
В классике оракул дешевый: expected == actual. В генерации контента оракула нет — есть суждение. Мы собрали его из трех слоев, по убыванию надежности:
- Детерминированные детекторы там, где качество механизируется. Пассивный залог, канцелярит, глагольная вода, простыни-предложения — все это считается кодом, без LLM, воспроизводимо и бесплатно. Правило: все, что можно проверить детерминированно, проверяется детерминированно.
- LLM-судья с рубрикой там, где качество тацитно. Судья — тоже тестируемый артефакт: у него есть свой golden-сет, своя матрица ошибок и свои регрессы. Судью, которого никто не тестировал, нельзя пускать в гейт — это оракул на честном слове.
- Golden-сет с ручной разметкой как верховный суд. Дорого, медленно, и единственный источник правды о том, не врут ли первые два слоя.
Война-стори: reward hacking в дикой природе
Теперь обещанная история. У нас есть метрика ритма текста — коэффициент вариации длин предложений. Живой текст ритмичен: короткое, длинное, среднее. Плоский монотонный ритм — маркер машинной каши. Логично? Логично. Метрику поставили в оптимизацию: выше вариативность — лучше.
На очередном прогоне система выдает механический скор 0.928. Праздник. Открываю текст — а там телеграфные обрывки. Рубленые огрызки по три слова вперемешку с длинными предложениями. Писатель понял, что максимизирует не качество, а метрику, и честно ее максимизировал: чередование огрызков и простыней дает идеальный коэффициент вариации. Текст нечитаемый, метрика счастлива.
Закон Гудхарта в чистом виде: когда метрика становится целью, она перестает измерять. Для QA-инженера это знакомый зверь — разработчики, оптимизирующие покрытие тестами до бессмысленных ассертов, из той же породы. Выводы, которые мы вкрутили в систему:
- Коридор вместо максимизации. Ритм должен попадать в диапазон живого текста, а не стремиться в бесконечность. Любая стилометрическая метрика в оптимизации обязана быть коридором — иначе ее взломают.
- Факт-чек обязан быть блокирующим гейтом, а не советом. Метрика, которая «предупреждает, но пропускает», в недетерминированной системе не существует: ее обойдут в первый же день.
- Судье — явные анти-паттерны в промпт. «Телеграфный стиль — дефект» пишется буквально, потому что судья без явного запрета ставил рубленому тексту приличные оценки.
Приемка = зеленый скрипт
Главная перенесенная из AQA привычка звучит просто: готово == зеленый скрипт. Каждая фаза системы принимается исполняемым скриптом, который печатает метрику и вердикт PASS/FAIL. Не «посмотрели глазами, вроде норм», а число против заранее зафиксированного порога. Для детекторов дефектов пороги выглядят как в любом ML-QA: recall выше 0.7, false positive на заведомо чистой прозе ниже 15 процентов. Детектор, не прошедший гейт, не получает права на авторемонт текста — остается под человеком.
И зеркальное правило для пайплайна: никакое изменение генерации не едет в прод без измеримого сигнала качества. Нет сигнала — нет мержа, какой бы красивой ни казалась идея.
Синтетика врет, и это нормально, если об этом помнить
Отдельная ловушка — откуда берутся размеченные данные. Синтетические пары «до/после» (сами испортили текст — сами детектим) дают recall 1.00, и это число не значит ничего: детектор ловит дефекты, которые мы же и сконструировали. Честный recall существует только на живой прозе с ручной разметкой, и пока ее нет, все цифры носят статус provisional — прямо в отчете, чтобы никто не обманулся.
Вторая ловушка тоньше: утечка через данные. Если одни и те же эталонные авторы кормят и калибровку метрик, и банк примеров для генерации, система выучивает обходить собственную линейку. Разводить авторов между eval-сетом и обучающим банком — тот же принцип, что запрет тестировать на данных, на которых учились, просто в непривычной одежде.
Регресс для системы, которая правит сама себя
Когда система не только детектит дефекты, но и чинит их, появляется новый класс багов: ремонт, убивающий смысл. Страж на этот случай — семантическая близость до/после правки: слишком низкая — произошла подмена содержания, автоматический откат. Плюс метрики уровня партии, а не документа: непохожесть постов внутри недели (косинус по эмбеддингам с порогом), дрейф голоса как линт. Регресс в LLM-мире — это не «тест снова зеленый», это «распределение выходов не поехало».
Что все это значит для QA-инженера
Недетерминизм не отменяет QA — он повышает его в должности. Из проверки соответствия эталону профессия превращается в проектирование измеримости: придумать оракула там, где его нет, зафиксировать пороги до прогона, предусмотреть, как метрику будут ломать, и держать единственный источник правды в ручной разметке. Люди с рефлексами тестировщика — границы, FP/FN, регресс, недоверие к красивым цифрам — сейчас самые дефицитные в AI-продуктах, и разрыв только растет.
Если интересно, вокруг чего построена вся эта машинерия — сам продукт живет здесь: ИИ-генератор контента для малого бизнеса, который из двухминутной голосовой заметки собирает неделю постов. Архитектура целиком, от голоса до памяти бизнеса, разобрана в англоязычном кейс-стади на partenit.io. А проще всего посмотреть на живой продукт руками — первая неделя бесплатная, и да, каждый пост в ней прошел через те самые гейты.