English translation is not available yet. Showing Russian content.

Как вы интегрируете 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-пайплайн часто строится вручную:

  1. Получить запрос пользователя.
  2. Найти релевантные чанки в векторной БД.
  3. Сформировать промпт: "Контекст: {chunks}. Вопрос: {question}. Ответь на вопрос на основе контекста."
  4. Отправить в LLM.
  5. Получить ответ.

Проблемы такого подхода

  • Хрупкость промптов Малейшее изменение формулировки может сломать качество.
  • Отсутствие оптимизации Как именно 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

  1. Берёт примеры из trainset.
  2. Для каждого примера запускает базовый пайплайн.
  3. Если ответ правильный (по метрике), сохраняет этот пример как few-shot пример.
  4. Добавляет эти примеры в промпт для новых запросов.

Шаг 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 для хранения эмбеддингов

Шаги:

  1. Сбор данных Скачайте документацию Python (например, из официального сайта) и разбейте на чанки по 500 токенов.
  2. Создание эмбеддингов Используйте sentence-transformers для создания эмбеддингов чанков.
  3. Настройка DSPy
    • Определите сигнатуру context: List[str], question: str -> answer: str.
    • Создайте модуль RAG с dspy.Retrieve (кастомный, использующий FAISS) и dspy.ChainOfThought.
  4. Создание датасета Вручную составьте 50-100 пар "вопрос-ответ" по документации Python.
  5. Оптимизация Используйте BootstrapFewShot для оптимизации пайплайна на 80% датасета.
  6. Оценка На оставшихся 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-системы?

Навигация