English translation is not available yet. Showing Russian content.

Как автоматизировать test generation для агента?

Краткий тезис

Автоматизация генерации тестов для агента — ключевая практика для обеспечения надёжности и корректности поведений в условиях недетерминированных LLM-вызовов. Основные подходы включают: сбор реальных запросов из production-логов, LLM-генерацию сценариев на основе шаблонов, фаззинг (fuzzing), property-based тестирование и coverage-guided генерацию. Каждый метод решает свою задачу, а комбинирование их даёт максимальное покрытие краевых случаев.


1. Термин: Test Generation для AI-агента

Test generation — это процесс автоматического создания тестовых сценариев (входных данных, ожидаемых действий, эталонных ответов) для проверки поведения агента. Агент (в контексте Agentic RAG) — это программа, которая использует LLM для принятия решений, вызова инструментов (поиск, API, базы данных) и возврата результата. В отличие от обычного RAG, тестирование агента усложняется тем, что один запрос может порождать несколько шагов (chain-of-thought, вызовы функций). Автоматизированная генерация тестов позволяет:

  • сократить ручной труд по написанию сотен тестов;
  • увеличить покрытие редких сценариев (edge cases);
  • обновлять тесты при изменении домена данных или версии LLM;
  • выявить регрессии после дообучения или смены модели.

Термин «автоматизация» в данном контексте означает замену ручного создания тестов на пайплайн, который извлекает, мутирует или синтезирует данные.


2. Зачем автоматизировать генерацию тестов для агента?

Без автоматизации тестирование агента обычно сводится к небольшому набору «золотых» запросов, написанных разработчиком. Этого недостаточно, потому что:

  • Недетерминизм LLM: одна и та же промпт-темплейта может привести к разным траекториям вызовов инструментов. Нужно много примеров, чтобы поймать нестабильность.
  • Эволюция базы знаний: документы в RAG обновляются, тесты на старых запросах могут стать невалидными.
  • Краевые случаи (edge cases): редкие аномалии вроде очень длинного запроса или нескольких туров диалога трудно придумать вручную.
  • Масштабирование: если агент поддерживает десятки инструментов, ручное тестирование каждого комбинированного вызова становится нереалистичным.

Автоматизация даёт масштабируемое и воспроизводимое покрытие.


3. Метод 1: Извлечение тестов из production-логов

Идея: собрать реальные запросы пользователей (обезличенные) и использовать их как входные данные тестов. Ожидаемые действия можно получить путём логирования траектории агента в production (при условии, что агент выдал корректный ответ) или ручной разметки небольшого подмножества.

Шаги:

  1. Сбор логов: сохранять запрос, цепочку вызовов инструментов, финальный ответ, метаданные (время, модель, версия агента).
  2. Обезличивание (anonymization): удалить персональные данные (PII), заменить их на плейсхолдеры.
  3. Фильтрация: отбросить запросы, на которые агент ответил некорректно (например, по метрикам faithfulness).
  4. Автоматическое определение ожидаемого поведения: если production-ответ признан верным (через согласование с LLM-оценкой или ручным ревью), то для теста закрепляется траектория как expected action sequence.

Преимущества:

  • Максимальная репрезентативность — тесты отражают реальное распределение запросов.
  • Автоматическое поддержание актуальности: можно настроить еженедельный пайплайн, который пополняет тестовый набор.

Недостатки:

  • Зависимость от production: в начале проекта логов может не быть.
  • Требуется механизм определения «правильности» ответа.
  • Шум: некоторые production-ответы могут быть ошибочными.

Инструменты: LangSmith, Weights & Biases, собственный дата-пайплайн на Spark.


4. Метод 2: LLM-генерация на основе шаблонов

Идея: использовать LLM для создания синтетических запросов по заданному шаблону. Это позволяет покрыть ниши, которые редко встречаются в логах.

Шаги:

  1. Определить шаблоны сценариев (например: «запрос с датой в прошлом», «запрос опечатка», «запрос на иностранном языке»).
  2. Написать seed-промпт для LLM: «Сгенерируй 10 вариантов запроса пользователя к системе, которая ищет информацию о медицинских препаратах. Варианты должны включать синонимы, числовые диапазоны, опечатки.»
  3. Полученные запросы прогнать через агент и зафиксировать траекторию. Для проверки корректности можно использовать LLM-as-a-judge — другую LLM, которая оценивает, правильно ли агент обработал синтетический запрос.

Пример промпта (упрощённый):

Сгенерируй 5 разнообразных пользовательских запросов к агенту, который помогает планировать путешествия.
Запросы должны включать:
- один с несколькими городами
- один с конкретными датами
- один с опечаткой в названии города
- один на английском
Формат: каждый запрос на отдельной строке.

Преимущества:

  • Быстрое создание большого объёма тестов.
  • Легко контролировать разнообразие через параметры промпта.

Недостатки:

  • Риск генерации нереалистичных запросов (LLM может выдумать то, чего нет).
  • Требуется верификация каждого теста (чтобы не было ложноположительных).

Инструменты: OpenAI API (gpt-4), LangChain, LLM-as-a-judge (например, через RAGAS или собственный калибратор).


5. Метод 3: Fuzzing (фазинг)

Идея: автоматическая мутация существующих тестовых запросов — замена символов, вставка специальных символов (null, escape sequences), изменение длины строки, добавление очень длинных последовательностей.

Цель: проверить устойчивость агента к некорректным или аномальным входам.

Примеры мутаций:

  • Исходный запрос: «Найди документ о квантовых вычислениях»
  • Мутированный: «Найди\u0000документ» (вставка нуль-символа)
  • Мутированный: «a» * 10000 (очень длинная строка)
  • Мутированный: «SELECT * FROM documents; — нашел?» (SQL-инъекция)

Инструменты: специальные либы для фаззинга: Hypothesis (Python) с custom strategy, Atheris (Google) для Python, Burp Suite (если агент доступен через API).

Преимущества:

  • Эффективен для поиска уязвимостей (prompt injection, краши).
  • Минимальные затраты на подготовку — нужно только начальное множество запросов.

Недостатки:

  • Очень много шума, высокая доля тестов, которые просто не имеют смысла (но это feature, а не баг).
  • Требуется автоматическое определение «аномалии» (аварийное завершение, ответ с угрозами и т.п.).

6. Метод 4: Property-based тестирование

Идея: описать свойства (properties), которым должен удовлетворять ответ агента для любых сгенерированных входных данных. Например: «для любого запроса из domain X ответ должен содержать ссылку на хотя бы один документ». Затем библиотека генерирует случайные данные, удовлетворяющие условиям, и проверяет свойства.

Пример (используем библиотеку Hypothesis):

from hypothesis import given, strategies as st
import requests

@given(
    query=st.text(min_size=1, max_size=200),
    max_results=st.integers(min_value=1, max_value=10)
)
def test_agent_returns_expected_keys(query, max_results):
    response = requests.post("http://agent/api/query", json={
        "query": query,
        "max_results": max_results
    })
    assert response.status_code == 200
    data = response.json()
    # Свойство 1: ответ содержит поле 'action_sequence'
    assert 'action_sequence' in data
    # Свойство 2: количество результатов не превышает max_results
    assert len(data.get('documents', [])) <= max_results

Property-based testing особенно полезен для проверки контрактов API и отсутствия исключений.

Преимущества:

  • Покрывает огромное пространство входов без ручного перечисления.
  • Обнаруживает регрессии в логике ветвления агента.

Недостатки:

  • Сложно описать свойства для семантически корректного поведения агента (например, «ответ должен быть осмысленным»).
  • Требует высокой производительности, чтобы за разумное время прогнать сотни случайных запросов.

Инструменты: Hypothesis, QuickCheck (Haskell), ScalaCheck.


7. Метод 5: Coverage-guided генерация (покрытие траекторий)

Идея: вместо случайной мутации направлять генерацию на те траектории (последовательности вызовов инструментов), которые ещё не были покрыты существующими тестами. Это аналог coverage-guided fuzzing в софтвере, но применённый к последовательностям действий агента.

Реализация:

  1. Каждый тестовый запуск агента логирует траекторию: например, [search, extract, llm_generate] или [search -> no_results -> ask_clarification].
  2. Построить граф покрытых траекторий. Счётчики посещений каждого узла (instrument call) и переходов.
  3. Генератор запросов (LLM или мутатор) получает задачу: «создай запрос, который приведёт к непокрытой траектории, включающей инструмент X с ошибкой Y».
  4. Тестовый запуск, обновление покрытия, повтор.

Пример метрики покрытия: State Coverage — доля уникальных состояний агента (набор параметров инвокаций) из всех возможных.

Преимущества:

  • Гарантирует, что редко используемые инструменты и ошибки будут протестированы.
  • Снижает дублирование тестов.

Недостатки:

  • Сложность построения графа состояний для агента с памятью.
  • Высокая вычислительная стоимость.

Инструменты: можно реализовать на базе LangSmith с кастомной метрикой, или использовать фреймворки для coverage-guided fuzzing (AFL, libFuzzer) в связке с эмуляцией агента.


8. Сравнительная таблица методов

МетодИсточник данныхОсновное достоинствоОсновной недостатокКогда применять
Production-логиРеальные пользователиРепрезентативностьНет логов в началеВ зрелом проекте
LLM-генерацияСинтетикаБыстрое масштабированиеМожет быть нереалистичнойНа старте, для разнообразия
FuzzingМутацияПоиск уязвимостейМного шумаНа этапе stress-test
Property-basedСлучайные данныеПроверка контрактовСемантические свойства сложныДля API/инструментов
Coverage-guidedПокрытие траекторийПолнота покрытияДорогоДля mission-critical агентов

9. Пайплайн автоматической генерации тестов (практические шаги)

  1. Начальная загрузка: собрать 100–200 production-запросов или вручную написать seed-тесты.
  2. Пополнение через LLM: используя few-shot промпты, генерировать варианты по категориям (опечатки, многословные, с отрицанием).
  3. Property-based проверка: прогонять все сгенерированные запросы через агента, проверять базовые свойства (наличие action_sequence, code 200, no exceptions).
  4. Coverage-guided отбор: после каждого прогона обновлять покрытие и давать генератору команду на создание запросов для непокрытых траекторий.
  5. Валидация: для части тестов использовать LLM-judge для оценки корректности ответа. Отбрасывать тесты, где ответ признан ошибочным.
  6. Хранение и версионирование: тесты хранить в репозитории (JSON/ YAML) вместе с ожидаемой траекторией. Обновлять раз в спринт.

10. Метрики для оценки тестового покрытия

  • Запросное покрытие (query coverage): доля всех логических типов запросов (по шаблону) от target-списка.
  • Инструментное покрытие (instrument coverage): доля вызовов инструментов, которые встречаются хотя бы в одном тесте, от всех возможных.
  • Ошибочное покрытие (error coverage): доля обработанных типов ошибок (timeout, exception, empty result).
  • Мутационное покрытие (mutation testing score): устойчивость тестов к мутациям кода агента (замена модели, изменение промпта).

Пет-проект для закрепления

Задача: реализовать пайплайн автоматической генерации тестов для агента, который отвечает на вопросы по библиотеке Python (например, requests). Агент использует инструменты: search_docs, execute_code (песочница), get_error_info.

Инструменты: Python, LangChain (Agent), Hypothesis, FastAPI (для обёртки агента), SQLite (хранение логов).

Шаги:

  1. Реализовать простого агента на LangChain с двумя инструментами: search_docs (поиск по doc-тексту) и execute_code (запуск кода в Docker).
  2. Собрать 50 реальных вопросов из StackOverflow по библиотеке requests (production-логи).
  3. Написать скрипт на Hypothesis, который генерирует 100 случайных запросов: смесь опечаток, пустых строк, длинных строк.
  4. Каждый прогон сохранять в SQLite: запрос, траектория (list of called tools), ошибка (если была).
  5. После прогона 100 тестов построить отчёт: coverage траекторий, доля ошибок.
  6. Добавить шаг: если какая-то траектория не покрыта (например, execute_code после get_error_info), то сгенерировать 10 дополнительных запросов через LLM, направленных на эту траекторию.

Ожидаемый результат: вы получите воспроизводимый тестовый набор, который можно запускать при изменении агента (новая версия LLM, другой ретривер). Покрытие траекторий должно вырасти с ~40% до >80%.


Связь с другими вопросами

ВопросТема
785Как вы оцениваете качество AI-агента?
790Как вы тестируете цепочки вызовов (tool calls) агента?
792Что такое prompt injection и как от него защищаться?
800Как вы отлаживаете поведение агента production?
776Какие метрики качества вы используете для RAG? (базовые)
801Как управлять конфигурацией агента (версии моделей, инструментов)?

Навигация