中文翻译暂不可用,显示俄语原文。

Реализовать test generation для агента

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

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

Научиться автоматизировать генерацию тестовых запросов (prompts) для системного тестирования AI-агента с помощью LLM. На основе заранее определённых шаблонов LLM должен генерировать валидные, разнообразные и покрывающие краевые случаи запросы. Ключевой результат рабочий пайплайн, который генерирует 500 уникальных тестовых запросов за 5 минут и может быть интегрирован в CI/CD.


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

Перед началом необходимо иметь:

Что нужноОткуда взять
Описание тестируемого AI-агента (назначение, инструменты, ожидаемое поведение)Документация проекта или открытый аналог (например, LangChain Agent)
Шаблоны тестовых запросов (типы, слоты, ожидаемые ответы или действия)Разработать самостоятельно на основе требований к агенту
Примеры ручных тестов (5–10 штук)Предыдущие тесты, issue трекер, регрессионные кейсы
Среда для запуска агента (локально или Docker)Код агента; если нет – использовать mock-агента
LLM API ключ (OpenAI, Anthropic или локальная модель)Личный аккаунт, облачный сервис или запуск локальной модели

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

  1. Создайте простого Python-агента на базе LangChain с 2–3 инструментами (например, calculator, weather, search). Код можно взять из учебных примеров LangChain.
  2. Определите 5–7 типов запросов (например, вызов одного инструмента, комбинированный запрос, запрос без инструмента, запрос с неверными данными).
  3. Зафиксируйте ожидаемый формат ответа (строковый или JSON) для каждого типа.

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

КомпонентИнструментыНазначение
LLM для генерацииGPT-4o-mini / Claude Haiku / Qwen2.5 (локально)Генерация тестовых запросов
Фреймворк для агентаLangChain / LlamaIndexСреда тестирования (целевой агент)
РазработкаPython 3.11+, PydanticОпределение шаблонов, валидация, логика
Управление промптамиLangChain PromptTemplate / Jinja2Шаблонизация запросов к LLM
Оптимизация производительностиasyncio, aiohttp, batch APIПараллельная генерация
Тестированиеpytest, pytest-asyncioПрогон сгенерированных запросов
CI/CDGitHub Actions, GitLab CIАвтоматический запуск при мерже
Версионирование данныхGit LFS или DVCХранение сгенерированных тестов (опционально)

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

Этап 1: Анализ и подготовка шаблонов (оценка: 1 час)

Действия

  1. Инвентаризация типов тестовых запросов

    • Выделите 5–7 категорий: single_tool, multi_tool, ambiguous, error_handling, context_only, out_of_scope, adversarial.
    • Для каждой категории опишите, какие слоты (переменные) должны подставляться (например, {tool_name}, {argument}, {expected_action}).
    • Запишите ожидаемое поведение агента (какой инструмент вызван, какой ответ).
  2. Создание шаблонного файла в формате YAML или JSON:

    - category: "single_tool"
      template: "Сколько будет {num1} {operator} {num2}?"
      slots:
        num1: [1, 10, 100, -5, 3.14]
        operator: ["+", "-", "*", "/"]
        expected_tool: "calculator"
      variants: 5
    - category: "error_handling"
      template: "Найди погоду в городе {city}"
      slots:
        city: ["", "None", "Нью-Йорк", "НесуществующийГород123"]
        expected_tool: "weather"
      variants: 3
    
    • Убедитесь, что количество слотов и их возможные значения покрывают краевые случаи (пустые строки, спецсимволы, очень длинные строки).
  3. Определите число генерируемых запросов:

    • Исходя из цели «500 запросов за 5 минут», распределите количество по категориям (например, 100, 100, 100, 100, 50, 50).

Ожидаемый результат этапа Файл templates.yaml (или templates.json) с минимум 5 категориями и валидными слотами.


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

Действия

  1. Реализуйте класс TestGenerator, который:

    • Читает шаблоны;
    • Для каждого шаблона итеративно комбинирует значения слотов (декартово произведение или случайная подвыборка с учётом variants);
    • Передаёт заполненный шаблон в LLM для парафразирования/вариации (чтобы запросы были разнообразными) или просто использует комбинаторику без LLM, если запросы строгие. В данной задаче используем LLM для генерации разнообразных формулировок того же интеншена.
  2. Пример кода (main.py):

    from langchain_core.prompts import PromptTemplate
    from langchain_openai import ChatOpenAI
    import asyncio
    import yaml
    from pydantic import BaseModel
    
    class Template(BaseModel):
        category: str
        template: str
        slots: dict
        variants: int
        expected_tool: str = ""
    
    class TestGenerator:
        def __init__(self, templates_path: str, llm):
            with open(templates_path) as f:
                self.templates = [Template(**t) for t in yaml.safe_load(f)]
            self.llm = llm
    
        async def generate_variant(self, template: Template, slot_values: dict) -> str:
            filled = template.template.format(**slot_values)
            # Промпт для парафразирования
            prompt = f"Сгенерируй один естественный вопрос пользователя, который означает то же самое, что и запрос ниже. Не добавляй объяснений.\n\nЗапрос: {filled}"
            response = await self.llm.agenerate([[prompt]])
            return response.generations[0][0].text.strip()
    
        async def run(self):
            tasks = []
            for tmpl in self.templates:
                # Создать комбинации слотов (упрощённо)
                import itertools
                keys = list(tmpl.slots.keys())
                combos = list(itertools.product(*[tmpl.slots[k] for k in keys]))
                # Ограничить количество комбинаций
                import random
                random.shuffle(combos)
                used = set()
                for combo in combos[:tmpl.variants]:
                    slot_dict = dict(zip(keys, combo))
                    if tuple(combo) in used:
                        continue
                    used.add(tuple(combo))
                    tasks.append(self.generate_variant(tmpl, slot_dict))
            results = await asyncio.gather(*tasks)
            return results
    
  3. Обработка дубликатов:

    • Добавьте в generate_variant проверку уникальности по эмбеддингам (через sentence-transformers и cosine similarity < 0.95) или просто по exact match.
  4. Вывод результатов:

    • Сохраните сгенерированные запросы в формате JSON Lines (test_requests.jsonl):
      {"id": 1, "query": "Сколько будет 10 плюс 3?", "expected_tool": "calculator", "category": "single_tool"}
      

Ожидаемый результат этапа Скрипт generate_tests.py, который за 1 прогон генерирует минимум 500 запросов (с учётом параллельности – 100–200 запросов к LLM, остальные – комбинаторные без LLM). Важно уложиться в 5 минут.


Этап 3: Интеграция с тестовым прогоном (оценка: 1.5 часа)

Действия

  1. Напишите pytest-фикстуру, которая загружает сгенерированные запросы и подставляет их в тест:

    import pytest
    import json
    from agent import run_agent  # ваш агент
    
    @pytest.fixture(scope="session")
    def test_requests():
        with open("test_requests.jsonl") as f:
            return [json.loads(line) for line in f]
    
    @pytest.mark.parametrize("req", test_requests())
    def test_agent_response(req):
        response = run_agent(req["query"])
        # Для простоты проверяем, что агент вызвал ожидаемый инструмент
        assert req["expected_tool"] in response.get("used_tools", []), \
            f"Query: {req['query']}. Expected tool {req['expected_tool']}, got {response.get('used_tools')}"
    
  2. Добавьте логирование времени выполнения – убедитесь, что прогон 500 тестов не превышает 10 минут (с учётом задержек API агента). Для этого можно использовать pytest-timeout.

  3. Создайте отчет (HTML или JUnit XML) для CI.

Ожидаемый результат этапа Тест pytest test_agent.py -n auto успешно запускается, покрывая 500+ кейсов, и генерируется отчёт.


Этап 4: Оптимизация производительности (оценка: 1 час)

Действия

  1. Замените последовательные вызовы LLM на batch-запросы (OpenAI поддерживает batch).

    async def batch_generate(self, prompts: list[str]) -> list[str]:
        # Использовать create_batch или parallel API
        responses = await asyncio.gather(*[self.llm.agenerate([[p]]) for p in prompts])
        return [r.generations[0][0].text.strip() for r in responses]
    
  2. Кэшируйте результаты генерации – если шаблоны не менялись, переиспользуйте прошлый файл. Используйте hashlib.md5 от содержимого templates.yaml.

  3. Измерьте время после оптимизации. Цель: 500 запросов за <5 минут (при разумном лимите API). Если не достигается – снизьте долю LLM-генерации: пусть LLM генерирует только 10–20% уникальных формулировок, остальное – комбинаторика.

Ожидаемый результат этапа В логах CI видно время генерации < 5 минут.


Этап 5: Документация и автоматизация (оценка: 1 час)

Действия

  1. Напишите README.md с описанием:
    • Как запустить генерацию: python generate_tests.py
    • Как запустить тесты: pytest test_agent.py
    • Как добавить новый шаблон.
  2. Создайте Makefile с целями generate, test, clean.
  3. Добавьте GitHub Action:
    name: Test Generation
    on: [push]
    jobs:
      test:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - name: Install dependencies
            run: pip install -r requirements.txt
          - name: Generate tests
            run: python generate_tests.py
          - name: Run agent tests
            run: pytest test_agent.py --junitxml=report.xml
    

Ожидаемый результат этапа Пайплайн в CI зелёный, генерация и прогон занимают <15 минут суммарно.


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

  • Сгенерировано 500 уникальных тестовых запросов (проверка по exact match или эмбеддингам).
  • Время генерации и сохранения в файл не превышает 5 минут (замеряется в CI).
  • Каждый запрос соответствует хотя бы одному шаблону из templates.yaml.
  • Тестовый прогон (pytest) проходит успешно (pass rate ≥ 95%).
  • Код генератора покрыт unit-тестами (минимум 2 теста: на уникальность и на корректность шаблонов).
  • Процесс документирован (README, Makefile, CI конфиг).

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

Основные артефакты

  • generate_tests.py – генератор тестов.
  • templates.yaml – конфигурация шаблонов (минимум 5 категорий).
  • test_requests.jsonl – файл со сгенерированными запросами.
  • test_agent.py – тест, параметризованный сгенерированными запросами.
  • Makefile – команды generate, test.
  • .github/workflows/test_generation.yml – CI пайплайн.

Опционально

  • requirements.txt со всеми зависимостями.
  • report.xml – отчёт pytest для интеграции с Allure или другим инструментом.

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

СложностьРешение
LLM генерирует дублирующиеся запросыДобавить дедупликацию на этапе сохранения (embeddings + cosine similarity < 0.95)
Время генерации превышает 5 минут из-за лимитов APIИспользовать batch API (OpenAI), уменьшить число LLM-запросов (генерировать через LLM только 10–20% уникальных вариантов, остальные – комбинаторно без LLM)
Агент не поддерживает быстрое тестирование (500 запросов могут занять часы)Использовать mock-версию агента без внешних вызовов (заменять API заглушками)
Сложность поддержки шаблонов при изменении агентаВынести шаблоны в отдельный YAML-файл с документацией полей; добавить тесты на валидность шаблонов (pydantic validation)
Промпт для парафразирования даёт неадекватные результатыИтеративно улучшать промпт: добавить примеры, ограничить длину, указать формат (один вопрос в строке)

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

ЭтапВремя
Этап 1: Анализ и подготовка шаблонов1 час
Этап 2: Разработка генератора2.5 часа
Этап 3: Интеграция с тестовым прогоном1.5 часа
Этап 4: Оптимизация производительности1 час
Этап 5: Документация и CI1 час
Итого7 часов

Примечание Для первого раза рекомендуется добавить 1–2 часа на отладку и настройку LLM-промптов.


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

ВопросТема
42Методы генерации тестовых данных с помощью LLM
107Асинхронная обработка запросов в Python (asyncio)
214Параметризованные тесты в pytest
318Оптимизация latency при batch-запросах к OpenAI
429Работа с YAML-конфигами в Python
531CI/CD для AI-проектов (GitHub Actions)
664Дедупликация текстов с помощью эмбеддингов
720Шаблонизация промптов (PromptTemplate, Jinja2)
801Тестирование AI-агентов: best practices
890Производительность генерации: замеры и профилирование

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

  • Я проверил, что сгенерированные 500 запросов уникальны (нет exact дубликатов).
  • Я замерил время выполнения generate_tests.py и убедился, что оно <5 минут.
  • Я проверил, что каждый запрос из test_requests.jsonl соответствует одному из шаблонов (можно протестировать парсингом).
  • Я запустил pytest test_agent.py – все тесты прошли (или я задокументировал, какие упали и почему).
  • Я проверил, что CI-пайплайн зелёный и артефакты (report.xml) создаются.