Гайд · TNWS AI
Почему dspy.context не меняет модель внутри стороннего Python-потока
Объясняем границу контекста DSPy: локальная подмена LM, возврат глобальной модели и поведение нового потока.
Задача и применимость
Актуальный поисковый вопрос: «почему dspy.context lm не действует в ThreadPoolExecutor». Локализовать случай, когда сравнение моделей в параллельном коде неожиданно вызывает глобальную модель. Материал основан на текущем официальном руководстве DSPy, проверенном 20 сентября 2026 года, и предназначен для Python 3.10+ с совместимым установленным dspy. У разных провайдеров разные доступные модели, лимиты и права: укажите собственный реально работающий provider/model в переменных окружения. Фиксируйте установленную версию пакета при повторении опыта, поскольку API может меняться.
Как устроено решение
Официальное объяснение DSPy разделяет глобальный configure и scoped context. configure изменяет настройки процесса при старте, а context временно перекрывает их через ContextVar в текущем контексте выполнения. Он не предназначен для постоянного переключения всей многопоточной программы. После выхода из блока прежняя LM снова доступна. Если в тесте используются разные модели, сначала проверьте identity настроек внутри и снаружи блока — это отделит ошибку области видимости от различий генераций.
Порядок действий
- Настройте глобальную LM один раз при старте процесса.
- Создайте другую LM с доступом к тому же или другому подтверждённому endpoint.
- Выполните контрольный вызов с with dspy.context(lm=alternate) и проверьте settings.lm внутри блока.
- После блока убедитесь, что глобальная модель восстановилась.
- При использовании сторонних Python-потоков не предполагайте наследование override; передавайте настройки работникам явно либо пользуйтесь механизмом DSPy.
Для сетевого запуска установите пакет командой pip install dspy, подготовьте свой доступ к модели и задайте DSPY_MODEL в форме provider/model и DSPY_API_KEY в окружении. Исключение — код, который только проверяет локальные настройки без сетевого вызова. Шаблон ниже содержит вымышленные входные данные и не включает секретов; перед запуском сверьте, что выбранная вами модель подходит для задачи и разрешена правилами провайдера.
Готовый пример программы
import os
import dspy
global_lm = dspy.LM(os.environ["DSPY_MODEL"], api_key=os.environ["DSPY_API_KEY"])
alternate = dspy.LM(os.environ["DSPY_ALT_MODEL"], api_key=os.environ["DSPY_ALT_API_KEY"])
dspy.configure(lm=global_lm)
assert dspy.settings.lm is global_lm
with dspy.context(lm=alternate):
assert dspy.settings.lm is alternate
# Вызывайте здесь программу для контролируемого сравнения.
assert dspy.settings.lm is global_lm
print("Локальная подмена и восстановление проверены")
Вход и ожидаемый результат
Вход — два валидных клиента LM. Ожидаемый детерминированный результат кода — сообщение о локальной подмене и восстановлении без вызова провайдера. Затем задайте двум моделям один и тот же вымышленный вопрос в разных контекстах; сами ответы сравнивайте отдельно. Пример не доказывает, что bare ThreadPoolExecutor автоматически унаследует override: согласно документации, это не так.
Проверка качества, а не только запуска
В документации отдельно уточнено, что await и asyncio.create_task наследуют контекст ожидаемым образом, а обычный threading.Thread или голый ThreadPoolExecutor не получают override автоматически. Поэтому результаты запросов в worker-потоке могут неожиданно относиться к глобальной LM. Для своей очереди задач явно пронесите нужную конфигурацию и протестируйте её в worker; документация называет dspy.Parallel альтернативой с передачей контекста. Не пытайтесь перенастраивать configure одновременно из каждого потока: руководство описывает ограничение владельца настроек.
Контекст модели стоит тестировать с разными идентификаторами конфигурации, не делая дорогостоящих сетевых запросов. Зафиксируйте значение в основном потоке до входа в блок, внутри блока и после выхода: ожидается возврат исходного состояния. Затем создайте обычный поток и проверьте его независимо; не переносите туда ожидание, основанное на поведении асинхронной задачи. Если приложение передаёт работу в пул потоков, явно задавайте конфигурацию в каждой рабочей единице и отдельно проверяйте очистку после исключения. Такой тест выявляет утечки настроек между запросами клиентов. Не используйте один глобальный переключатель для разных пользователей одновременно, если изоляция конкретного способа запуска не проверена.
Особые случаи и границы
Параметры temperature, max_tokens, api_base и повторов живут у конкретного объекта dspy.LM, а не становятся произвольными глобальными настройками context. Если вы сравниваете две LM, фиксируйте эти параметры для честного сопоставления и измеряйте фактическое качество на одном отложенном наборе. Для публичного сервиса проверьте, что данные одного пользователя не попадают по ошибке в чужой выбранный провайдер. Тест без сетевого вызова защищает от случайного расхода API-лимита на этапе диагностики области действия.
Критерии приёмки
- Внутри блока используется alternate, после блока — global_lm.
- Обычный сторонний поток не считается наследующим override без проверки.
- Реальный вызов выполняется лишь после фиксации маршрута модели.
Если критерий не выполнен, сначала разделите четыре причины: доступ к модели, контракт полей, поведение самого модуля и правильность предметного ответа. Один успешно выполненный Python-вызов подтверждает только часть этих условий. Сохраняйте минимальный безопасный пример и сравнивайте версии по одинаковым вопросам; не используйте реальные имена клиентов, токены или закрытые документы для отладки в общих журналах. Когда сценарий требует инференса, точные формулировки ответов и затраты зависят от выбранной модели, поэтому в статье заданы проверяемые свойства, а не выдуманные гарантии.
Типичные ошибки и чего не делать
- Не вызывать configure одновременно в разных потоках.
- Не приписывать ThreadPoolExecutor поведение asyncio.create_task.
- Не считать локальную подмену изменением глобальной модели.
Частые вопросы
Сохраняется ли context после выхода из with?
Нет, override снимается.
Наследует ли asyncio.create_task контекст?
В документации — да.
Наследует ли обычный новый поток?
Нет, передавайте настройки явно.
Официальные источники
Частые вопросы
Сохраняется ли context после выхода из with?
Нет, override снимается.
Наследует ли asyncio.create_task контекст?
В документации — да.
Наследует ли обычный новый поток?
Нет, передавайте настройки явно.
Читайте также
Как дать DSPy ReAct только читающий инструмент для ответа по справке
Подключаем read-only функцию словаря к ReAct, ограничиваем число итераций и проверяем источник ответа.
Как задать простой русскоязычный вопрос через DSPy Predict и Signature
Пишем сигнатуру question -> answer и проверяем поля Prediction на безопасном тесте справочного ответа.
Как оптимизировать инструкции DSPy через GEPA без утечки тестового набора
Разделяем примеры, метрику и reflection LM перед компиляцией GEPA; оцениваем итог на отдельном отложенном наборе.
Комментарии
Пока тихо. Скажите первое слово