Реализовать synthetic eval для агента

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Реализовать synthetic eval для агента

1. Цель задачи

Разработать пайплайн генерации синтетических тестовых наборов (eval set) для оценки AI-агента. Пайплайн должен автоматически создавать 500 разнообразных запросов пользователя и соответствующих ожидаемых траекторий (последовательность вызовов инструментов/действий агента) с полной валидацией — за время не более 10 минут. Ключевой результат готовая, воспроизводимая система генерации тестов, которую можно интегрировать в CI/CD процесс оценки агента.

2. Исходные данные

Что нужноОткуда взять
Спецификация агента (домен, доступные инструменты, их описание, сигнатуры)Предоставляется заказчиком (реальный проект). В демо-версии используем открытую спецификацию например, toolhouse-ai/weather или langchain/tools.
Примеры реальных диалогов с агентом (seed-примеры)Предоставляются заказчиком или генерируются вручную (5–10 примеров)
Шаблоны сценариев (domains: поддержка, бронирование, информация и т.п.)Разрабатываются на этапе анализа
Критерии валидности траектории (что значит «ожидаемая»? как проверить?)Описаны в спецификации агента (например, последовательность вызовов, поля ответа)

Если нет реального инструмента — симулируем:

  1. Создаём файл agent_spec.yaml с фиктивными инструментами: get_weather(city), book_flight(from, to, date), get_hotel_info(hotel_id).
  2. Вручную записываем 3 seed-примера: {"user": "Какая погода в Москве?", "expected_trace": [{"tool": "get_weather", "args": {"city": "Москва"}}]}.
  3. Определяем 2–3 домена: weather, travel, general_knowledge.

3. Технологический стек

КомпонентИнструментыНазначение
ЯзыкPython 3.11+Основной язык разработки
Генерация текстаOpenAI API (gpt-4o-mini) или локальная LLM (vLLM + Llama 3.1 8B)Синтез запросов и траекторий
Асинхронностьasyncio, aiohttpПараллельная генерация для скорости
Структуры данныхpydantic (v2), jsonВалидация схемы теста
Промпт-инжинирингlangchain (ChatPromptTemplate, OutputParser)Шаблонизация и разбор ответа LLM
Мониторингtqdm, loguruПрогресс и логирование
Хранилищеpandas + parquetСохранение тестов

4. Этапы выполнения

Этап 1: Проектирование схемы теста и валидация (45 минут)

Действия

  1. Определить структуру одного теста (Pydantic-модель):
    from pydantic import BaseModel, Field
    from typing import List, Dict, Any
    
    class ToolCall(BaseModel):
        tool: str
        args: Dict[str, Any]
    
    class TestCase(BaseModel):
        user_query: str = Field(..., min_length=1)
        expected_trace: List[ToolCall]
        domain: str
        difficulty: str  # easy, medium, hard
        metadata: Dict[str, Any] = {}
    
  2. Создать функцию validate_test_case(case: TestCase) -> bool, проверяющую:
    • Каждый ToolCall.tool есть в списке доступных инструментов.
    • Аргументы корректны по типу (например, city — строка, date — в формате ISO).
    • Траектория не пустая и соответствует ожидаемой логике (например, для запроса "Погода в Москве" не может быть вызова book_flight).
  3. Подготовить папку data/ для хранения seed-примеров (seed.jsonl) и конфигурации (agent_spec.yaml).

Ожидаемый результат этапа Модель TestCase, валидатор, файл agent_spec.yaml с 3–5 инструментами.

Этап 2: Разработка генератора запросов и траекторий (1.5 часа)

Действия

  1. Написать промпт для LLM (используя langchain):
    Ты генератор тестов для агента с инструментами: {tools_list}.
    Сгенерируй {n} тестовых случаев для домена {domain}.
    Каждый случай: вопрос пользователя (на русском) и ожидаемая траектория (набор вызовов инструментов с аргументами).
    Формат: JSON list объектов с полями: user_query, expected_trace (list of {"tool": ..., "args": {...}}), difficulty.
    Используй разнообразие: простые, средние, сложные запросы.
    Учитывай seed-примеры: {seed_examples}
    
  2. Реализовать OutputParser, который извлекает JSON из ответа LLM и преобразует в список TestCase.
  3. Написать асинхронную функцию generate_batch(domain, n, tools) -> List[TestCase] с повторными попытками при ошибках парсинга (max 3).
  4. Интегрировать validate_test_case в пайплайн: невалидные тесты отбрасываются и повторно запрашиваются.

Ожидаемый результат этапа Функция generate_test_cases(n=10), которая возвращает 10 валидных тестов за <30 секунд.

Этап 3: Масштабирование до 500 тестов (1 час)

Действия

  1. Разделить генерацию на параллельные таски по доменам (например, по 5 доменов × 100 тестов каждый) с использованием asyncio.gather.
  2. Ограничить параллелизм запросов к LLM (например, semaphore = 10), чтобы не превышать лимиты API.
  3. Добавить дедупликацию: сравнение user_query через difflib.SequenceMatcher (порог 0.85) — дубли переносятся в резервный пул.
  4. Реализовать прогресс-бар и логирование каждого этапа:
    [13:42:15] GEN | домен weather: сгенерировано 83, отсеяно 2, дублей 5 — всего осталось 76
    
  5. Замерить время генерации 500 тестов (цель <10 мин). Если медленнее — оптимизировать:
    • Использовать более быструю модель (GPT-4o-mini → GPT-4o-mini-fine-tuned).
    • Увеличить n в одном вызове LLM (генерировать сразу 10–20 тестов за запрос).

Ожидаемый результат этапа Скрипт generate_eval_set.py с параметром --count 500, выводящий файл eval_set.jsonl за ≤10 минут.

Этап 4: Постобработка и анализ (30 минут)

Действия

  1. Рассчитать метрики качества:
    • Соотношение доменов (должно быть равномерным ±10%).
    • Распределение сложности (easy 40%, medium 40%, hard 20%).
    • Количество уникальных траекторий (разнообразие).
  2. Визуализировать распределение с помощью matplotlib или seaborn (гистограмма, круговая диаграмма).
  3. Экспортировать выборку из 10 случайных тестов для ручной верификации в sample_report.md.

Ожидаемый результат этапа Файл eval_set_analysis.html с графиками и статистикой.

Этап 5: Интеграция и документация (30 минут)

Действия

  1. Упаковать пайплайн в CLI-инструмент с argparse:
    python synth_eval.py --spec agent_spec.yaml --seed seed.jsonl --count 500 --output eval_set.parquet
    
  2. Написать README.md с примерами использования.
  3. Добавить unit-тесты для функций валидации и парсинга (pytest).

Ожидаемый результат этапа Репозиторий с кодом, тестами, документацией и сгенерированным набором.

5. Критерии приемки (Definition of Done)

  • Пайплайн генерирует ровно 500 тестов (или ближайшее меньшее целое) за ≤600 секунд.
  • Каждый тест содержит валидную Pydantic-модель.
  • Все имена инструментов в траектории присутствуют в agent_spec.yaml.
  • Запросы распределены как минимум по 3 доменам, ни один домен не превышает 60% всех тестов.
  • Дубликатов запросов (по тексту) не более 5%.
  • Результат сохраняется в формате JSONL (или Parquet) с возможностью чтения pd.read_json(..., lines=True).
  • Команда python synth_eval.py --help выводит справку.
  • Unit-тесты с покрытием >80% для модулей валидации и генерации.

6. Ожидаемый результат

Главный артефакт — файл eval_set.jsonl (или .parquet), содержащий 500 записей вида:

{
  "user_query": "Забронируй отель Hilton в Париже на 20 декабря",
  "expected_trace": [
    {"tool": "get_hotel_info", "args": {"hotel_id": "Hilton-Paris"}},
    {"tool": "book_hotel", "args": {"hotel_id": "Hilton-Paris", "date": "2025-12-20"}}
  ],
  "domain": "travel",
  "difficulty": "medium",
  "metadata": {"generated_by": "gpt-4o-mini", "seed_used": "example_3"}
}

Дополнительно:

  • Папка analysis/ с отчётом report.html, гистограммами.
  • README с описанием пайплайна.
  • Юнит-тесты (pytest).

7. Возможные сложности и их решение

СложностьРешение
LLM генерирует много невалидных траекторий (аргументы не в тех типах, несуществующие инструменты)Улучшить промпт: дать JSON-схему в few-shot. Использовать функцию function_calling API (OpenAI tools) для принудительной генерации корректных вызовов.
Генерация занимает больше 10 минут из-за rate-limitУвеличить параллелизм через несколько API-ключей (round-robin). Использовать локальную модель (vLLM, llama.cpp) для полного контроля.
Домены распределены неравномерноВвести балансировщик: после первого прохода добавить генерацию для доменов, где тестов меньше целевого.
Дубликаты запросовИспользовать набор стоп-слов и хэши с LSH (MinHash) или просто хранить все тексты и проверять на схожесть.
LLM "галлюцинирует" нереалистичные сценарииВключить валидацию на уровне бизнес-правил (например, не может быть бронирования в прошлом).

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Проектирование и валидация45 мин
Этап 2: Разработка генератора1 ч 30 мин
Этап 3: Масштабирование до 5001 ч
Этап 4: Постобработка и анализ30 мин
Этап 5: Интеграция и документация30 мин
Итого4 ч 15 мин

Примечание Для первого выполнения (без готовых шаблонов) заложите +1 час на отладку промптов и API. При наличии seed-примеров и конфига время сокращается до 3 часов.

9. Связанные вопросы из базы знаний

ВопросТема
#13Синтетическая генерация данных
#27Оценка качества RAG (eval sets)
#45Пайплайны промпт-инжиниринга
#102Асинхронные запросы к LLM API
#150Pydantic и валидация схем
#203Дедупликация текстовых данных
#255Мониторинг и метрики генерации
#310CI/CD для ML-моделей (тестирование)
#418Функция вызова инструментов (Function Calling)
#567Распределённая генерация с asyncio

10. Чек-лист самопроверки

  • Я убедился, что agent_spec.yaml содержит все необходимые инструменты и их описание.
  • Я проверил, что промпт для LLM включает четкий JSON-формат и примеры.
  • Я протестировал генерацию для одного домена (n=10) и убедился, что все тесты проходят валидацию.
  • Я замерил время генерации 500 тестов и получил <10 минут.
  • Я проанализировал распределение доменов, сложности и уникальность запросов.
  • Я задокументировал команду запуска и возможные параметры.
  • Я добавил юнит-тесты для модулей валидации и парсинга (минимум 3 функции).
  • Я сохранил финальный eval-набор в репозиторий (или указал путь к нему).