Тестирование LLM-функций

LLM недетерминирован, но это не повод не тестировать код вокруг него.

Тестировать нужно свой код (сборку промптов, парсинг, обработку ошибок, логику инструментов), а качество ответов модели проверять отдельными оценочными тестами.

Проблема: реальный API в тестах — плохо

Дёргать настоящий API в юнит-тестах медленно, стоит денег и нестабильно (ответы каждый раз разные, бывают 429). Поэтому в тестах API подменяют фейком (mock), который возвращает заранее заданный ответ.

Тест с фейк-клиентом (запускаемо)

Бизнес-функция принимает клиент как зависимость; в тесте подставляем фейк и проверяем логику без сети. Чистый Python:

def classify_sentiment(client, text):
    # Логика: дергает client.complete и парсит ответ
    raw = client.complete(f"Тон сообщения одним словом: {text}")
    return raw.strip().lower()

class FakeClient:
    def __init__(self, canned):
        self.canned = canned
        self.calls = []
    def complete(self, prompt):
        self.calls.append(prompt)
        return self.canned

# Тест: не дёргаем реальный API, подставляем фейк
fake = FakeClient(canned="  Позитивный  ")
result = classify_sentiment(fake, "Отличный сервис!")
assert result == "позитивный", result
assert len(fake.calls) == 1
print("Результат:", result)
print("Вызовов API:", len(fake.calls))
print("Тест пройден")

Вывод:

Результат: позитивный
Вызовов API: 1
Тест пройден

Здесь проверена ваша логика (обрезка пробелов, приведение к нижнему регистру) — без обращения к модели. Передача клиента как аргумента (dependency injection) делает подмену тривиальной.

Что именно покрывать юнит-тестами

  • Сборка промпта/сообщений из входных данных.
  • Парсинг ответа: корректный JSON, частичный, мусор.
  • Обработка ошибок: 429, таймаут, обрыв по max_tokens.
  • Диспетчер инструментов: известный/неизвестный инструмент, плохие аргументы.

Оценочные тесты (evals) — про качество модели

Отдельно проверяют качество ответов на наборе примеров с ожидаемым результатом: точность классификации, доля валидного JSON, прохождение проверок. Поскольку вывод недетерминирован, оценивают не «равно строке», а по критериям (содержит нужное, проходит схему, оценка судьёй). Evals гоняют при смене промпта или модели, чтобы поймать регресс.

Итог

  • Юнит-тесты — на ваш код вокруг LLM, с фейком вместо реального API.
  • Передавайте клиент как зависимость — так его легко подменить в тесте.
  • Качество модели проверяют отдельными оценочными тестами по критериям, а не по точному совпадению.
Проверьте себя
1. Почему в юнит-тестах LLM-кода API обычно подменяют фейком?
AЧтобы тесты были точнее модели
BРеальный API медленный, платный и нестабильный; фейк даёт быстрый детерминированный ответ
CПотому что SDK запрещает тесты
DЧтобы увеличить покрытие токенов
2. Что проверяют юнит-тесты вокруг LLM-функции?
AКачество формулировок модели
BСвой код: сборку промпта, парсинг ответа, обработку ошибок, логику инструментов
CСкорость интернета
DЦену токенов
3. Как оценивают качество ответов модели, раз вывод недетерминирован?
AСравнением строго по равенству строке
BОценочными тестами по критериям: содержит нужное, проходит схему, оценка судьёй
CОни не поддаются оценке
DТолько вручную в проде
Поддержать проект