langchain - 💡(How to fix) Fix ChatOpenAI drops reasoning_content from OSS models (like vLLM/DeepSeek) during AIMessage conversion

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

When using langchain_openai.ChatOpenAI to connect to an OpenAI-compatible endpoint that serves open-source reasoning models (e.g., via vLLM or Ollama), the reasoning_content field returned in the raw JSON response is silently dropped.

Many users serve models like DeepSeek-R1 via vLLM, which utilizes the reasoning_content field for the model's thought process. However, because _convert_dict_to_message in langchain_openai/chat_models/base.py hardcodes the extraction of content, function_call, tool_calls, and audio, any additional non-standard fields like reasoning_content are destroyed before the AIMessage is returned.

Root Cause

Many users serve models like DeepSeek-R1 via vLLM, which utilizes the reasoning_content field for the model's thought process. However, because _convert_dict_to_message in langchain_openai/chat_models/base.py hardcodes the extraction of content, function_call, tool_calls, and audio, any additional non-standard fields like reasoning_content are destroyed before the AIMessage is returned.

Fix Action

Workaround

Currently, users are forced to use ChatDeepSeek (which overrides this method to preserve reasoning_content), even if the model isn't strictly a DeepSeek model, just to preserve the vLLM response format. Passing additional_kwargs dynamically in the base class would resolve this limitation natively.

Code Example

1. Initialize `ChatOpenAI` pointing to a local `vLLM` server serving a reasoning model (e.g., `DeepSeek-R1`).

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="openai/gpt-oss-20b",
    openai_api_base="http://localhost:8000/v1",
    openai_api_key="EMPTY"
)

response = llm.invoke("What is 10 + 10? Please think step by step.")

2. The raw response from vLLM contains `reasoning_content` in the `choices[0].message` payload.
3. Check `response.additional_kwargs`. The `reasoning_content` is missing, and `response.content` only contains the final output without the reasoning trace.

---

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="deepseek-r1-20b",
    openai_api_base="http://localhost:8000/v1",
    openai_api_key="EMPTY"
)

response = llm.invoke("What is 10 + 10? Please think step by step.")

---

if role == "assistant":
        content = _dict.get("content", "") or ""
        additional_kwargs: dict = {}
        
        if function_call := _dict.get("function_call"):
            additional_kwargs["function_call"] = dict(function_call)
            
        tool_calls = []
        invalid_tool_calls = []
        if raw_tool_calls := _dict.get("tool_calls"):
            # ... parses tool calls ...

        if audio := _dict.get("audio"):
            additional_kwargs["audio"] = audio
            
        # reasoning_content is ignored!
            
        return AIMessage(
            content=content,
            additional_kwargs=additional_kwargs,
            name=name,
            id=id_,
            tool_calls=tool_calls,
            invalid_tool_calls=invalid_tool_calls,
        )
RAW_BUFFERClick to expand / collapse

Submission checklist

  • This is a bug, not a usage question.
  • I added a clear and descriptive title that summarizes this issue.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain rather than my code.
  • The bug is not resolved by updating to the latest stable version of LangChain (or the specific integration package).
  • This is not related to the langchain-community package.
  • I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.

Package (Required)

  • langchain
  • langchain-openai
  • langchain-anthropic
  • langchain-classic
  • langchain-core
  • langchain-model-profiles
  • langchain-tests
  • langchain-text-splitters
  • langchain-chroma
  • langchain-deepseek
  • langchain-exa
  • langchain-fireworks
  • langchain-groq
  • langchain-huggingface
  • langchain-mistralai
  • langchain-nomic
  • langchain-ollama
  • langchain-openrouter
  • langchain-perplexity
  • langchain-qdrant
  • langchain-xai
  • Other / not sure / general

Related Issues / PRs

https://github.com/langchain-ai/langchain/issues/37347

Reproduction Steps / Example Code (Python)

1. Initialize `ChatOpenAI` pointing to a local `vLLM` server serving a reasoning model (e.g., `DeepSeek-R1`).

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="openai/gpt-oss-20b",
    openai_api_base="http://localhost:8000/v1",
    openai_api_key="EMPTY"
)

response = llm.invoke("What is 10 + 10? Please think step by step.")

2. The raw response from vLLM contains `reasoning_content` in the `choices[0].message` payload.
3. Check `response.additional_kwargs`. The `reasoning_content` is missing, and `response.content` only contains the final output without the reasoning trace.

Description

When using langchain_openai.ChatOpenAI to connect to an OpenAI-compatible endpoint that serves open-source reasoning models (e.g., via vLLM or Ollama), the reasoning_content field returned in the raw JSON response is silently dropped.

Many users serve models like DeepSeek-R1 via vLLM, which utilizes the reasoning_content field for the model's thought process. However, because _convert_dict_to_message in langchain_openai/chat_models/base.py hardcodes the extraction of content, function_call, tool_calls, and audio, any additional non-standard fields like reasoning_content are destroyed before the AIMessage is returned.

Steps to Reproduce

  1. Initialize ChatOpenAI pointing to a local vLLM server serving a reasoning model (e.g., DeepSeek-R1).
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="deepseek-r1-20b",
    openai_api_base="http://localhost:8000/v1",
    openai_api_key="EMPTY"
)

response = llm.invoke("What is 10 + 10? Please think step by step.")
  1. The raw response from vLLM contains reasoning_content in the choices[0].message payload.
  2. Check response.additional_kwargs. The reasoning_content is missing, and response.content only contains the final output without the reasoning trace.

Expected Behavior

ChatOpenAI should either:

  1. Dump unhandled payload fields into additional_kwargs (e.g., additional_kwargs["reasoning_content"] = _dict.get("reasoning_content")) so that users can still access custom fields.
  2. Provide a parameter like include_extra=True to preserve extra fields from the raw provider response.

Code snippet where the issue occurs:

In _convert_dict_to_message (langchain_openai/chat_models/base.py):

    if role == "assistant":
        content = _dict.get("content", "") or ""
        additional_kwargs: dict = {}
        
        if function_call := _dict.get("function_call"):
            additional_kwargs["function_call"] = dict(function_call)
            
        tool_calls = []
        invalid_tool_calls = []
        if raw_tool_calls := _dict.get("tool_calls"):
            # ... parses tool calls ...

        if audio := _dict.get("audio"):
            additional_kwargs["audio"] = audio
            
        # reasoning_content is ignored!
            
        return AIMessage(
            content=content,
            additional_kwargs=additional_kwargs,
            name=name,
            id=id_,
            tool_calls=tool_calls,
            invalid_tool_calls=invalid_tool_calls,
        )

Workaround

Currently, users are forced to use ChatDeepSeek (which overrides this method to preserve reasoning_content), even if the model isn't strictly a DeepSeek model, just to preserve the vLLM response format. Passing additional_kwargs dynamically in the base class would resolve this limitation natively.

System Info

  • langchain-openai==1.2.2
  • langchain-core==1.4.1
  • langchain==1.3.4

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING