Вы выпускаете в продакшен хаос. Давайте это исправим
Вы настраиваете цепочку из трех LLM-агентов. Один анализирует запрос, второй ищет данные в базе, третий генерирует ответ. В демо все работает идеально. В продакшене первый агент иногда решает, что запрос - это шутка, и передает второму мусор. Система ломается, а вы не понимаете почему. Классика.
Традиционные юнит-тесты здесь бесполезны. LLM недетерминированы по своей природе. Один и тот же промпт может дать разные результаты. Добавьте сюда взаимодействие нескольких агентов - и вы получаете систему, которую невозможно отладить стандартными методами.
Главная проблема: вы не можете написать assert agent.run(input) == expected_output. Потому что agent.run(input) каждый раз возвращает что-то новое. Иногда лучше, иногда хуже, иногда совсем не то. Как оценить, готов ли такой агент к продакшену?
Офлайн-оценка: тестируем, когда все спят
Офлайн-оценка (offline evaluation) - это прогон вашего LLM-агента на заранее собранном наборе тестовых данных без подключения к реальным пользователям или внешним API. Вы симулируете работу системы в контролируемой среде.
Зачем? Потому что запускать A/B тест на реальных пользователях с сырым агентом - это профессиональное самоубийство. Офлайн-оценка дает вам цифры. Не "вроде работает", а конкретные метрики: на 87% запросов агент выбирает правильный инструмент, заполнение параметров стабильно на 92%, задержка не превышает 2.3 секунды.
В 2026 году это уже не опционально. Это обязательный этап пайплайна ML-инженерии для любого серьезного продукта на основе LLM.
Из чего строится фреймворк
Хороший фреймворк офлайн-оценки для LLM-агентов состоит из четырех взаимосвязанных компонентов. Если пропустить один - вся конструкция рухнет.
| Компонент | Что делает | Критичность |
|---|---|---|
| Контролируемая среда выполнения | Запускает агентов в изоляции, подменяет внешние API, логирует все шаги | Высокая. Без этого вы тестируете не агента, а весь интернет. |
| Эталонный датасет | Содержит входные запросы и "правильные" последовательности действий агентов | Максимальная. Мусор на входе - мусор на выходе. |
| Набор метрик | Измеряет не только правильность, но и стабильность, стоимость, скорость | Высокая. Одна метрика - слепота. |
| Аналитический движок | Агрегирует результаты, находит паттерны ошибок, генерирует отчеты | Средняя. Без него вы утонете в данных. |
Звучит сложно? Это потому, что так и есть. Тестирование многоагентных систем сложнее тестирования одного LLM в разы. Но деваться некуда - либо вы строите этот фреймворк, либо ваш агент в продакшене ведет себя как пьяный сотрудник.
1Собираем датасет, который не стыдно показать
Первая ошибка - использовать 10-20 примеров из головы. Вторая - скрестить руки и сказать "нужны тысячи размеченных данных". Истина посередине.
Для старта хватит 100-200 разнообразных сценариев. Но разнообразие - ключевое слово. Если ваш агент бронирует столики, нужны не только "забронируй на двоих на 19:00", но и "мой кот хочет поужинать в вашем ресторане, что посоветуете?", и "отмени бронь номер 54812", и запросы с пропущенными параметрами, и с противоречивыми инструкциями.
Как получить эти данные? Три пути:
- Синтетика: Генерация с помощью другой LLM (например, GPT-4.5 Turbo, актуальной на март 2026). Да, вы используете одну модель для тестирования другой. Это нормально, если валидировать результаты.
- Пользовательские логи: Берите реальные запросы из логов (если есть). Очищайте от персональных данных. Размечайте вручную или полуавтоматически.
- Краудсорсинг: Сервисы вроде Scale AI или Appen. Дорого, но качественно.
Формат датасета? JSON Lines (.jsonl). Каждая строка - объект с полями: уникальный id, входной запрос, ожидаемая последовательность вызовов инструментов (или конечный ответ), метаданные (сложность, категория).
{
"id": "booking_042",
"query": "Перенеси мою бронь с сегодня 20:00 на завтра 19:30",
"expected_actions": [
{"tool": "find_reservation", "args": {"date": "2026-03-24", "time": "20:00"}},
{"tool": "update_reservation", "args": {"new_date": "2026-03-25", "new_time": "19:30"}}
],
"metadata": {"category": "update", "complexity": "medium"}
}2Строим контролируемую песочницу
Ваши агенты вызывают внешние API? Работают с базой данных? Отправляют письма? В офлайн-режиме все это должно быть заглушено (mocked).
Цель: полная детерминированность окружения. Вызов инструмента send_email(to, body) не должен реально отправлять письмо, а должен возвращать заранее заданный ответ ("ok", "error") и записывать факт вызова в лог.
Реализация на Python с помощью паттерна Dependency Injection:
from abc import ABC, abstractmethod
from typing import Any, Dict
class ToolMock(ABC):
"""Абстрактная заглушка для инструмента агента."""
@abstractmethod
def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
pass
class EmailToolMock(ToolMock):
def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
# Логируем вызов для последующего анализа
ExecutionLogger.log_tool_call("send_email", args)
# Всегда возвращаем успех в тестах
return {"status": "sent", "message_id": "mock_123"}
# Внедряем заглушку в агента
agent.email_tool = EmailToolMock()
# Теперь агент работает в изоляцииПесочница также должна контролировать случайность (randomness) в самих LLM. Установите seed для генератора случайных чисел в начале каждого теста. Для облачных моделей типа OpenAI API используйте параметр seed в запросе (поддерживается в GPT-4.5 Turbo и новее). Это не сделает ответы полностью идентичными, но значительно снизит вариативность.
Предупреждение: Даже с установленным seed ответы LLM могут отличаться между прогонами, особенно при изменении температуры (temperature). Для оценки стабильности запускайте каждый тестовый сценарий несколько раз (например, 5) и смотрите на распределение результатов. Один прогон - статистически ничто.
3Выбираем метрики, которые имеют смысл
Accuracy (точность) для LLM-агентов - почти бессмысленная метрика. Вместо нее используйте набор специализированных метрик, каждая из которых отвечает на конкретный вопрос.
| Метрика | Что измеряет | Как считать |
|---|---|---|
| Success Rate | Доля сценариев, где агент выполнил задачу полностью и правильно | Сравнение итогового состояния системы с ожидаемым |
| Tool Selection Accuracy | Насколько часто выбирается правильный инструмент | F1-score по названиям вызванных инструментов |
| Argument Fidelity | Точность заполнения параметров инструментов | Cosine similarity или точное совпадение ключевых значений |
| Stability Score | Консистентность результатов при многократном запуске | Доля сценариев, где результат не менялся в N запусках |
| Cost per Task | Средняя стоимость выполнения одного сценария (в токенах или деньгах) | Сумма токенов вход/выход по всем вызовам моделей |
| Latency 95th percentile | Время выполнения для 95% самых быстрых сценариев | Замер от начала запроса до финального ответа |
Для подсчета метрик вроде Success Rate часто используют LLM-судью - еще одну модель, которая оценивает, выполнил ли агент задачу. В 2026 году для этого есть специализированные модели с улучшенным критическим мышлением, такие как Claude 3.7 Sonnet или специально дообученные версии Llama-4. Но будьте осторожны: судья тоже может ошибаться. Всегда делайте выборочную проверку человеком.
4Автоматизируем прогон и анализ
Ручной запуск 200 тестов по 5 раз каждый - это ад. Нужен скрипт или система, которая делает все сама.
Базовый пайплайн на Python с использованием асинхронности для скорости:
import asyncio
import json
from typing import List
from your_agent_module import MultiAgentSystem
from sandbox import MockEnvironment
async def run_evaluation_pipeline(dataset_path: str, num_runs: int = 5):
"""Запускает оценку агента на датасете."""
# Загрузка датасета
with open(dataset_path, 'r') as f:
scenarios = [json.loads(line) for line in f]
results = []
for scenario in scenarios:
scenario_results = []
for run_id in range(num_runs):
# Создаем свежую песочницу для каждого запуска
env = MockEnvironment(seed=run_id)
agent = MultiAgentSystem(environment=env)
# Запускаем агента
start_time = asyncio.get_event_loop().time()
agent_actions = await agent.run(scenario['query'])
end_time = asyncio.get_event_loop().time()
# Собираем результаты
run_result = {
'scenario_id': scenario['id'],
'run_id': run_id,
'actions': agent_actions,
'latency': end_time - start_time,
'tokens_used': env.get_token_count()
}
scenario_results.append(run_result)
results.append(scenario_results)
# Анализ и вывод отчета
analyze_and_report(results, scenarios)
return resultsПосле прогона вы получаете гору данных. Не сваливайте все в CSV. Используйте инструменты вроде Weights & Biases, MLflow или даже простой Jupyter Notebook с Plotly для визуализации.
Что смотреть в первую очередь:
- Корреляция ошибок со сложностью сценария: Агент стабильно падает на запросах с отрицанием? Значит, нужно доработать понимание логики.
- Распределение задержек: Есть выбросы? Возможно, какой-то инструмент работает нестабильно.
- Стоимость самых дорогих сценариев: Может, для простых запросов агент делает лишние вызовы LLM?
Ошибки, которые сломают вашу оценку
Я видел десятки попыток. Почти все проваливаются на одних и тех же граблях.
Ошибка 1: Тестирование в реалистичных, но неконтролируемых условиях. Вы подключаете агента к реальной тестовой базе данных. База падает - все тесты красные. Вы чините базу, но это уже не оценка агента, а оценка инфраструктуры. Заглушки. Всегда заглушки.
Ошибка 2: Игнорирование стоимости. Ваш агент решает задачу с успешностью 99%, но тратит на один запрос $0.50 из-за вызовов GPT-4.5 Turbo с огромным контекстом. В продакшене это сожжет бюджет за неделю. Считайте токены с первого дня.
Ошибка 3: Одна метрика для всех. У вас есть сценарии "простой поиск" и "сложный анализ с рассуждениями". Усредненный success rate скроет, что на сложных сценариях агент проваливается в 40% случаев. Сегментируйте результаты по категориям сложности.
Ошибка 4: Отсутствие базы знаний об ошибках. Каждый провальный тест - это золотая жила. Не просто фиксируйте "failed", а сохраняйте полную цепочку рассуждений агента, вызовы инструментов, промежуточные состояния. Это даст материал для дообучения или настройки промптов.
Что в итоге? Цифры, а не ощущения
Готовность к продакшену - это не субъективное "я думаю, он готов". Это выполнение пороговых значений по ключевым метрикам.
Установите минимальные барьеры для своей системы. Например:
- Success Rate ≥ 95% для простых сценариев, ≥ 80% для сложных.
- Stability Score ≥ 90% (в 9 из 10 запусков результат одинаковый).
- Средняя задержка ≤ 3 секунды.
- Стоимость 95-го перцентиля ≤ $0.10 за запрос.
Если ваша система проходит по всем пунктам на офлайн-датасете - у вас есть основание для cautious релиза в прод с мониторингом. Если нет - возвращайтесь к доработке промптов, архитектуры агентов или выбору моделей.
Самый неочевидный совет? Планируйте, что ваш фреймворк офлайн-оценки будет живым. Новые типы запросов появятся уже после релиза. Добавляйте их в датасет, прогоняйте оценку заново. Это не разовая акция перед запуском, а постоянный процесс, который держит ваших агентов в тонусе.
К 2026 году инструменты для такой оценки стали доступнее. Смотрите в сторону open-source решений вроде глубоко развитого к 2026 году DeepEval или коммерческих платформ типа Weights & Biases для LLM-опс. Но фреймворк, заточенный под вашу специфику, вы построите только сами. Начинайте сегодня, и ваш завтрашний продакшен-релиз не превратится в ночной кошмар поддержки.