Гайд · TNWS AI

Как собрать двухшаговый DSPy Module для выбора темы и ответа FAQ

5 мин—

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

Задача и применимость

Актуальный поисковый вопрос: «как объединить два dspy Predict в один Module forward для ответа». Отделить неверный выбор темы от неверного ответа по правильно выбранной справке. Материал основан на текущем официальном руководстве DSPy, проверенном 20 сентября 2026 года, и предназначен для Python 3.10+ с совместимым установленным dspy. У разных провайдеров разные доступные модели, лимиты и права: укажите собственный реально работающий provider/model в переменных окружения. Фиксируйте установленную версию пакета при повторении опыта, поскольку API может меняться.

Как устроено решение

Официальное руководство композиции показывает dspy.Module с init, создающим внутренние модули, и forward, который проводит данные через них и возвращает dspy.Prediction. В этом примере route не получает весь текст справки и не может сам придумать документ; reply вызывается только после попадания в известную тему. Такое разделение помогает объяснить разные регрессии: topic неверен из-за маршрутизатора, а искажение R-17 при правильном topic относится к ответчику. Два логических шага не гарантируют меньшую стоимость или большую точность без измерения.

Порядок действий

  1. Зафиксируйте две вымышленные записи справки и правило отсутствия данных.
  2. Создайте в init два подмодуля Predict с разными контрактами.
  3. В forward передайте вопрос классификатору, затем найдите контекст по результату.
  4. При отсутствии контекста завершите вызов контролируемым отказом, не спрашивая модель о неизвестном правиле.
  5. Верните Prediction с answer и topic, проверьте обе части отдельно.

Для сетевого запуска установите пакет командой pip install dspy, подготовьте свой доступ к модели и задайте DSPY_MODEL в форме provider/model и DSPY_API_KEY в окружении. Исключение — код, который только проверяет локальные настройки без сетевого вызова. Шаблон ниже содержит вымышленные входные данные и не включает секретов; перед запуском сверьте, что выбранная вами модель подходит для задачи и разрешена правилами провайдера.

Готовый пример программы

import os
import dspy
dspy.configure(lm=dspy.LM(os.environ["DSPY_MODEL"], api_key=os.environ["DSPY_API_KEY"]))
KB = {"возврат": "Для возврата нужна заявка R-17.",
      "доставка": "Условия доставки уточняются в заказе."}

class FaqRouter(dspy.Module):
    def __init__(self):
        super().__init__()
        self.route = dspy.Predict("question -> topic")
        self.reply = dspy.Predict("question, context -> answer")

    def forward(self, question: str) -> dspy.Prediction:
        topic = self.route(question=question).topic.strip().lower()
        context = KB.get(topic)
        if context is None:
            return dspy.Prediction(topic=topic, answer="В справке нет данных.")
        answer = self.reply(question=question, context=context).answer
        return dspy.Prediction(topic=topic, answer=answer)

print(FaqRouter()(question="Как вернуть заказ?"))

Вход и ожидаемый результат

Вход — «Как вернуть заказ?». Если классификатор вернул ровно «возврат», ожидается topic=возврат и ответ со ссылкой на заявку R-17; если он вернул неизвестную метку, ожидается безопасное «В справке нет данных.». Ответ не предсказуем побуквенно, поэтому проверяйте смысл и выбранную ветвь раздельно.

Проверка качества, а не только запуска

Строковая классификация хрупка: модель может вернуть «Возврат заказа» вместо «возврат», тогда ветвь пойдёт в отказ. Не пытайтесь лечить всё бесконечным списком эвристик; типизированная сигнатура, набор разрешённых меток и тестовые данные лучше задают контракт. В учебном коде отказ безопаснее выдуманного условия. В рабочем приложении неизвестная тема может требовать эскалации оператору. Добавьте в контрольный набор прямой вопрос о доставке и вопрос, отсутствующий в KB, чтобы убедиться, что ветви не смешаны.

Разделение маршрутизации и ответа позволяет проверять каждый этап отдельно. Соберите для теста вопросы, где слово из одной категории встречается в контексте другой: такой пример выявляет наивное сопоставление по ключевому слову. Сохраните одновременно исходный вопрос, выбранную тему и конечный ответ, чтобы при ошибке определить, какой именно этап её вызвал. Если классификатор выбирает неподдерживаемую тему, модуль обязан перейти к предусмотренному отказу, а не создавать правдоподобный ответ из соседней справки. Изменяя второй шаг, не меняйте тестовые вопросы первого; иначе сравнение версий теряет смысл. В рабочем приложении исходные журналы вопросов клиентов должны обрабатываться с учётом их конфиденциальности.

Особые случаи и границы

Проверка факта R-17 должна обращаться к текущей версии документа, а не только к тексту ответа модели. Если правила поменялись, обновите справочник и тестовые ожидания согласованно, сохраняйте редакцию базы для расследования. Передача сырого context в LLM может раскрыть чувствительную часть внутренних документов: отбирайте только разрешённые фрагменты. Ответ на неизвестную тему возвращается без вызова второго Predict, что можно контролировать через журнал или инструментализацию отдельных стадий.

Критерии приёмки

  1. При известной теме возвращены topic и ответ по соответствующей записи.
  2. При неизвестной теме не вызывается генерация правила из воздуха.
  3. Тесты локализуют сбой маршрутизации отдельно от генерации.

Если критерий не выполнен, сначала разделите четыре причины: доступ к модели, контракт полей, поведение самого модуля и правильность предметного ответа. Один успешно выполненный Python-вызов подтверждает только часть этих условий. Сохраняйте минимальный безопасный пример и сравнивайте версии по одинаковым вопросам; не используйте реальные имена клиентов, токены или закрытые документы для отладки в общих журналах. Когда сценарий требует инференса, точные формулировки ответов и затраты зависят от выбранной модели, поэтому в статье заданы проверяемые свойства, а не выдуманные гарантии.

Типичные ошибки и чего не делать

  • Не считайте любую текстовую вариацию метки ключом словаря.
  • Не выдавайте отсутствие контекста за разрешение моделировать политику.
  • Не храните в KB настоящие приватные документы без контроля доступа.

Частые вопросы

Зачем super().init?

Это стандартная инициализация dspy.Module.

Что делает forward?

Связывает вызовы подмодулей и собирает результат.

Нужно ли вызывать второй Predict при пустой базе?

В показанной схеме нет.

Официальные источники

Частые вопросы

Зачем super().__init__?

Это стандартная инициализация dspy.Module.

Что делает forward?

Связывает вызовы подмодулей и собирает результат.

Нужно ли вызывать второй Predict при пустой базе?

В показанной схеме нет.

Читайте также

Комментарии

Пока тихо. Скажите первое слово