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

Как вы делаете synthetic data для multi-turn диалогов (агентов)?

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

Синтетические данные для многошаговых диалогов агентов генерируются с помощью LLM через self-chat (LLM поочерёдно играет роли пользователя и агента), сценарный подход (задаётся цель, например «купить билет», и LLM разыгрывает диалог) и интеграцию инструментов (вставка API-вызовов в диалог). Полученные данные обязательно валидируются вторым LLM или набором правил, чтобы обеспечить логичность, корректность вызовов инструментов и соответствие сценарию. Такой подход позволяет создать датасет для дообучения или тестирования агентных систем без дорогой ручной разметки.


1. Зачем нужен synthetic data для multi-turn диалогов агентов?

Synthetic data — это искусственно сгенерированные данные, имитирующие реальные взаимодействия. В контексте Agentic RAG (системы, где LLM может вызывать инструменты, делать несколько шагов и поддерживать контекст) синтетические диалоги решают несколько задач:

  • Обучение/дообучение (fine-tuning): для улучшения способности агента следовать инструкциям, корректно вызывать инструменты и вести многошаговый диалог нужны тысячи примеров. evaluation|Ручная разметка таких диалогов крайне дорога.
  • Тестирование и оценка: синтетические сценарии позволяют проверить, как агент справляется с типичными и граничными случаями (например, отказ инструмента, неполный ввод пользователя).
  • Аугментация данных: если есть небольшой набор реальных диалогов, синтетические данные могут его дополнить, увеличив разнообразие.

Термин multi-turn диалог — это разговор, состоящий из нескольких реплик (turn), где каждая следующая реплика зависит от предыдущего контекста. Агент в таком диалоге может выполнять действия (call|вызовы API, поиск в БД) и возвращать результаты.


2. Self-chat: генерация диалогов с переключением ролей

Self-chat — самый простой метод: один LLM последовательно генерирует реплики пользователя и агента, переключая системный промпт. Пример структуры:

import openai

def generate_self_chat(scenario_prompt, turns=5):
    messages = [
        {"role": "system", "content": "Ты — пользователь, который хочет забронировать отель. Твои реплики должны быть естественными, иногда уточняющими."}
    ]
    for i in range(turns):
        # Генерация реплики пользователя
        user_reply = openai.ChatCompletion.create(
            model="gpt-4",
            messages=messages + [{"role": "user", "content": "Сгенерируй следующую реплику пользователя."}]
        )["choices"][0]["message"]["content"]
        messages.append({"role": "user", "content": user_reply})
        
        # Генерация реплики агента (с возможностью вызова инструментов)
        agent_reply = openai.ChatCompletion.create(
            model="gpt-4",
            messages=messages + [{"role": "system", "content": "Ты — агент бронирования. Отвечай пользователю, при необходимости вызывай функции."}]
        )["choices"][0]["message"]["content"]
        messages.append({"role": "assistant", "content": agent_reply})
    return messages

Ключевые моменты:

  • Роли переключаются: для каждой реплики меняется system prompt, чтобы LLM понимал, кого он сейчас играет.
  • Контекст сохраняется: все предыдущие реплики передаются в историю.
  • Инструменты: в репликах агента можно эмулировать вызовы функций, например, [SEARCH_HOTEL: Paris, dates].

Проблемы self-chat:

  • Диалоги могут быть неестественными, повторяющимися.
  • LLM может «забывать» цель сценария.
  • Нет гарантии, что агент действительно использует инструменты.

3. Сценарный подход: задаём цель и контекст

Вместо свободной генерации лучше задать сценарий — высокоуровневое описание задачи пользователя и ожидаемого поведения агента. Пример сценария:

Пользователь хочет купить билет на поезд из Москвы в Санкт-Петербург на завтра. Бюджет — до 3000 рублей. Агент должен сначала проверить наличие билетов, затем предложить варианты, после чего пользователь выбирает и агент оформляет покупку.

Процесс генерации:

  1. Определяем сценарий (вручную или с помощью LLM).
  2. Генерируем «скелет» диалога: ключевые шаги (запрос → поиск → предложение → уточнение → покупка).
  3. Заполняем детали с помощью LLM, используя self-chat, но с жёсткой привязкой к шагам.

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

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

4. Интеграция инструментов (API calls)

В Agentic RAG агент может вызывать внешние функции: поиск, отправка email, запрос к БД. В синтетических данных нужно явно отмечать такие вызовы. Есть два подхода:

  • Эмуляция в тексте: агент пишет SEARCH: Вики/Query|query, а затем [RESULT: ...]. Это просто, но не подходит для обучения моделей, которые используют Вики/OpenAI Functions|function calling (формат Вики/GPT-4o|OpenAI).
  • Структурированный вывод: генерация в формате JSON с полями function_call или tool_calls. Пример:
{
  "role": "assistant",
  "content": null,
  "function_call": {
    "name": "search_flights",
    "arguments": "{\"from\": \"Moscow\", \"to\": \"SPb\", \"date\": \"2025-03-01\"}"
  }
}

Как генерировать:

  • В system prompt агента добавляем описание доступных функций (как в OpenAI API).
  • LLM сам решает, когда вызвать функцию, и генерирует соответствующий JSON.
  • Затем в диалоге появляется ответ функции (можно сгенерировать отдельно или взять из заранее подготовленного набора).

Пример генерации вызова инструмента:

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Получить погоду в городе",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string"}
                },
                "required": ["city"]
            }
        }
    }
]

response = openai.ChatCompletion.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "Какая погода в Париже?"}],
    tools=tools,
    tool_choice="auto"
)
# В ответе будет tool_calls

5. Проверка качества (validation)

Сгенерированные диалоги могут содержать ошибки: нелогичные реплики, неправильные вызовы инструментов, нарушение сценария. Поэтому нужна валидация. Способы:

  • LLM-валидатор: второй LLM (или та же модель с другим промптом) проверяет диалог по критериям:
    • Логичность (реплики пользователя и агента связаны).
    • Корректность вызовов инструментов (аргументы соответствуют описанию, результат используется).
    • Достижение цели сценария.
  • Правила (rule-based): например, проверка, что после вызова функции обязательно идёт ответ с результатом, или что количество шагов не превышает лимит.
  • Человеческая разметка: для небольшой части данных, чтобы оценить качество автоматической валидации.

Пример промпта для валидатора:

Ты — эксперт по оценке диалогов агентов. Оцени следующий диалог по шкале 1-5 по трём критериям:
1. Логичность (реплики соответствуют контексту)
2. Корректность вызовов инструментов (правильные аргументы, своевременные вызовы)
3. Достижение цели (пользователь получил то, что хотел)

Диалог: {dialog}
Выведи JSON с оценками и комментариями.

6. Методы улучшения качества synthetic data

  • Разнообразие: варьировать стиль пользователя (вежливый, нетерпеливый), сложность запросов, количество шагов.
  • Баланс: включать как успешные, так и неуспешные сценарии (агент ошибся, инструмент недоступен).
  • Избегание паттернов: если LLM часто генерирует однотипные фразы, можно добавить в промпт требование «используй разные формулировки».
  • Итеративная генерация: сгенерировать черновик, проверить, отредактировать проблемные места, сгенерировать заново.

7. Инструменты и фреймворки

  • LangChain — имеет модуль langchain.chains.loading для генерации диалогов, но чаще используется для построения агентов, а не для синтетики.
  • DSPy — фреймворк для оптимизации промптов, можно использовать для автоматической генерации и валидации диалогов.
  • OpenAI Evals — библиотека для оценки, включает генерацию тестовых данных.
  • Собственные скрипты на Python с вызовами API — наиболее гибкий вариант.

8. Пример кода: генерация multi-turn диалога с инструментами

Ниже — упрощённый скрипт, который генерирует диалог из 3 шагов с вызовом функции search_flights.

import openai
import json

def generate_dialog(scenario, tools, max_turns=4):
    messages = [{"role": "system", "content": f"Ты — пользователь. Сценарий: {scenario}"}]
    dialog = []
    
    for turn in range(max_turns):
        # Пользователь
        user_msg = openai.ChatCompletion.create(
            model="gpt-4",
            messages=messages + [{"role": "user", "content": "Сгенерируй естественную реплику пользователя, продолжая диалог."}]
        )["choices"][0]["message"]["content"]
        dialog.append({"role": "user", "content": user_msg})
        messages.append({"role": "user", "content": user_msg})
        
        # Агент
        agent_response = openai.ChatCompletion.create(
            model="gpt-4",
            messages=messages + [{"role": "system", "content": "Ты — агент. Используй инструменты, если нужно."}],
            tools=tools,
            tool_choice="auto"
        )
        choice = agent_response["choices"][0]["message"]
        if choice.get("tool_calls"):
            dialog.append({"role": "assistant", "content": None, "tool_calls": choice["tool_calls"]})
            # Эмулируем результат инструмента
            for tc in choice["tool_calls"]:
                # Здесь можно подставить реальный результат
                result = {"role": "tool", "content": json.dumps({"flights": ["SU 123", "SU 456"]}), "tool_call_id": tc["id"]}
                dialog.append(result)
                messages.append(result)
        else:
            dialog.append({"role": "assistant", "content": choice["content"]})
            messages.append({"role": "assistant", "content": choice["content"]})
    return dialog

# Пример использования
tools = [{
    "type": "function",
    "function": {
        "name": "search_flights",
        "parameters": {"type": "object", "properties": {"from": {"type": "string"}, "to": {"type": "string"}}, "required": ["from", "to"]}
    }
}]
dialog = generate_dialog("Пользователь хочет найти рейс из Москвы в Лондон", tools)
print(json.dumps(dialog, indent=2))

9. Ограничения и риски

  • Галлюцинации: LLM может генерировать несуществующие инструменты или некорректные аргументы.
  • Перекос (bias): если сценарии однотипны, модель будет плохо обобщать.
  • Стоимость: генерация тысяч диалогов через API может быть дорогой.
  • Качество валидации: LLM-валидатор тоже может ошибаться, поэтому нужна человеческая выборка.

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

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

Инструменты: Python, OpenAI API (или локальная LLM), JSON.

Шаги:

  1. Определить 5 сценариев (например, «бюджетный отель в центре», «отель с бассейном», «отмена брони»).
  2. Для каждого сценария написать шаблон диалога (список шагов).
  3. Использовать self-chat с переключением ролей, добавив инструменты search_hotels, book_hotel, cancel_booking.
  4. После генерации прогнать каждый диалог через LLM-валидатор с критериями: логичность, корректность вызовов, достижение цели.
  5. Отфильтровать диалоги с оценкой ниже 4/5.
  6. Сохранить результат в формате, пригодном для fine-tuning (например, в формате OpenAI messages).

Ожидаемый результат: JSON-файл с 50-70 качественными диалогами, готовыми для обучения агента.


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

ВопросТема
691Как спроектировать агента с инструментами?
692Как оценивать качество работы агента?
693Как дообучать LLM для агентных задач?
694Как деплоить агента в production?
696Как тестировать агента на граничные случаи?

Навигация