Тестирование AI-приложений принципиально отличается от тестирования детерминированного софта. Языковая модель может дать разные ответы на один и тот же вопрос, и «правильность» ответа часто субъективна. Тем не менее, без тестирования невозможно уверенно выкатывать изменения в промптах, переключать модели или обновлять RAG-пайплайн. В этой статье разберём три уровня тестирования: unit-тесты, eval-метрики и бенчмарки.
Unit-тесты для AI-приложений
Unit-тесты в AI-приложениях проверяют не саму модель, а вашу обвязку: формирование промптов, парсинг ответов, обработку ошибок, PII-фильтрацию.
import pytest
def build_prompt(user_query: str, context: str) -> list:
return [
{"role": "system", "content": f"Answer based on context:\n{context}"},
{"role": "user", "content": user_query},
]
def parse_response(raw: str) -> dict:
"""Извлекает структурированный ответ из текста модели"""
import json
# Находим JSON в ответе
start = raw.find("{")
end = raw.rfind("}") + 1
if start == -1 or end == 0:
raise ValueError("No JSON found in response")
return json.loads(raw[start:end])
# --- Тесты ---
def test_build_prompt_includes_context():
messages = build_prompt("Какая цена?", "Товар X стоит 100 руб.")
assert len(messages) == 2
assert "Товар X стоит 100 руб." in messages[0]["content"]
def test_parse_response_extracts_json():
raw = 'Вот результат: {"answer": "42", "confidence": 0.95} надеюсь помог'
result = parse_response(raw)
assert result["answer"] == "42"
assert result["confidence"] == 0.95
def test_parse_response_raises_on_no_json():
with pytest.raises(ValueError):
parse_response("Просто текст без JSON")
Eval-метрики: оценка качества ответов LLM
Eval (evaluation) — это автоматизированная оценка качества ответов модели на наборе эталонных вопросов. Основные подходы:
LLM-as-Judge
Используйте сильную модель для оценки ответов более слабой или для сравнения двух моделей:
async def llm_judge(client, question: str, answer: str, reference: str) -> dict:
"""Оценка ответа через LLM-судью"""
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{
"role": "system",
"content": (
"You are evaluating AI responses. Score each criterion 1-5.\n"
"Respond in JSON: {relevance, accuracy, completeness, overall}"
),
}, {
"role": "user",
"content": (
f"Question: {question}\n"
f"Reference answer: {reference}\n"
f"AI answer: {answer}\n"
"Evaluate the AI answer against the reference."
),
}],
response_format={"type": "json_object"},
temperature=0,
)
import json
return json.loads(response.choices[0].message.content)
# Пример запуска eval-набора
eval_set = [
{"q": "Что такое RAG?", "ref": "RAG — это Retrieval-Augmented Generation..."},
{"q": "Как работает embedding?", "ref": "Embedding — это векторное представление..."},
]
for item in eval_set:
answer = await get_model_answer(item["q"])
scores = await llm_judge(client, item["q"], answer, item["ref"])
print(f"Q: {item['q']} | Overall: {scores['overall']}/5")
Автоматические метрики
Для некоторых задач можно использовать вычислимые метрики без LLM-судьи:
- Exact Match — точное совпадение ответа с эталоном (для фактологических вопросов)
- BLEU / ROUGE — оценка перекрытия n-грамм (для суммаризации)
- Cosine Similarity — семантическая близость через эмбеддинги
- Format Compliance — ответ соответствует заданному формату (JSON, markdown)
Бенчмарки: сравнение моделей на ваших данных
Публичные бенчмарки (MMLU, HumanEval) полезны для общего понимания, но ваша задача уникальна. Создайте собственный бенчмарк:
import time
from openai import OpenAI
client = OpenAI(
base_url="https://api.modelswitch.ru/v1",
api_key="msk_ваш_ключ",
)
MODELS = ["gpt-4o", "gpt-4o-mini", "claude-3.5-sonnet"]
BENCHMARK = [
{"input": "Переведи на английский: Привет, мир!", "expected_contains": "Hello"},
{"input": "2 + 2 * 3 = ?", "expected_contains": "8"},
{"input": "Столица Франции?", "expected_contains": "Париж"},
]
for model in MODELS:
correct, total_time = 0, 0
for test in BENCHMARK:
start = time.time()
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": test["input"]}],
max_tokens=100,
)
elapsed = time.time() - start
total_time += elapsed
answer = resp.choices[0].message.content
if test["expected_contains"] in answer:
correct += 1
print(f"{model}: accuracy={correct}/{len(BENCHMARK)}, "
f"avg_latency={total_time/len(BENCHMARK):.2f}s")
ModelSwitch упрощает бенчмаркинг, позволяя отправлять одинаковые запросы к разным моделям через единый API. Вы меняете только параметр model — остальной код идентичен.
Заключение
Тестирование AI-приложений — это три уровня: unit-тесты для вашего кода, eval-метрики для качества ответов и бенчмарки для сравнения моделей. Автоматизируйте все три уровня в CI/CD, запускайте eval при каждом изменении промпта и используйте бенчмарки при выборе или смене модели. Только так вы сможете уверенно развивать AI-продукт.