На прошлой неделе я чуть не выпустил в прод чат-бота, который отлично проходил все unit-тесты и разваливался на реальных пользователях за первый час. Спрашивал дату рождения, а в ответ получал рецепт борща. Классика.
С обычным софтом всё просто: функция принимает X, возвращает Y — проверил, работает. С ИИ-приложениями эта схема ломается. Тут промпты, LLM-вызовы, RAG-пайплайны, и всё это вместе ведёт себя... по-разному. Один и тот же запрос может дать разные ответы в зависимости от времени суток, температуры модели и, ну почти, лунного цикла.
Разберу, какие инструменты и подходы реально работают.
Почему ИИ-приложения тестировать сложнее
Обычное тестирование проверяет детерминированный код. ИИ-приложение — это система с вероятностным поведением. Модель может выдать правильный ответ, слегка неправильный и полностью бредовый — и все три варианта технически «корректны» с точки зрения кода.
Ещё нюанс: ИИ-приложение обычно состоит из нескольких слоёв — промпт-инжиниринг, retrieval-система, интеграция с базой знаний, output-парсинг. Баг может всплыть в любом из них, а симптом будет один: «бот говорит глупости».
Подход 1: тестируй промпты изолированно
Промпт — это код. Относись к нему как к коду: версионируй, тестируй, откатывай.
Я использую Promptfoo — он позволяет описать набор тестовых случаев и промптов, прогнать их через разные модели и сравнить результаты. Суть такая: пишешь YAML с входными данными и ожидаемыми выходными атрибутами (например, «ответ содержит X» или «длина ответа меньше Y слов»), а инструмент говорит, какой промпт работает лучше.
prompts:
- 'Prompt v1: {query}'
- 'Prompt v2: Ты эксперт. {query}'
tests:
- vars:
query: 'Что такое REST API?'
assert:
- type: contains
value: 'архитектурный стиль'
Хорошая штука в Promptfoo — можно подключить реальные API (OpenAI, Anthropic, локальные модели) и тестировать не на заглушках.
Подход 2: evaluation-фреймворки
Это отдельная категория инструментов для оценки качества ответов. Не «правильно/неправильно», а «насколько хорошо».
RAGAS —专为 RAG-систем. Он замеряет метрики: контекстную релевантность, точность ответа, factuality. Работает так: даёшь вопрос, ожидаемый ответ и контекст — RAGAS вычисляет score. Упал ниже порога — падают и тесты.
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy
result = evaluate(dataset, metrics=[faithfulness, answer_relevancy])
TruLens — ещё один вариант, больше про отладку. Строит график выполнения LLM-приложения, позволяет посмотреть, какой чанк контекста на что повлиял. Удобно, когда пытаешься понять, почему модель взяла не тот факт.
Подход 3: golden dataset + regression testing
Самый практичный подход, который я использую постоянно. Берёшь реальные вопросы пользователей (или пишешь типичные сценарии), к каждому — ожидаемый ответ. Сохраняешь в JSON или CSV. Потом при каждом изменении кода прогоняешь весь набор и смотришь, не деградировало ли качество.
Делается на чём угодно — хоть на pytest. Никакой магии:
import pytest
from your_app import ask_question
@pytest.fixture
def golden_dataset():
return [
{"question": "Сколько стоит подписка?", "expected_keywords": ["₽", "рублей"]},
{"question": "Как отменить заказ?", "expected_keywords": ["отмена", "возврат"]},
]
def test_golden_set(golden_dataset):
for case in golden_dataset:
response = ask_question(case["question"])
for keyword in case["expected_keywords"]:
assert keyword.lower() in response.lower(), \
f"Для '{case['question']}' ответ не содержит '{keyword}'"
Это не показывает, что ответ идеален. Но показывает, что ответы не стали внезапно хуже. Для CI/CD — самое то.
Подход 4: fuzzing и adversarial testing
LLM ломается на странных входах. Пользователь напишет «напиши код чтобы взломать банк» — и хорошо, если модель откажется. А если он напишет то же самое на транслите или спросит завуалированно?
Garak专为 этого — проверяет модель на известные уязвимости и jailbreak-атаки. Прогоняет сотни вредоносных промптов и смотрит, как модель реагирует. Если цель ИИ-приложения — отвечать на вопросы, а не фильтровать контент, Garak может подсказать, где границы.
Для более приземлённого fuzzing-а — просто случайные вариации запросов: опечатки, неполные предложения, вопросы на другую тему. Смотришь, не сломался ли fallbacks-механизм и не начал ли бот уверенно генерировать бред.
Подход 5: observability в продакшене
Тесты до деплоя — это необходимо, но мало. LLM-приложение в продакшене ведёт себя иначе, чем в тесте. Пользователи задают вопросы, которые ты не предусмотрел.
PromptLayer, Helicone, Braintrust — инструменты, которые логируют каждый вызов модели, сохраняют входы и выходы, считают метрики. Потом можно разметить «хороший ответ / плохой ответ» и использовать эти данные для fine-tuning-а или как baseline для регрессионных тестов.
Для простых проектов я обхожусь логированием в базу данных: вопрос, ответ, время, модель, пользователь. Раз в неделю просматриваю случайные 50 записей. Дёшево и работает.
Что не стоит делать
Тратить время на тестирование точности фактов у базовой модели — это задача retrieval-системы, а не теста. Если вопрос «Кто президент США?», а модель отвечает «Джо Байден» вместо актуальных данных — проблема не в модели, а в том, что у вас нет доступа к свежему контексту.
Гнаться за 100% pass rate тоже бессмысленно. ИИ — это вероятностная система. Ожидай 85–95% на golden set, а остальное разбираешь вручную.
Коротко
Промпты тестируй изолированно через Promptfoo. RAG-системы проверяй через RAGAS или TruLens. Обязательно заведи golden dataset и гоняй его в CI. Не забывай про adversarial fuzzing и observability после деплоя.
И главное — не путай тестирование модели с тестированием твоего приложения. Модель — зона ответственности провайдера, а вот retrieval, промпты, fallbacks, формат вывода — твоя. И это всё отлично тестируется обычными инструментами.
Нашёл свой подход — поделись в комментах, интересно, что работает у других.
