Снапшот-тесты для AI-агента: как я заставил pytest ловить регрессы в промптах

Снапшот-тесты для AI-агента: как я заставил pytest ловить регрессы в промптах

Как всё сломалось из-за одного абзаца

Понедельник, утро. Приходит сообщение: бот снова отдаёт заявки на ручную обработку. Вчера работал нормально, сегодня — нет. Смотрю логи и понимаю: за выходные кто-то добавил в системный промпт пару строк про работу с физлицами. Само по себе нормально, но модель начала перестраховываться и помечать «требует менеджера» всё, где сумма больше полумиллиона.

Два дня агент работал вполсилы, пока не накопилось жалоб. После этой истории я решил сделать обычные регрессионные тесты, только не для кода, а для промптов. За следующие 11 месяцев они поймали 23 поломки до того, как их увидел клиент.

Почему обычные assert здесь не работают

В обычном коде пишешь assert calculate(2, 2) == 4. С LLM так не получается — модель может ответить «4», «четыре» или «два плюс два равно четырём». Строковое сравнение сразу отваливается.

Плюс недетерминированность. Даже с temperature=0 иногда вылезает лишнее слово. Если тест падает раз в десять запусков, через неделю его просто начинают игнорировать.

И ещё цена. Тысяча вызовов GigaChat Pro уже не бесплатная история.

Золотой датасет из 47 кейсов

Я взял реальные логи за два месяца и отобрал 47 примеров, где поведение должно оставаться предсказуемым:

  • 12 кейсов на квалификацию лида;
  • 8 на извлечение сущностей;
  • 9 «тонких» сценариев, где уже были баги;
  • 18 на безопасность и промпт-инъекции.

Всё лежит в YAML, который спокойно правят и продакт, и я.

Три уровня проверок

  • Жёсткие — для JSON-ответов: сравниваю поля, числа с допуском, списки без учёта порядка.
  • Семантические — беру эмбеддинги и считаю косинусную близость. Порог подобрал 0.78.
  • Инвариантные — обычный regex или мини-промпт, чтобы проверить запреты.

Чтобы уменьшить флуктуации, делаю три прогона и беру медиану. Структурные проверки должны проходить все три раза.

Как это работает в CI

Тесты запускаются только при изменениях в prompts/ и agents/. Полный прогон занимает около четырёх минут и 60–70 рублей на токенах. Каждый запуск сохраняет JSON с ответами — потом видно, как меняется тон или длина ответов.

Что изменилось

До тестов было в среднем 2.3 хотфикса в месяц по жалобам клиентов. Среднее время от поломки до фикса — 31 час.

После: 0.4 хотфикса в месяц, а обнаружение занимает 4 минуты. 23 регресса пойманы до прода, из них семь были критичными.

Цена внедрения — пять дней на сборку датасета. Дальше поддержка занимает час в месяц.

Где не работает

На креативных задачах семантическая близость не поможет отличить «хорошо» от «отлично». В быстро меняющейся доменной области датасет устаревает слишком быстро. С многошаговыми агентами тоже отдельная история — там нужны моки.

Главный вывод

Промпты — это тоже код. Их правят чаще, чем функции, а ломаются они так же легко. Если у тебя есть хотя бы один платящий клиент, 30–50 кейсов окупятся уже на первом предотвращённом откате.