Что такое «test coverage» для агента (покрытие траекторий, а не кода)?
Краткий тезис
coverage|Test coverage для агента — это метрика, оценивающая, насколько тесты охватывают возможные траектории (последовательности шагов) и состояния агента, в отличие от традиционного code coverage, который измеряет покрытие строк кода. Поскольку агент управляется LLM, его поведение недетерминировано, и основное внимание уделяется разнообразию протестированных путей — от начального запроса до конечного действия, включая промежуточные состояния памяти и сессии. Цель — покрыть все часто встречающиеся траектории (принцип Парето 80/20) и минимизировать риск пропуска редких, но критичных маршрутов.
1. Термин: Test coverage для агента и его отличие от code coverage
Test coverage в классической разработке — это процент кода (строк, ветвей, условий), который выполняется во время тестов. Для LLM-агента этот подход не работает.
| Аспект | Code coverage | Trajectory / State coverage |
|---|---|---|
| Объект измерения | Код (функции, классы) | Поведение агента (шаги, решения) |
| Детерминизм | Высокий (один и тот же вход → один выход) | Низкий (LLM может выбирать разные действия) |
| Что тестируется | Пути исполнения кода | Последовательности действий и переходы состояний |
| Инструменты | pytest --cov, JaCoCo | Логи траекторий, симуляторы, краудсорсинг |
Агент здесь — это программа, которая с помощью LLM принимает решения: выбирает инструменты, формирует запросы, обрабатывает ответы. Его поведение нельзя описать конечным числом путей в коде, поэтому нужна новая метрика.
2. Почему code coverage не работает для агентов
- LLM как «чёрный ящик» – генерация текста зависит от вероятностного распределения, а не от ветвлений кода. Покрытие 100% строк Python-прокладки (функции вызова API) не гарантирует, что протестированы все сценарии поведения.
- Бесконечное пространство действий – агент может комбинировать инструменты, генерировать произвольные запросы, реагировать на ошибочные ответы. Код агента (оркестратор) обычно мал, а вариативность поведения огромна.
- Неявные состояния – агент накапливает историю диалога, память, результаты вызовов. Code coverage не видит, что, скажем, после третьего вызова API память переполнена.
- Редкие, но критичные пути – типичный баг агента: «после двух неудачных попыток поиска начать бесконечно крутиться в цикле». Код покрыт, но траектория не протестирована.
Таким образом, coverage|test coverage агента — это мера проверки сценариев, а не строк.
3. Trajectory coverage (покрытие траекторий)
Траектория — это последовательность шагов агента от начального запроса пользователя до финального ответа. Например:
Запрос → инструмент_1 → парсинг ответа → условие_1 → инструмент_2 → финал.
Trajectory coverage (покрытие траекторий]]) = доля протестированных уникальных последовательностей шагов среди всех возможных (или ожидаемых) траекторий.
Пример:
| Траектория | Шаги | Протестирована? |
|---|---|---|
| Простой поиск | search → answer | Да |
| Поиск с уточнением | search → parse → search → answer | Да |
| Ошибка инструмента | search → tool_error → retry → search → answer | Нет |
| Цикл при отсутствии данных | search → empty → search → empty → ... → timeout | Нет |
Если из 4 возможных траекторий протестированы 2, то trajectory coverage = 50%. Критично, что непротестированная траектория «цикл» может привести к бесконечному выполнению в продакшене.
Метрика похожа на branch coverage в коде, но ветви здесь — не if-else, а решения LLM (какой инструмент выбрать, как интерпретировать результат).
4. State coverage (покрытие состояний)
Состояние агента — это контекст, в котором он находится в данный момент: содержимое памяти, история диалога, текущая сессия, загруженные документы, параметры вызовов.
State coverage — доля протестированных уникальных комбинаций состояний (или классов состояний) среди всех возможных.
Примеры состояний:
0 запросов(пустая память)- 1 успешный запрос, память < 80%
- 2 неудачных запроса, память > 80%
- ошибка аутентификации, необходимо перелогиниться
Каждое состояние может влиять на поведение агента. Если протестированы только состояния с малой памятью, то в продакшене при переполнении памяти может возникнуть неожиданное поведение.
Как измерять
Задать список критических состояний (например, через state space — пространство состояний) и проверять, какие из них встретились в тестах.
5. Instrumentation и логирование траекторий
Чтобы измерить coverage, нужно собирать данные о реальном поведении агента в тестах и продакшене. Для этого применяют instrumentation (инструментирование) — добавление логирования в ключевые точки: вызов инструмента, обработка результата, решение LLM.
Пример логируемого события (JSON):
{
"session_id": "abc123",
"step": 3,
"action": "search",
"input": "температура в Москве",
"result": {"status": "success"},
"memory_usage": 0.45,
"decision": "proceed_to_answer"
}
После сбора логов траектории кластеризуются (например, с помощью sequence matching или Levenshtein distance по списку действий). Редкие траектории (низкая частота) считаются «непокрытыми» и добавляются в план тестирования.
Инструменты
- Специализированные библиотеки для логирования агентов (LangSmith, Weights & Biases, custom logging)
- Анализаторы логов (ELK, Grafana + Prometheus для агрегации)
6. Цель покрытия — принцип Парето (80/20)
Не нужно стремиться к 100% coverage (как в коде). Цель — покрыть все часто встречающиеся траектории и состояния. Обычно 80% сессий пользователей проходят по 20% траекторий. Эти 20% должны быть полностью протестированы.
Остальные 80% траекторий — редкие. Их нужно проанализировать и протестировать только те, которые ведут к критическим ошибкам (потеря данных, бесконечный цикл, раскрытие конфиденциальной информации).
Процесс
- Собрать логи из продакшена или симуляций.
- Подсчитать частоту каждой траектории (топ-N).
- Для топ-N траекторий написать автоматизированные тесты.
- Для редких траекторий провести ручной анализ рисков и, если риск высок, добавить тесты.
- Периодически обновлять метрику по мере изменения агента (fine-tuning, смена инструментов).
7. Пример тестирования траекторий на практике
Допустим, агент для ответов на вопросы по документации компании:
Возможные траектории (фрагмент):
- user_query → search → answer (простой вопрос, документ найден)
- user_query → search → no_result → reformulate → search → answer (переформулировка)
- user_query → search → no_result → reformulate → search → no_result → fallback: «извините, не знаю»
- user_query → search → too_many_results → ask_clarification → user_input → search → answer
Тест для траектории №3
def test_no_result_fallback():
agent = Agent()
# Мокаем vector search так, чтобы он всегда возвращал пустой результат
with patch('agent.vector_search', return_value=[]):
response = agent.process("уникальный запрос без документа")
assert "извините" in response.lower()
Если такой тест не написан, trajectory coverage ниже. При изменении LLM (например, fine-tune на новую модель) может измениться решение: агент начнёт бесконечно переформулировать. Тест поймает регрессию.
8. Состояния и memory coverage
Кроме траекторий, важно тестировать состояния памяти и сессии:
- Empty memory — начало диалога.
- Partial memory — до 50% лимита токенов.
- Near full memory — > 90%, агент должен сжимать или обрезать историю.
- Error state — предыдущий вызов инструмента вернул ошибку.
Таблица test cases для состояний
| Состояние | Описание | Тестовый сценарий |
|---|---|---|
memory_full | Память заполнена на 100% | Создать 100 последовательных запросов, проверить, что агент не падает |
tool_error | Инструмент вернул HTTP 500 | Проверить, что агент делает retry (или graceful shutdown) |
timeout | Инструмент не отвечает 30 с | Проверить timeout и логирование |
Пет-проект для закрепления
Задача Разработать простого агента для извлечения информации из новостных RSS и написать систему измерения test coverage его траекторий.
Инструменты Python, OpenAI API (или локальная LLM), pandas, pytest, библиотека для RSS (feedparser), JSON-логирование.
Шаги:
- Реализовать агента с двумя инструментами:
fetch_rss(url)иsummarize(text). Агент получает запрос типа «найди последние новости про ИИ и кратко перескажи». - Определить 4–6 возможных траекторий: успешный fetch + успешный summarize, ошибка fetch (недоступен источник), ошибка summarize (текст слишком длинный), пустой RSS и т.д.
- Написать инструментирование: логировать каждый шаг в JSON-файл.
- Создать тестовый сценарий и написать pytest-тесты для каждой траектории, используя моки (unittest.mock) для имитации ответов.
- Написать скрипт, который читает логи тестов и подсчитывает trajectory coverage: число уникальных протестированных траекторий / общее число определённых траекторий.
- Дополнительно: ввести state coverage — проверить, что протестированы состояния
empty_memory,after_error,summary_too_long.
Ожидаемый результат
- Агент, который легко мокается и логируется.
- Отчёт о coverage (например: Trajectory coverage: 83% (5 из 6), State coverage: 75% (3 из 4)).
- Чёткое понимание, какую траекторию нужно дотестировать.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 790 | Что такое агент в контексте LLM и RAG? |
| 791 | Как проектировать траектории агента? |
| 793 | Как мониторить агентов в продакшене? |
| 795 | Как организовать регрессионное тестирование агентов? |
| 800 | Какие метрики качества для агента существуют? |
Навигация
- Предыдущий: 793
- Следующий: 795
- Индекс: 00. Индекс разборов