Как тестировать мультиагентные пайплайны без слёз
Мультиагентная система падает не там, где падает одиночный вызов LLM. Разбираем, что и как тестировать, когда агенты вызывают друг друга, инструменты и внешние API.

Одиночный вызов модели тестируется просто: подали промпт, сравнили ответ с эталоном. Мультиагентный пайплайн так не проверишь. Здесь агент A вызывает инструмент, передаёт результат агенту B, тот решает, вернуться к A или дёрнуть внешний API, и вся эта цепочка недетерминированна: два запуска на одном входе дают разные траектории. Ошибка всплывает не в отдельном шаге, а в связке между шагами — и её видно только на уровне всего маршрута.
Ниже — как строить тесты так, чтобы ловить регрессии до продакшена, не гоняя живую модель на каждый коммит и не платя за это как за отдельного сотрудника.
Три уровня, которые нужно разделить
Главная ошибка — тестировать пайплайн как чёрный ящик: подали задачу, посмотрели финальный ответ. Так вы узнаете что сломалось, но не где. Разнесите проверки по уровням, и каждый следующий будет опираться на зафиксированный предыдущий.
- Уровень инструментов (tools). Функции, которые дёргает агент: запрос в базу, вызов API, парсинг. Это обычный детерминированный код — покрывается классическими юнит-тестами без всякой модели.
- Уровень одного агента. Проверяем, что при заданном входе и замоканных инструментах агент принимает верное решение: какой инструмент вызвать, с какими аргументами, когда остановиться.
- Уровень оркестрации. Проверяем маршрутизацию между агентами: правильно ли передаётся контекст, срабатывают ли условия перехода, не зацикливается ли граф.
Финальный сквозной тест на живой модели остаётся, но он самый дорогой и медленный. Он не должен быть единственным.
Мокайте недетерминированное, тестируйте детерминированное
Инструмент, который ходит в платёжный шлюз, не должен ходить туда в CI. Замените его на фикстуру с зафиксированным ответом. Тогда тест агента проверяет именно логику агента, а не доступность внешнего сервиса. То же с самой моделью: на уровне оркестрации подмените вызов LLM на заглушку, возвращающую заранее записанный ответ (record-replay), и вы получите быстрый детерминированный тест графа переходов.
Если тест мультиагентной системы падает раз через раз на одном и том же входе — это не флаки-тест, это флаки-архитектура. Недетерминизм должен жить только там, где вы его сознательно оставили.
Что именно проверять на уровне агента
Финальный текст ответа — плохая цель для ассерта: модель перефразирует, и тест падает без реальной регрессии. Проверяйте структурные вещи, которые либо верны, либо нет.
- Выбор инструмента. Агент вызвал
search_orders, а неrefund? Это чёткий факт, его легко зафиксировать. - Аргументы вызова. В инструмент ушли те поля, что нужно, в правильном формате.
- Условие остановки. Агент завершил цикл, а не ушёл в бесконечное «подумаю ещё раз».
- Число шагов. Задача решена за разумное количество итераций, а не за 40 вызовов модели.
Для смыслового качества ответа (а не структуры) используют отдельный подход — LLM-as-judge*. Он полезен, но помните: судья тоже недетерминирован, поэтому его вердикт — метрика для отслеживания тренда, а не бинарный gate, который блокирует мёрж.
Пример: тест агента с замоканным инструментом
import pytest
from unittest.mock import MagicMock
def test_agent_calls_search_before_refund():
# инструмент-заглушка вместо реального API
tools = {
"search_orders": MagicMock(return_value={"order_id": 42, "status": "delivered"}),
"refund": MagicMock(return_value={"ok": True}),
}
agent = build_agent(tools=tools, llm=recorded_llm("refund_case_01"))
trace = agent.run("Верните деньги за заказ 42")
calls = [step.tool_name for step in trace.steps]
# агент обязан сначала проверить заказ, потом делать возврат
assert calls == ["search_orders", "refund"]
tools["refund"].assert_called_once_with(order_id=42)Здесь recorded_llm — заглушка, воспроизводящая ранее записанный ответ модели для этого сценария. Тест детерминированный, гоняется за миллисекунды и падает ровно тогда, когда сломалась логика вызова инструментов.
Уровень оркестрации: ловим циклы и потерю контекста
Мультиагентный граф ломается двумя классическими способами. Первый — потеря контекста: агент B не видит то, что нашёл агент A, и переспрашивает пользователя или галлюцинирует. Второй — циклы: A отправляет B, B возвращает A, и так до упора в лимит токенов или таймаут.
Оба лечатся тестами на уровне графа с записанными ответами модели:
- Зафиксируйте набор сценариев-траекторий: вход → ожидаемая последовательность переходов между узлами.
- Прогоните граф на replay-заглушках LLM.
- Проверяйте не текст, а маршрут: какие узлы посещены, в каком порядке, с каким состоянием на входе каждого.
- Отдельно поставьте guard-тест на лимит шагов: любой сценарий обязан завершиться за N итераций, иначе тест падает.
Сравнение подходов к проверке качества
| Подход | Что ловит | Детерминизм | Стоимость прогона |
|---|---|---|---|
| Юнит-тесты инструментов | Баги в коде функций | Полный | Минимальная |
| Record-replay траекторий | Регрессии маршрутизации и вызовов | Полный | Низкая |
| LLM-as-judge | Смысловое качество ответа | Нет | Средняя (вызов модели) |
| Сквозной прогон на живой модели | Интеграцию целиком | Нет | Высокая |
CI без разорения на токенах
Держите пирамиду: много дешёвых детерминированных тестов внизу, мало дорогих сквозных наверху.
- На каждый push — юнит-тесты инструментов и record-replay траекторий. Живую модель не трогаем.
- На merge в основную ветку — набор LLM-as-judge на репрезентативной выборке сценариев, с порогом по метрике, а не по каждому кейсу.
- По расписанию (ночью) или перед релизом — сквозные прогоны на реальной модели, чтобы поймать дрейф провайдера: одна и та же версия модели со временем может менять поведение.
Логируйте полную траекторию каждого прогона — вызовы инструментов, аргументы, промежуточные состояния. Когда упадёт сквозной тест, именно трасса покажет, на каком переходе всё поехало, без переигрывания вручную.
* LLM-as-judge — приём, при котором одну модель просят оценить ответ другой по заданным критериям (полнота, соответствие фактам, тон). Заменяет ручную разметку, но сам подвержен недетерминизму и смещениям, поэтому применяется как метрика тренда, а не как строгий проходной тест.
Prompt-инженер: Идеальные запросы для Midjourney, ChatGPT и других моделей.
Спросить за 15 ₽Частые вопросы
Можно ли вообще обойтись без вызова живой модели в тестах?
Как бороться с флаки-тестами из-за недетерминизма модели?
Что такое record-replay в контексте агентов?
Как поймать бесконечные циклы между агентами?
Нужно ли тестировать промпты отдельно?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.