Гайд · TNWS AI
Как собрать двухшаговый DSPy Module для выбора темы и ответа FAQ
Связываем распознавание темы и ответ по контексту внутри 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 относится к ответчику. Два логических шага не гарантируют меньшую стоимость или большую точность без измерения.
Порядок действий
- Зафиксируйте две вымышленные записи справки и правило отсутствия данных.
- Создайте в init два подмодуля Predict с разными контрактами.
- В forward передайте вопрос классификатору, затем найдите контекст по результату.
- При отсутствии контекста завершите вызов контролируемым отказом, не спрашивая модель о неизвестном правиле.
- Верните 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, что можно контролировать через журнал или инструментализацию отдельных стадий.
Критерии приёмки
- При известной теме возвращены topic и ответ по соответствующей записи.
- При неизвестной теме не вызывается генерация правила из воздуха.
- Тесты локализуют сбой маршрутизации отдельно от генерации.
Если критерий не выполнен, сначала разделите четыре причины: доступ к модели, контракт полей, поведение самого модуля и правильность предметного ответа. Один успешно выполненный Python-вызов подтверждает только часть этих условий. Сохраняйте минимальный безопасный пример и сравнивайте версии по одинаковым вопросам; не используйте реальные имена клиентов, токены или закрытые документы для отладки в общих журналах. Когда сценарий требует инференса, точные формулировки ответов и затраты зависят от выбранной модели, поэтому в статье заданы проверяемые свойства, а не выдуманные гарантии.
Типичные ошибки и чего не делать
- Не считайте любую текстовую вариацию метки ключом словаря.
- Не выдавайте отсутствие контекста за разрешение моделировать политику.
- Не храните в KB настоящие приватные документы без контроля доступа.
Частые вопросы
Зачем super().init?
Это стандартная инициализация dspy.Module.
Что делает forward?
Связывает вызовы подмодулей и собирает результат.
Нужно ли вызывать второй Predict при пустой базе?
В показанной схеме нет.
Официальные источники
Частые вопросы
Зачем super().__init__?
Это стандартная инициализация dspy.Module.
Что делает forward?
Связывает вызовы подмодулей и собирает результат.
Нужно ли вызывать второй Predict при пустой базе?
В показанной схеме нет.
Читайте также
Как дать DSPy ReAct только читающий инструмент для ответа по справке
Подключаем read-only функцию словаря к ReAct, ограничиваем число итераций и проверяем источник ответа.
Как задать простой русскоязычный вопрос через DSPy Predict и Signature
Пишем сигнатуру question -> answer и проверяем поля Prediction на безопасном тесте справочного ответа.
Как оптимизировать инструкции DSPy через GEPA без утечки тестового набора
Разделяем примеры, метрику и reflection LM перед компиляцией GEPA; оцениваем итог на отдельном отложенном наборе.
Комментарии
Пока тихо. Скажите первое слово