Вы создали внутренний инструмент, который позволяет команде запускать 28 юнит-тестов для функции на базе LLM, вообще не вызывая API модели. Вы сделали это, обернув модель в интерфейс, позволяющий использовать заглушки (fakeable interface), и добавив три уровня оценки: детерминированную, эвристическую и на основе LLM.

Стандартные утверждения (assertions) ломаются, как только LLM начинает генерировать текст. Один и тот же промпт может давать разные предложения при каждом запуске, поэтому assertEqual(output, expected) сигнализирует об ошибке, даже если модель повела себя правильно. Большинство инженерных групп либо выпускают функцию без проверки, либо пытаются тестировать саму модель, относясь к постоянно меняющейся цели так, будто это статичная библиотека.

Почему это важно

LLM теперь встроены в рабочие процессы, взаимодействующие с клиентами: рассылки по электронной почте, ответы службы поддержки, генерация контента. Одна галлюцинация или утечка идентификатора может нанести ущерб репутации бренда, раскрыть конфиденциальные данные или привести к нарушениям комплаенса. Без надежной стратегии тестирования команды тратят время на борьбу с нестабильными (flaky) сбоями или выпускают баги, которые проявляются только в продакшене.

Подход: сужение зоны ответственности модели

Первым шагом было ограничение того, что именно делает LLM. В системе автора модель только составляет черновики сообщений. Вся логика маршрутизации, управление состоянием и проверки безопасности остаются в обычном коде. Ограничивая модель одной четко определенной задачей на выходе, окружающая система остается детерминированной и тестируемой.

Чтобы это стало возможным, LLM находится за интерфейсом провайдера, что позволяет использовать заглушку (fake) в тестах. В продакшене реализация вызывает внешний API; в тестовом наборе легковесная заглушка возвращает заготовленный ответ. Поскольку остальной код взаимодействует только с интерфейсом, весь рабочий процесс можно проверить с помощью юнит-тестов, которые никогда не обращаются к сети. В результате получается предсказуемое ядро, которое проверяется 28 тестами.

Честная система оценки

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

  • Уровень 1 — Детерминированные проверки Простые правила на основе регулярных выражений отлавливают конкретные ошибки, такие как неверный ID здания или запрещенные токены. Эти проверки работают быстро и дают бинарный результат: пройдено/не пройдено.

  • Уровень 2 — Эвристические проверки Скрипты ищут выдуманные числа или даты, фиксируя очевидные фактические ошибки. Они пропускают ложные утверждения, не содержащие числовых данных, и автор открыто признает это ограничение.

  • Уровень 3 — LLM-судья Вторичная модель оценивает тон и профессионализм. Поскольку этот шаг опирается на другую вероятностную систему, он используется только для субъективных аспектов, где детерминированные правила были бы невозможны.

Ключом к системе является набор данных, используемый для оценки. Автор закодировал известные паттерны ошибок — специфические ловушки и знания предметной области — так, чтобы система тестировала именно те ошибки, которые возникали на практике. Это не магическая «всеобъемлющая» защита, а целевая сеть безопасности.

Что это значит для команд

  • Минимизируйте задачи LLM. Меньше ответственности — проще изоляция и тестирование.
  • Выносите маршрутизацию, состояние и безопасность в код. Традиционная логика остается детерминированной и полностью тестируемой.
  • Используйте интерфейс с возможностью подмены (fakeable interface). Юнит-тесты запускаются без внешних вызовов, что делает набор тестов быстрым и надежным.
  • Используйте многоуровневую оценку. Начинайте с детерминированных правил, добавляйте эвристику для известных галлюцинаций и оставляйте LLM-судей для субъективных проверок качества.
  • Обозначайте границы. Ни один уровень не гарантирует совершенства; система ловит только то, что вы явно запрограммировали обнаруживать.

Контраргумент: вы все равно не можете протестировать саму модель юнит-тестами

Автор признает, что модель — это «подвижная мишень». Даже уровень LLM-судьи наследует тот же недетерминизм, который он пытается оценить. Следовательно, система никогда не сможет гарантировать, что каждая галлюцинация или нарушение политики будут пойманы до релиза. Этот подход снижает риск, а не устраняет его, и он опирается на способность команды поддерживать актуальность данных для оценки по мере появления новых видов ошибок.

Итог

Вы не можете написать классический юнит-тест, который проверяет точный вывод LLM, но вы можете построить систему, в которой влияние модели ограничено, ее интерфейс заменим, а ее вывод проходит через многоуровневые прозрачные проверки. Такое сочетание превращает в противном случае нестабильный компонент в предсказуемую часть более крупного, тестируемого приложения.