Как вы интегрируете DSPy с RAG-пайплайном? Приведите пример сигнатуры.
Краткий тезис
DSPy — это фреймворк для программируемых промптов, который автоматически оптимизирует промпты и цепочки вызовов LLM. Интеграция с RAG-пайплайном происходит через объявление сигнатуры (Signature), которая описывает типы входных и выходных данных, и использование модулей (Modules) для построения логики. Ключевая идея: DSPy позволяет не писать промпты вручную, а декларативно описывать задачу, после чего фреймворк сам подбирает оптимальный промпт и способ использования контекста.
1. Термин: DSPy (Declarative Self-improving Python)
DSPy — это фреймворк от Стэнфорда, который меняет парадигму работы с LLM. Вместо ручного написания промптов и их итеративного улучшения, вы описываете сигнатуру (что входит и что должно выйти) и модуль (как это сделать), а DSPy сам оптимизирует промпты, few-shot примеры и даже цепочки вызовов.
Ключевые концепции DSPy
- Сигнатура (Signature): Декларативное описание типов входных и выходных данных. Например, context: List[str], question: str -> answer: str.
- Модуль (Module): Готовый строительный блок, который реализует типовую логику (например, dspy.ChainOfThought, dspy.ReAct).
- Оптимизатор (Optimizer / Teleprompter): Алгоритм, который автоматически подбирает промпты, few-shot примеры и инструкции для заданной метрики. Например, BootstrapFewShot, MIPROv2.
- Метрика (Metric): Функция, которая оценивает качество ответа (например, точность, faithfulness, релевантность).
2. Зачем интегрировать DSPy с RAG?
Традиционный RAG-пайплайн часто строится вручную:
- Получить запрос пользователя.
- Найти релевантные чанки в векторной БД.
- Сформировать промпт: "Контекст: {chunks}. Вопрос: {question}. Ответь на вопрос на основе контекста."
- Отправить в LLM.
- Получить ответ.
Проблемы такого подхода
- Хрупкость промптов Малейшее изменение формулировки может сломать качество.
- Отсутствие оптимизации Как именно LLM должна использовать контекст? В начале, в конце, с рассуждением? Это не оптимизируется автоматически.
- Ручная настройка Для каждого нового датасета или задачи нужно заново писать и тестировать промпты.
DSPy решает эти проблемы
- Автоматическая оптимизация DSPy сам подбирает лучший способ подачи контекста и формулировку инструкции.
- Декларативность Вы описываете что нужно сделать, а не как.
- Воспроизводимость Пайплайн становится программным кодом, а не набором текстовых промптов.
3. Базовая сигнатура для RAG
Сигнатура — это основа интеграции. Она определяет, какие поля будут использоваться для передачи данных между модулями.
Пример базовой сигнатуры
import dspy
class RAGSignature(dspy.Signature):
"""Ответь на вопрос, используя предоставленный контекст."""
context = dspy.InputField(desc="Список релевантных документов")
question = dspy.InputField(desc="Вопрос пользователя")
answer = dspy.OutputField(desc="Ответ на вопрос на основе контекста")
Расшифровка
- context: List[str] — список строк, каждая строка — это один релевантный чанк.
question: str— исходный запрос пользователя.- answer: str — финальный ответ, который должна сгенерировать LLM.
Важно Сигнатура не говорит, как использовать контекст. Она только описывает типы данных. Как именно LLM будет обрабатывать контекст (читать сначала, потом вопрос, или наоборот) — это решает модуль и оптимизатор.
4. Модули DSPy для RAG
DSPy предоставляет несколько модулей, которые можно использовать для построения RAG-пайплайна.
4.1. dspy.ChainOfThought
Этот модуль добавляет в промпт инструкцию "Let's think step by step" (или её оптимизированную версию). Он заставляет LLM сначала рассуждать, а потом давать ответ.
class RAGCoT(dspy.Module):
def __init__(self):
super().__init__()
self.generate_answer = dspy.ChainOfThought(RAGSignature)
def forward(self, context, question):
return self.generate_answer(context=context, question=question)
4.2. dspy.ReAct
Этот модуль реализует паттерн Reasoning + Acting (Рассуждение + Действие). Он позволяет LLM не только рассуждать, но и выполнять действия (например, вызывать поиск, калькулятор). Для RAG это может быть полезно, если нужно сделать несколько шагов поиска.
4.3. dspy.ProgramOfThought
Модуль, который генерирует и исполняет код Python для решения задачи. Может быть полезен для сложных аналитических запросов к RAG.
5. Полный пример интеграции DSPy с RAG
Рассмотрим полный пример, где мы используем DSPy для оптимизации RAG-пайплайна.
Шаг 1: Установка и импорт
pip install dspy-ai
import dspy
from dspy.datasets import HotPotQA
from dspy.teleprompt import BootstrapFewShot
Шаг 2: Настройка LM и RM
# Настройка языковой модели (LM)
turbo = dspy.OpenAI(model='gpt-3.5-turbo', max_tokens=250)
dspy.settings.configure(lm=turbo)
# Настройка retrieval model (RM) — например, ColBERTv2
colbert = dspy.ColBERTv2(url='http://localhost:8893/api/search')
dspy.settings.configure(rm=colbert)
Шаг 3: Определение сигнатуры и модуля
class GenerateAnswer(dspy.Signature):
"""Ответь на вопрос, используя предоставленный контекст."""
context = dspy.InputField(desc="Список релевантных документов")
question = dspy.InputField(desc="Вопрос пользователя")
answer = dspy.OutputField(desc="Ответ на вопрос на основе контекста")
class RAG(dspy.Module):
def __init__(self, num_passages=3):
super().__init__()
self.retrieve = dspy.Retrieve(k=num_passages)
self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
def forward(self, question):
context = self.retrieve(question).passages
prediction = self.generate_answer(context=context, question=question)
return dspy.Prediction(context=context, answer=prediction.answer)
Пояснение
- dspy.Retrieve(k=num_passages) — это встроенный модуль DSPy для поиска. Он использует настроенный RM (ColBERTv2).
- forward — метод, который описывает логику пайплайна: сначала поиск, потом генерация ответа.
Шаг 4: Загрузка данных и определение метрики
# Загрузка датасета HotPotQA
dataset = HotPotQA(train_seed=1, train_size=20, eval_seed=2023, dev_size=50, test_size=0)
# Метрика: точность ответа
def validate_context_and_answer(example, pred, trace=None):
# Проверяем, что ответ правильный (сравниваем с gold answer)
answer_EM = dspy.evaluate.answer_exact_match(example, pred)
return answer_EM
Шаг 5: Оптимизация пайплайна
# Настройка оптимизатора
teleprompter = BootstrapFewShot(metric=validate_context_and_answer)
# Оптимизация
compiled_rag = teleprompter.compile(RAG(), trainset=dataset.train)
Что делает BootstrapFewShot
- Берёт примеры из
trainset. - Для каждого примера запускает базовый пайплайн.
- Если ответ правильный (по метрике), сохраняет этот пример как few-shot пример.
- Добавляет эти примеры в промпт для новых запросов.
Шаг 6: Использование оптимизированного пайплайна
# Пример запроса
question = "Кто был президентом США в 1963 году?"
prediction = compiled_rag(question)
print(f"Вопрос: {question}")
print(f"Контекст: {prediction.context}")
print(f"Ответ: {prediction.answer}")
6. Что оптимизирует DSPy в RAG-пайплайне?
DSPy не просто подбирает few-shot примеры. Он оптимизирует весь промпт, включая:
| Компонент промпта | Что оптимизируется |
|---|---|
| Инструкция | Как сформулировать задачу (например, "Ответь на вопрос" vs "Используя контекст, дай развернутый ответ") |
| Порядок полей | Что идёт первым: контекст или вопрос? |
| Формат контекста | Как отображать чанки: нумерованный список, абзацы, с разделителями? |
| Рассуждение | Нужно ли добавлять "Let's think step by step" или другую инструкцию для рассуждения? |
| Few-shot примеры | Какие примеры лучше всего демонстрируют задачу? |
7. Продвинутые техники интеграции
7.1. Мульти-шаговый RAG
Можно построить пайплайн, где LLM сама решает, какие запросы делать к retrieval.
class MultiHopRAG(dspy.Module):
def __init__(self):
super().__init__()
self.retrieve = dspy.Retrieve(k=3)
self.generate_query = dspy.ChainOfThought("context, question -> search_query")
self.generate_answer = dspy.ChainOfThought("context, question -> answer")
def forward(self, question):
# Первый шаг: поиск по исходному вопросу
context1 = self.retrieve(question).passages
# Генерация уточняющего запроса
search_query = self.generate_query(context=context1, question=question).search_query
# Второй шаг: поиск по уточняющему запросу
context2 = self.retrieve(search_query).passages
# Финальный ответ
all_context = context1 + context2
answer = self.generate_answer(context=all_context, question=question).answer
return dspy.Prediction(answer=answer)
7.2. Кастомный retrieval
Если вы используете свою векторную БД (например, Pinecone, Weaviate), можно создать кастомный модуль retrieval.
class CustomRetriever(dspy.Retrieve):
def __init__(self, k=5):
super().__init__(k=k)
self.vector_db = YourVectorDB() # ваша БД
def forward(self, query):
results = self.vector_db.search(query, k=self.k)
passages = [result.text for result in results]
return dspy.Prediction(passages=passages)
8. Сравнение: RAG без DSPy vs с DSPy
| Аспект | Без DSPy | С DSPy |
|---|---|---|
| Промпты | Пишутся вручную, хрупкие | Оптимизируются автоматически |
| Few-shot примеры | Подбираются вручную | Подбираются оптимизатором |
| Адаптация к новым данным | Требует ручного тестирования | Автоматическая компиляция |
| Воспроизводимость | Низкая (промпты в коде) | Высокая (всё в коде) |
| Гибкость | Высокая (можно любой промпт) | Средняя (ограничена сигнатурами) |
| Сложность | Низкая (просто промпт) | Средняя (нужно изучить DSPy) |
Пет-проект для закрепления
Задача Создать RAG-систему для ответов на вопросы по документации Python, используя DSPy для оптимизации.
Инструменты
- Python, DSPy, LangChain (для парсинга документации)
- OpenAI API (или локальная модель через Ollama)
- FAISS или ChromaDB для хранения эмбеддингов
Шаги:
- Сбор данных Скачайте документацию Python (например, из официального сайта) и разбейте на чанки по 500 токенов.
- Создание эмбеддингов Используйте
sentence-transformersдля создания эмбеддингов чанков. - Настройка DSPy
- Определите сигнатуру
context: List[str], question: str -> answer: str. - Создайте модуль
RAGсdspy.Retrieve(кастомный, использующий FAISS) иdspy.ChainOfThought.
- Определите сигнатуру
- Создание датасета Вручную составьте 50-100 пар "вопрос-ответ" по документации Python.
- Оптимизация Используйте
BootstrapFewShotдля оптимизации пайплайна на 80% датасета. - Оценка На оставшихся 20% данных оцените точность ответов (exact match или LLM-as-judge).
Ожидаемый результат
- Оптимизированный пайплайн, который даёт точные ответы на вопросы по Python.
- Понимание, как DSPy меняет промпты и few-shot примеры для улучшения качества.
- Сравнение с базовым RAG (без DSPy) — вы увидите улучшение точности на 10-20%.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 103 | Что такое DSPy и как он отличается от LangChain? |
| 105 | Как работает оптимизатор BootstrapFewShot в DSPy? |
| 106 | Какие метрики можно использовать в DSPy для оценки RAG? |
| 5 | Как вы оцениваете качество retrieval'а в RAG-системе? |
| 1 | Как бы вы спроектировали RAG-систему для 10 000 документов? |
| 7 | Как вы уменьшаете latency RAG-системы? |
Навигация
- Предыдущий: 103
- Следующий: 105
- Индекс: 00. Индекс разборов