В процессе работы с фреймворком LangChain была обнаружена существенная проблема в чат-классах (ChatOpenAI, ChatDeepSeek и др.) при интеграции с различными провайдерами и агрегаторами LLM. Ни один из них не сохраняет содержимое блока рассуждений (reasoning content) в финальном ответе, что увеличивает время ожидания ответа пользователем и негативно сказывается на UX ИИ-приложений, использующих CoT-модели.

В данной статье я расскажу, как можно решить эту проблему на примере модели stepfun/step-3.5-flash и провайдера polza.ai.

Почему все так? Путаница в стандартах

При разработке ИИ-ассистента для инвестирования я столкнулся с этой проблемой. Первоначально я пытался решить её с помощью официальной библиотеки OpenAI, однако такой подход не давал возможности в полной мере использовать фреймворки LangChain и LangGraph в моём MVP. Я погрузился в эту проблему глубже, и вот что узнал:

В одном из баг репортов разработчики прокомментировали это так:

ChatOpenAI не будет добавляться поддержка полей ответа reasoning_contentreasoningreasoning_details, или других полей, специфичных для конкретного поставщика услуг.

ChatOpenAIбудет ориентирован на официальную спецификацию API завершения чата OpenAI . reasoning_contentне является частью этой спецификации — это нестандартное расширение, разработанное DeepSeek и принятое другими поставщиками (OpenRouter, vLLM, xAI и т. д.), которые используют формат завершения чата. Каждый поставщик также реализует его немного по-разному ( reasoning_contentvs reasoningvs reasoning_details), и эти поля продолжают развиваться (например, vLLM перешел с reasoning_contentнаreasoning ).

Добавление поддержки этих полей ChatOpenAIозначало бы следующее:

  • Поддержание постоянно растущего набора сопоставлений полей, специфичных для каждого поставщика услуг.

  • Мы неявно обязуемся отслеживать изменения у поставщиков услуг, которые мы не контролируем.

  • Создание, ChatOpenAIпо сути, универсального адаптера, что не является его предназначением.

    ...

Также в своем ответе они предлагают использовать другие чаты (ChatDeepSeek или ChatOpenRouter), НО большинство провайдеров используют стандарт OpenAI Chat Completion API. Получается замкнутый круг: одни утверждают, что в официальной документации такого поля нет, другие используют его.

Как все исправить?

Для решения данной проблемы я полез в исходный код langchain_openai/chat_models/base.py. Тут нужно найти функции convert_dict_to_message и convert_delta_to_message_chunkи добавить следующее:

def  _convert_dict_to_message (...):
  ...
        content = _dict.get("content", "") or ""
        reasoning = _dict.get("reasoning", "") or "" # add
        additional_kwargs: dict = {}
        if reasoning: # add
            additional_kwargs['reasoning_content'] = reasoning # add
        if function_call := _dict.get("function_call"):
            additional_kwargs["function_call"] = dict(function_call)
        tool_calls = []
  ...

  def _convert_delta_to_message_chunk (...):
    ...
    id_ = _dict.get("id")
    role = cast(str, _dict.get("role"))
    content = cast(str, _dict.get("content") or "")
    reasoning = cast(str, _dict.get("reasoning", "") or "") # add
    additional_kwargs: dict = {}
    if reasoning: # add
        additional_kwargs['reasoning_content'] = reasoning # add

Благодаря такому способу, блок рассуждений будет сохраняться в additional_kwargs как в обычном, так и в потоковом вызове.

Пример:

from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    model="stepfun/step-3.5-flash",
    openai_api_base="https://api.polza.ai/v1",
    openai_api_key="your_api_key",
)

for chunk in model.stream('Привет!'):
    if "reasoning_content" in chunk.additional_kwargs:
        print(chunk.additional_kwargs['reasoning_content'], end="", flush=True)
    else:
        print(chunk.content, end="", flush=True)

Вывод

В этой статье мы разобрали проблему потери блока рассуждений (reasoning content) в CoT-моделях при использовании фреймворка LangChain. Официальное исправление данной проблемы выйдет ещё не скоро, но ждать не обязательно. Пара изменений в base.py и мы забираем reasoning_content в additional_kwargs.

Патч лёгкий, не ломает архитектуру. Это изменение, которое уже сейчас может помочь разработчикам ИИ-приложений.

Если вы обошли проблему иначе, знаете более чистый способ или столкнулись с подводными камнями - кидайте в комментарии. Буду рад обсудить и дополнить статью!

Комментарии (0)


  1. kardanShurup
    20.04.2026 13:02

    В решении используется поле reasoning из словаря dict. Однако вы сами цитируете комментарий разработчиков о том, что разные провайдеры называют это поле по-разному (reasoningcontentreasoningreasoning_details). Гарантирует ли предложенный патч работоспособность с vLLM (который перешел на reasoning) или с xAI, или же код жестко завязан на специфику stepfun/step-3.5-flash и polza.ai?


    1. RadAI Автор
      20.04.2026 13:02

      Спасибо за вопрос!

      В данной статье решение рассматривалось в рамках работы с провайдером polza.ai. Конечно можно сделать и более общее решение, чтобы подходило для всех.


      1. Koster-Valdaw
        20.04.2026 13:02

        Немного не по теме, но вызывает доверие, что вы написали минимум абзацев - строго по делу.

        Провайдеры типа polza.ai (или конкретно polza.ai) - каким либо образом влияют на результаты нативных моделей, надстраиваются доп. системными промптами со стороны провайдера?

        Конечно, вряд ли мы знаем начинку - но хотя бы на примере вашей практики.


        1. RadAI Автор
          20.04.2026 13:02

          Спасибо за вопрос!

          Я думаю, что вряд ли провайдер (по крайней мере polza.ai) будет надстраивать скрытые системные промпты, но такое, в целом, возможно.