close
Skip to content

langchain-deepseek drops reasoning_content when building the request → DeepSeek thinking mode returns 400 on multi-turn tool-call histories (still reproduces on v1.1.0) #39370

Description

Repro: DeepSeek thinking mode returns 400 — reasoning_content must be passed back

Title (suggestion)

langchain-deepseek drops reasoning_content when building the request → DeepSeek thinking mode returns 400 on multi-turn tool-call histories (still reproduces on v1.1.0)

Environment

  • OS: Windows 11
  • Python: 3.12.13
  • langchain: 1.2.12
  • langchain-core: 1.2.18
  • langchain-deepseek: 1.1.0 (latest, reproduces) / 1.0.1 (also reproduces)
  • langchain-openai: 1.1.11
  • openai: 2.26.0
  • Model: deepseek-v4-flash (thinking mode, enabled by default)
  • Base URL: https://api.deepseek.com

Steps to reproduce

pip install -U langchain-deepseek==1.1.0 langchain-openai
import os
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage

load_dotenv()  # provides DEEPSEEK_BASE_URL / DEEPSEEK_API_KEY / DEEPSEEK_MODEL

llm = init_chat_model(
    base_url=os.getenv("DEEPSEEK_BASE_URL"),
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    model=os.getenv("DEEPSEEK_MODEL"),  # deepseek-v4-flash
)

# Hand-crafted multi-turn history: assistant called a tool, tool replied.
# This is the standard shape an agent loop produces after a tool call.
messages = [
    HumanMessage("今天北京天气如何?"),
    AIMessage(
        content="",
        tool_calls=[{
            "name": "get_weather",
            "args": {"location": "北京"},
            "id": "call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s",
        }],
    ),
    ToolMessage(
        content="今天北京天气晴朗,万里无云~",
        tool_call_id="call_00_nUD2NC9QRN5Cg1GaoIkBJQ4s",
    ),
]

llm.invoke(messages)

Actual result

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'}}

Expected result

The request should be accepted. The hand-crafted history above is a legitimate tool-call turn; a real agent loop produces exactly this shape and should not hard-fail.

Root cause analysis

langchain_openai._convert_message_to_dict builds the outbound assistant message from content + tool_calls and never merges AIMessage.additional_kwargs (verified against the source of langchain-openai 1.4.2). So even if reasoning_content is present on the message — e.g. captured from a previous DeepSeek response via msg.additional_kwargs["reasoning_content"] — it is dropped before the request is sent.

DeepSeek's official Thinking Mode docs state that for tool-call turns, reasoning_content must participate in context concatenation and must be passed back in all subsequent turns. With thinking mode on, the API rejects the request when it is missing.

Workaround that confirms the diagnosis — disabling thinking mode makes the exact same history pass:

llm = init_chat_model(
    base_url=os.getenv("DEEPSEEK_BASE_URL"),
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    model=os.getenv("DEEPSEEK_MODEL"),
    extra_body={"thinking": {"type": "disabled"}},
)

Related, previously-closed PRs

The same fix was attempted multiple times but every PR was closed without merging:

The closing comment claimed the failure "no longer reproduces on langchain-deepseek 1.1.0" and that DeepSeek changed the contract so reasoning_content should be excluded from input. That claim is contradicted by:

  1. The linked docs page in the closing comment now 404s, and the current Thinking Mode docs still state reasoning_content must be passed back after tool calls.
  2. The exact 400 reproduces here on langchain-deepseek 1.1.0 (the version cited in the closing comment) against the live API on 2026-08-10.
  3. langchain-deepseek 1.1.0 contains no request-side reasoning_content handling — it only reads the field from responses into additional_kwargs; langchain-openai still drops additional_kwargs on outbound conversion.

Suggested fix

In _convert_message_to_dict (or a DeepSeek-specific subclass), forward AIMessage.additional_kwargs["reasoning_content"] into the outbound assistant message dict (empty string is accepted by the API as a placeholder when no reasoning text exists). This is exactly what the closed PRs above implemented.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions