langchain - 💡(How to fix) Fix langchain-deepseek: Missing reasoning_content passback in _get_request_payload causes 400 error on multi-turn agent calls [2 comments, 2 participants]

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…
GitHub stats
langchain-ai/langchain#37174Fetched 2026-05-05 05:43:43
View on GitHub
Comments
2
Participants
2
Timeline
5
Reactions
0
Timeline (top)
commented ×2closed ×1labeled ×1unsubscribed ×1

When using langchain-deepseek with DeepSeek models that support thinking mode (e.g., deepseek-v4-flash, deepseek-r1), calling agent.invoke() fails on the second internal LLM call with a 400 error.

DeepSeek API requires that when an assistant message with reasoning_content is sent back to the API in a subsequent request, the reasoning_content field must be preserved in the message. However, ChatDeepSeek._get_request_payload() does not pass through reasoning_content from the original AIMessage.additional_kwargs to the API payload, causing the request to be rejected.

Error Message

openai.BadRequestError: Error code: 400
{'error': {'message': 'The `reasoning_content` in the thinking mode must be passed back to the API.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_request_error'}}

Root Cause

The issue is in ChatDeepSeek._get_request_payload(). Here's the data flow:

  1. First LLM call: DeepSeek returns an assistant message with reasoning_content + tool_calls
  2. _create_chat_result() correctly extracts reasoning_content and stores it in AIMessage.additional_kwargs["reasoning_content"]
  3. Second LLM call: BaseChatOpenAI._get_request_payload() calls _convert_message_to_dict() to serialize the previous assistant message
  4. _convert_message_to_dict() does not merge additional_kwargs into the top-level dict — it only extracts standard OpenAI fields (content, role, tool_calls)
  5. The resulting API payload is missing reasoning_content, causing DeepSeek API to return a 400 error

Fix Action

Fix

The _get_request_payload method needs to retrieve the original AIMessage objects and pass through reasoning_content from additional_kwargs to the API payload.

Patch applied to _get_request_payload in libs/partners/deepseek/langchain_deepseek/chat_models.py:

def _get_request_payload(
    self,
    input_: LanguageModelInput,
    *,
    stop: list[str] | None = None,
    **kwargs: Any,
) -> dict:
    payload = super()._get_request_payload(input_, stop=stop, **kwargs)

    # Get original messages to preserve reasoning_content from assistant messages.
    # DeepSeek requires reasoning_content from previous responses to be passed
    # back in subsequent requests, but _convert_message_to_dict drops it.
    messages = self._convert_input(input_).to_messages()

    for i, message in enumerate(payload["messages"]):
        if message["role"] == "assistant":
            if isinstance(message.get("content"), list):
                text_parts = [
                    block.get("text", "")
                    for block in message["content"]
                    if isinstance(block, dict) and block.get("type") == "text"
                ]
                message["content"] = "".join(text_parts) if text_parts else ""

            # Pass through reasoning_content from original AIMessage
            if i < len(messages) and isinstance(messages[i], AIMessage):
                reasoning = messages[i].additional_kwargs.get(
                    "reasoning_content"
                )
                if reasoning is not None:
                    message["reasoning_content"] = reasoning

        elif message["role"] == "tool" and isinstance(message["content"], list):
            message["content"] = json.dumps(message["content"])
    return payload

Code Example

openai.BadRequestError: Error code: 400
{'error': {'message': 'The `reasoning_content` in the thinking mode must be passed back to the API.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_request_error'}}

---

Traceback (most recent call last):
  File "test_agent.py", line 17, in <module>
    res = agent.invoke({"messages": [{"role": "user", "content": "..."}]})
  File ".../langgraph/pregel/main.py", line 3365, in invoke
    for chunk in self.stream(...)
  ...
  File ".../langchain_deepseek/chat_models.py", line 356, in _generate
    return super()._generate(...)
  File ".../langchain_openai/chat_models/base.py", line 1653, in _generate
    _handle_openai_bad_request(e)
openai.BadRequestError: Error code: 400 - {'error': {'message': 'The `reasoning_content` in the thinking mode must be passed back to the API.'}}

---

from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain.chat_models import init_chat_model

@tool(description="query weather")
def get_weather() -> str:
    return "sunny"

model = init_chat_model(model="deepseek-v4-flash")

agent = create_agent(
    model=model,
    tools=[get_weather],
    system_prompt="You are a helpful assistant.",
)

res = agent.invoke({
    "messages": [
        {"role": "user", "content": "What is the weather tomorrow?"},
    ]
})

for msg in res["messages"]:
    print(type(msg).__name__, msg.content)

---

def _get_request_payload(
    self,
    input_: LanguageModelInput,
    *,
    stop: list[str] | None = None,
    **kwargs: Any,
) -> dict:
    payload = super()._get_request_payload(input_, stop=stop, **kwargs)

    # Get original messages to preserve reasoning_content from assistant messages.
    # DeepSeek requires reasoning_content from previous responses to be passed
    # back in subsequent requests, but _convert_message_to_dict drops it.
    messages = self._convert_input(input_).to_messages()

    for i, message in enumerate(payload["messages"]):
        if message["role"] == "assistant":
            if isinstance(message.get("content"), list):
                text_parts = [
                    block.get("text", "")
                    for block in message["content"]
                    if isinstance(block, dict) and block.get("type") == "text"
                ]
                message["content"] = "".join(text_parts) if text_parts else ""

            # Pass through reasoning_content from original AIMessage
            if i < len(messages) and isinstance(messages[i], AIMessage):
                reasoning = messages[i].additional_kwargs.get(
                    "reasoning_content"
                )
                if reasoning is not None:
                    message["reasoning_content"] = reasoning

        elif message["role"] == "tool" and isinstance(message["content"], list):
            message["content"] = json.dumps(message["content"])
    return payload
RAW_BUFFERClick to expand / collapse

Description

When using langchain-deepseek with DeepSeek models that support thinking mode (e.g., deepseek-v4-flash, deepseek-r1), calling agent.invoke() fails on the second internal LLM call with a 400 error.

DeepSeek API requires that when an assistant message with reasoning_content is sent back to the API in a subsequent request, the reasoning_content field must be preserved in the message. However, ChatDeepSeek._get_request_payload() does not pass through reasoning_content from the original AIMessage.additional_kwargs to the API payload, causing the request to be rejected.

Error Message

openai.BadRequestError: Error code: 400
{'error': {'message': 'The `reasoning_content` in the thinking mode must be passed back to the API.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_request_error'}}

Full Traceback

Traceback (most recent call last):
  File "test_agent.py", line 17, in <module>
    res = agent.invoke({"messages": [{"role": "user", "content": "..."}]})
  File ".../langgraph/pregel/main.py", line 3365, in invoke
    for chunk in self.stream(...)
  ...
  File ".../langchain_deepseek/chat_models.py", line 356, in _generate
    return super()._generate(...)
  File ".../langchain_openai/chat_models/base.py", line 1653, in _generate
    _handle_openai_bad_request(e)
openai.BadRequestError: Error code: 400 - {'error': {'message': 'The `reasoning_content` in the thinking mode must be passed back to the API.'}}

Reproduction Code

This code is for testing purposes only.

from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain.chat_models import init_chat_model

@tool(description="query weather")
def get_weather() -> str:
    return "sunny"

model = init_chat_model(model="deepseek-v4-flash")

agent = create_agent(
    model=model,
    tools=[get_weather],
    system_prompt="You are a helpful assistant.",
)

res = agent.invoke({
    "messages": [
        {"role": "user", "content": "What is the weather tomorrow?"},
    ]
})

for msg in res["messages"]:
    print(type(msg).__name__, msg.content)

Root Cause

The issue is in ChatDeepSeek._get_request_payload(). Here's the data flow:

  1. First LLM call: DeepSeek returns an assistant message with reasoning_content + tool_calls
  2. _create_chat_result() correctly extracts reasoning_content and stores it in AIMessage.additional_kwargs["reasoning_content"]
  3. Second LLM call: BaseChatOpenAI._get_request_payload() calls _convert_message_to_dict() to serialize the previous assistant message
  4. _convert_message_to_dict() does not merge additional_kwargs into the top-level dict — it only extracts standard OpenAI fields (content, role, tool_calls)
  5. The resulting API payload is missing reasoning_content, causing DeepSeek API to return a 400 error

Fix

The _get_request_payload method needs to retrieve the original AIMessage objects and pass through reasoning_content from additional_kwargs to the API payload.

Patch applied to _get_request_payload in libs/partners/deepseek/langchain_deepseek/chat_models.py:

def _get_request_payload(
    self,
    input_: LanguageModelInput,
    *,
    stop: list[str] | None = None,
    **kwargs: Any,
) -> dict:
    payload = super()._get_request_payload(input_, stop=stop, **kwargs)

    # Get original messages to preserve reasoning_content from assistant messages.
    # DeepSeek requires reasoning_content from previous responses to be passed
    # back in subsequent requests, but _convert_message_to_dict drops it.
    messages = self._convert_input(input_).to_messages()

    for i, message in enumerate(payload["messages"]):
        if message["role"] == "assistant":
            if isinstance(message.get("content"), list):
                text_parts = [
                    block.get("text", "")
                    for block in message["content"]
                    if isinstance(block, dict) and block.get("type") == "text"
                ]
                message["content"] = "".join(text_parts) if text_parts else ""

            # Pass through reasoning_content from original AIMessage
            if i < len(messages) and isinstance(messages[i], AIMessage):
                reasoning = messages[i].additional_kwargs.get(
                    "reasoning_content"
                )
                if reasoning is not None:
                    message["reasoning_content"] = reasoning

        elif message["role"] == "tool" and isinstance(message["content"], list):
            message["content"] = json.dumps(message["content"])
    return payload

Environment

  • langchain-deepseek: 1.0.1
  • Model: deepseek-v4-flash (also affects deepseek-r1)
  • OS: Windows 11

Background

I'm a college student from Tianjin, China, currently learning the LangChain framework. I encountered this issue while building a simple agent for testing purposes.

extent analysis

TL;DR

The issue can be fixed by modifying the _get_request_payload method in ChatDeepSeek to preserve the reasoning_content from the original AIMessage objects.

Guidance

  • The root cause of the issue is that the reasoning_content field is not being passed through from the original AIMessage objects to the API payload in the _get_request_payload method.
  • To fix this, you need to modify the _get_request_payload method to retrieve the original AIMessage objects and pass through the reasoning_content from additional_kwargs to the API payload.
  • The provided patch for the _get_request_payload method should be applied to fix the issue.
  • After applying the patch, verify that the reasoning_content is being correctly passed through to the API payload by checking the messages field in the payload.

Example

The provided patch for the _get_request_payload method demonstrates how to modify the method to preserve the reasoning_content:

def _get_request_payload(
    self,
    input_: LanguageModelInput,
    *,
    stop: list[str] | None = None,
    **kwargs: Any,
) -> dict:
    # ... (rest of the method remains the same)
    for i, message in enumerate(payload["messages"]):
        if message["role"] == "assistant":
            # ... (rest of the logic remains the same)
            if i < len(messages) and isinstance(messages[i], AIMessage):
                reasoning = messages[i].additional_kwargs.get(
                    "reasoning_content"
                )
                if reasoning is not None:
                    message["reasoning_content"] = reasoning
    return payload

Notes

  • The issue only affects models that support thinking mode, such as deepseek-v4-flash and deepseek-r1.
  • The provided patch is specific to the `langchain-de

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

langchain - 💡(How to fix) Fix langchain-deepseek: Missing reasoning_content passback in _get_request_payload causes 400 error on multi-turn agent calls [2 comments, 2 participants]