Как генерировать synthetic датасеты для RAG evaluation?
Краткий тезис
Синтетические датасеты (synthetic datasets) — это искусственно созданные пары «запрос — релевантный контекст — ожидаемый ответ», которые позволяют оценивать RAG-систему без дорогой ручной разметки. Основной подход состоит из пяти шагов: взять корпус документов, с помощью LLM сгенерировать по ним вопросы, проверить качество вопросов (self-consistency, человеческая валидация), сгенерировать ground-truth ответы и обогатить датасет разнообразием (перефразирование, усложнение запросов). Инструменты вроде Ragas и Evol-Instruct автоматизируют этот процесс, а типичный размер качественного датасета — 500–2000 примеров.
1. Что такое synthetic датасеты и зачем они нужны
Synthetic датасет — это набор искусственно сгенерированных данных, имитирующий реальные пользовательские запросы вместе с эталонными ответами (ground truth). В контексте RAG он используется для оценки (evaluation) качества поиска и генерации, а также для fine-tuning компонентов системы.
Зачем нужен synthetic датасет
- Скорость и масштаб: evaluation|ручная разметка 1000 примеров может занять недели, а generation|синтетическая генерация — часы.
- Покрытие редких сценариев: LLM может сгенерировать сложные, многошаговые или редкие запросы, которые сложно собрать от пользователей.
- Контролируемое разнообразие: можно менять стиль, сложность, язык, тип запроса (фактологический, сравнительный, аналитический).
- Отсутствие приватности: данные генерируются из открытого корпуса, не содержат личной информации.
Ограничение: синтетические данные могут не полностью отражать распределение реальных запросов, поэтому их обычно дополняют небольшой ручной выборкой (human-in-the-loop).
2. Шаг 1: Подготовка корпуса документов
Исходные данные — это корпус документов (или чанков), которые RAG-система использует как базу знаний.
Требования к корпусу
- Документы должны быть чистыми: удалены дубликаты, шум, лишние метаданные.
- Желательно нормализовать формат: единый тип (текст, Markdown, HTML).
- Если корпус большой, можно семплировать репрезентативную подвыборку (например, 10% случайных документов или кластеризовать по темам).
Пример подготовительного кода (Python):
import pandas as pd
# Предположим, у нас есть список чанков
chunks = pd.read_parquet("my_chunks.parquet")
# Фильтрация коротких чанков
chunks = chunks[chunks['text'].str.len() > 100].reset_index(drop=True)
print(f"Отобрано {len(chunks)} чанков")
3. Шаг 2: Генерация вопросов на основе документов
Основная идея: для каждого чанка (или группы чанков) LLM генерирует один или несколько вопросов, на которые можно ответить только из этого чанка.
Методы генерации вопросов
| Метод | Описание | Плюсы | Минусы |
|---|---|---|---|
| Один вопрос на чанк | LLM просят: «Задайте один вопрос, ответ на который содержится в данном тексте» | Простота, высокая покрываемость | Может быть тривиальным |
| Несколько вопросов раундами | Для одного чанка генерируется 3–5 вопросов разной сложности | Больше разнообразия | Риск повторений |
| На основе сценариев | Определяются типы вопросов (факт, сравнение, вывод) и генерируются целенаправленно | Лучше покрывает разные навыки | Сложнее в реализации |
Пример промпта
Ты — эксперт по оценке RAG. По данному тексту сгенерируй вопрос, на который можно ответить, используя только этот текст. Вопрос должен быть чётким, недвусмысленным и не содержать ответа внутри себя. Ответь в формате: {"question": "...", "context_index": ...}.
Текст: {chunk}
Важно: в вопрос не должен вкладываться ответ. Иначе evaluation станет бессмысленным.
4. Шаг 3: Валидация качества вопросов (self-consistency и человек)
Не все сгенерированные вопросы будут хорошими. Нужен этап фильтрации.
Self-consistency — техника, при которой один и тот же вопрос подаётся LLM несколько раз (с разными температурами). Если ответы совпадают по сути (или модель отвечает правильно, используя тот же чанк), вопрос считается валидным. В противном случае — отбрасывается.
Человеческая валидация: небольшая выборка (50–100 вопросов) проверяется аннотатором. Можно настроить классификатор для автоматической отбраковки явно некачественных (например, слишком общих вопросов или вопросов, ответ на которые не в чанке).
Критерии отбраковки
- Вопрос не имеет однозначного ответа в чанке.
- Вопрос содержит ответ или подсказку.
- Явная грамматическая/стилистическая ошибка делает вопрос непонятным.
- Вопрос дублирует уже имеющийся (для этого используют эмбеддинг-сходство, например, cosine > 0.95).
5. Шаг 4: Генерация ground-truth ответов
После того, как отобраны качественные вопросы, для каждого вопроса нужно создать эталонный ответ (ground truth).
Способы
- Дословный фрагмент из документа: из чанка извлекается предложение, содержащее ответ. Подходит для фактологических вопросов.
- Ответ сгенерированный LLM: LLM формулирует ответ на основе чанка, но без использования внешних знаний. Важно указать промпт: «Ответь строго на основе данного текста».
- Аннотация человеком: для критически важных сценариев (например, медицинские или юридические) человек пишет эталон.
Пример генерации с помощью LLM
from openai import OpenAI
client = OpenAI()
def generate_ground_truth(question, chunk):
response = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "Ответь на вопрос, используя только предоставленный текст. Не добавляй знания извне."},
{"role": "user", "content": f"Текст: {chunk}\nВопрос: {question}"}
]
)
return response.choices[0].message.content
Важно: ground truth должен быть полным и корректным относительно документа. Если ответ состоит из нескольких фактов, нужно их все включить.
6. Шаг 5: Добавление разнообразия (Evol-Instruct, парафраз и сложные запросы)
Базовый датасет, где каждому вопросу соответствует ровно один чанк, слишком прост. Реальные пользовательские запросы бывают:
- многошаговые (требуют объединения нескольких чанков);
- с неявной информацией (нужна перефразировка);
- с негативными формулировками («почему не работает?»).
Evol-Instruct — техника автоматического усложнения инструкций с помощью LLM. Изначально был предложен для instruction tuning, но отлично работает для RAG.
Пример применения к RAG
- Возьмите базовый вопрос (простой фактологический).
- Попросите LLM переписать его так, чтобы ответ требовал анализа двух документов или логического вывода.
- Отфильтруйте вопросы, которые уже не имеют ответа в корпусе (проверка через retrieval).
Парафразирование: сгенерировать 3–5 вариантов одного вопроса с разными формулировками (синонимы, инверсия подлежащего, вопросительные слова).
7. Инструменты: Ragas и другие
Основные инструменты для создания synthetic датасетов:
| Инструмент | Описание | Особенности |
|---|---|---|
| Ragas (библиотека Python) | Содержит модуль testset_generator | Поддерживает генерацию вопросов, ground truth, метрики качества |
| Evol-Instruct (от Microsoft) | Алгоритм усложнения через LLM | Используется в WizardLM, адаптируется под RAG |
| LangChain + LLM | Кастомная цепочка промптов | Гибкость, можно комбинировать с собственными правилами |
| GPT-4 / Claude API | Прямая генерация через API | Самый простой способ, но дорогой |
Пример использования Ragas:
from ragas.testset.generator import TestsetGenerator
from ragas.testset.evolutions import simple, reasoning, multi_context
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
generator = TestsetGenerator(
llm=ChatOpenAI(model="gpt-4"),
embedding_model=OpenAIEmbeddings(),
knowledge_set=documents # список LangChain Document объектов
)
testset = generator.generate(
test_size=500,
distributions={simple: 0.5, reasoning: 0.3, multi_context: 0.2}
)
# testset — это список TestSample: question, contexts, ground_truth
8. Размер датасета и баланс
Оптимальный размер synthetic датасета для RAG evaluation — 500–2000 примеров. Меньше 500 — недостаточно статистической значимости, больше 2000 — дорого и избыточно при разумном доверительном интервале.
Баланс распределения:
- Простые вопросы (50%) — фактологические, ответ в одном чанке.
- Вопросы на рассуждение (30%) — требуют объединения двух-трёх чанков.
- Сложные вопросы (20%) — многошаговые, с неявной информацией, или requiring multi-hop reasoning.
Важно: распределение должно имитировать реальное использование, иначе метрики будут смещены.
9. Оценка качества самого synthetic датасета
Прежде чем использовать датасет для RAG evaluation, нужно убедиться, что он качественный. Метрики:
| Метрика | Описание | Как измерять |
|---|---|---|
| Answer Relevancy | Соответствует ли вопрос ответу (ground truth) | С помощью LLM-as-judge (например, Ragas answer_relevancy) |
| Faithfulness | Насколько ответ точен относительно контекста | Проверка фактов из контекста |
| Coverage | Какая доля чанков корпуса упомянута хотя бы в одном вопросе | Подсчёт уникальных context_index |
| Diversity | Лексическое разнообразие вопросов (type-token ratio, тематическая кластеризация) | Использовать TF-IDF, BERTopic |
Если датасет содержит много тривиальных вопросов, его надо дообогатить (вернуться к шагу 5).
10. Плюсы и минусы synthetic датасетов
| Плюсы | Минусы |
|---|---|
| Быстрая итерация (цикл «генерируй — оценивай — улучшай») | Возможен дрейф распределения (distribuition shift) от реальных запросов |
| Низкая стоимость (по сравнению с ручной разметкой) | Зависимость от LLM-модели: слабая модель генерирует плохие вопросы |
| Легко масштабировать до тысяч примеров | Трудности с оценкой субъективных ответов (мнения, рекомендации) |
| Можно контролировать сценарии (негативные, adversarial) | Риск переобучения на синтетические паттерны |
Рекомендация: использовать synthetic датасет как основной, но дополнительно собрать 100–200 реальных запросов от пользователей и добавить их в evaluation set.
11. Пример полного пайплайна (Ragas + Evol-Instruct)
Ниже приведён псевдокод, объединяющий описанные шаги:
from ragas.testset.generator import TestsetGenerator
from ragas.testset.evolutions import simple, reasoning, multi_context
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 1. Загрузка и чанкинг документов
loader = TextLoader("docs.txt")
documents = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)
chunks = splitter.split_documents(documents)
# 2. Настройка генератора
generator = TestsetGenerator(
llm=ChatOpenAI(model="gpt-4", temperature=0.7),
embedding_model=OpenAIEmbeddings(),
knowledge_set=chunks
)
# 3. Генерация 1000 примеров с распределением эволюций
testset = generator.generate(
test_size=1000,
distributions={simple: 0.4, reasoning: 0.4, multi_context: 0.2}
)
# 4. Фильтрация по self-consistency (упрощённо)
# (в Ragas этот этап встроен в generate)
# 5. Сохранение
import json
with open("rag_synthetic_testset.json", "w") as f:
json.dump([sample.dict() for sample in testset], f, indent=2)
Пет-проект для закрепления
Задача: разработать synthetic датасет для оценки RAG-системы, которая отвечает на вопросы по документации вымышленного продукта.
Инструменты: Python, Ragas, OpenAI API, набор Markdown-файлов (10–20 страниц).
Шаги:
- Собрать тексты (или использовать датасет
ms_marcoкак готовый корпус). - Разбить на чанки (500–1000 символов).
- Сгенерировать 300 вопросов с помощью Ragas (простые + многошаговые).
- Вручную проверить 30 случайных вопросов на корректность.
- Создать ground truth для каждого вопроса (используя LLM или извлечение).
- Оценить полученный датасет метрикой
answer_relevancyиfaithfulnessчерез Ragas. - Сравнить распределение вопросов с десятью реальными вопросами от коллег.
Ожидаемый результат: готовый JSON-файл с полями question, contexts, ground_truth, а также отчёт о качестве датасета (таблица метрик). Вы научитесь видеть, какие типы вопросов покрыты, а какие — нет, и сможете улучшать датасет итеративно.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 5 | Как оценивать качество retrieval в RAG? |
| 6 | Как оценивать качество генерации (faithfulness и др.)? |
| 10 | Что такое Self-RAG и когда его использовать? |
| 12 | Как организовать Human-in-the-loop для RAG? |
| 15 | Как готовить данные для fine-tuning RAG-компонентов? |
| 20 | Data augmentation для LLM: методы и ограничения |
Навигация
- Предыдущий: 865
- Следующий: 867
- Индекс: 00. Индекс разборов