Реализовать synthetic eval для агента
ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Реализовать synthetic eval для агента
1. Цель задачи
Разработать пайплайн генерации синтетических тестовых наборов (eval set) для оценки AI-агента. Пайплайн должен автоматически создавать 500 разнообразных запросов пользователя и соответствующих ожидаемых траекторий (последовательность вызовов инструментов/действий агента) с полной валидацией — за время не более 10 минут. Ключевой результат готовая, воспроизводимая система генерации тестов, которую можно интегрировать в CI/CD процесс оценки агента.
2. Исходные данные
| Что нужно | Откуда взять |
|---|---|
| Спецификация агента (домен, доступные инструменты, их описание, сигнатуры) | Предоставляется заказчиком (реальный проект). В демо-версии используем открытую спецификацию например, toolhouse-ai/weather или langchain/tools. |
| Примеры реальных диалогов с агентом (seed-примеры) | Предоставляются заказчиком или генерируются вручную (5–10 примеров) |
| Шаблоны сценариев (domains: поддержка, бронирование, информация и т.п.) | Разрабатываются на этапе анализа |
| Критерии валидности траектории (что значит «ожидаемая»? как проверить?) | Описаны в спецификации агента (например, последовательность вызовов, поля ответа) |
Если нет реального инструмента — симулируем:
- Создаём файл agent_spec.yaml с фиктивными инструментами:
get_weather(city),book_flight(from, to, date),get_hotel_info(hotel_id). - Вручную записываем 3 seed-примера: {"user": "Какая погода в Москве?", "expected_trace": [{"tool": "get_weather", "args": {"city": "Москва"}}]}.
- Определяем 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 минут)
Действия
- Определить структуру одного теста (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] = {} - Создать функцию
validate_test_case(case: TestCase) -> bool, проверяющую:- Каждый ToolCall.tool есть в списке доступных инструментов.
- Аргументы корректны по типу (например,
city— строка,date— в формате ISO). - Траектория не пустая и соответствует ожидаемой логике (например, для запроса "Погода в Москве" не может быть вызова
book_flight).
- Подготовить папку
data/для хранения seed-примеров (seed.jsonl) и конфигурации (agent_spec.yaml).
Ожидаемый результат этапа Модель TestCase, валидатор, файл agent_spec.yaml с 3–5 инструментами.
Этап 2: Разработка генератора запросов и траекторий (1.5 часа)
Действия
- Написать промпт для LLM (используя langchain):
Ты генератор тестов для агента с инструментами: {tools_list}. Сгенерируй {n} тестовых случаев для домена {domain}. Каждый случай: вопрос пользователя (на русском) и ожидаемая траектория (набор вызовов инструментов с аргументами). Формат: JSON list объектов с полями: user_query, expected_trace (list of {"tool": ..., "args": {...}}), difficulty. Используй разнообразие: простые, средние, сложные запросы. Учитывай seed-примеры: {seed_examples} - Реализовать OutputParser, который извлекает JSON из ответа LLM и преобразует в список
TestCase. - Написать асинхронную функцию generate_batch(domain, n, tools) -> List[TestCase] с повторными попытками при ошибках парсинга (max 3).
- Интегрировать
validate_test_caseв пайплайн: невалидные тесты отбрасываются и повторно запрашиваются.
Ожидаемый результат этапа Функция generate_test_cases(n=10), которая возвращает 10 валидных тестов за <30 секунд.
Этап 3: Масштабирование до 500 тестов (1 час)
Действия
- Разделить генерацию на параллельные таски по доменам (например, по 5 доменов × 100 тестов каждый) с использованием asyncio.gather.
- Ограничить параллелизм запросов к LLM (например, semaphore = 10), чтобы не превышать лимиты API.
- Добавить дедупликацию: сравнение
user_queryчерез difflib.SequenceMatcher (порог 0.85) — дубли переносятся в резервный пул. - Реализовать прогресс-бар и логирование каждого этапа:
[13:42:15] GEN | домен weather: сгенерировано 83, отсеяно 2, дублей 5 — всего осталось 76 - Замерить время генерации 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 минут)
Действия
- Рассчитать метрики качества:
- Соотношение доменов (должно быть равномерным ±10%).
- Распределение сложности (easy 40%, medium 40%, hard 20%).
- Количество уникальных траекторий (разнообразие).
- Визуализировать распределение с помощью matplotlib или seaborn (гистограмма, круговая диаграмма).
- Экспортировать выборку из 10 случайных тестов для ручной верификации в
sample_report.md.
Ожидаемый результат этапа Файл eval_set_analysis.html с графиками и статистикой.
Этап 5: Интеграция и документация (30 минут)
Действия
- Упаковать пайплайн в CLI-инструмент с argparse:
python synth_eval.py --spec agent_spec.yaml --seed seed.jsonl --count 500 --output eval_set.parquet - Написать README.md с примерами использования.
- Добавить 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: Масштабирование до 500 | 1 ч |
| Этап 4: Постобработка и анализ | 30 мин |
| Этап 5: Интеграция и документация | 30 мин |
| Итого | 4 ч 15 мин |
Примечание Для первого выполнения (без готовых шаблонов) заложите +1 час на отладку промптов и API. При наличии seed-примеров и конфига время сокращается до 3 часов.
9. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| #13 | Синтетическая генерация данных |
| #27 | Оценка качества RAG (eval sets) |
| #45 | Пайплайны промпт-инжиниринга |
| #102 | Асинхронные запросы к LLM API |
| #150 | Pydantic и валидация схем |
| #203 | Дедупликация текстовых данных |
| #255 | Мониторинг и метрики генерации |
| #310 | CI/CD для ML-моделей (тестирование) |
| #418 | Функция вызова инструментов (Function Calling) |
| #567 | Распределённая генерация с asyncio |
10. Чек-лист самопроверки
- Я убедился, что
agent_spec.yamlсодержит все необходимые инструменты и их описание. - Я проверил, что промпт для LLM включает четкий JSON-формат и примеры.
- Я протестировал генерацию для одного домена (n=10) и убедился, что все тесты проходят валидацию.
- Я замерил время генерации 500 тестов и получил <10 минут.
- Я проанализировал распределение доменов, сложности и уникальность запросов.
- Я задокументировал команду запуска и возможные параметры.
- Я добавил юнит-тесты для модулей валидации и парсинга (минимум 3 функции).
- Я сохранил финальный eval-набор в репозиторий (или указал путь к нему).