AnRouter
Engineering

Fix DeepSeek 400 Error: reasoning_content in thinking mode must be passed back

DeepSeek returns HTTP 400 on multi-turn tool calls because standard OpenAI client libraries strip the non-standard reasoning_content field when replaying message history.

When calling DeepSeek thinking-enabled models (such as deepseek-v4.1-flash-1017), the first prompt generates an answer or executes a tool without issue. But as soon as your client sends the tool result back for the next turn, the API rejects the request with HTTP 400:

API Error: 400 {"error":{"message":"The `reasoning_content` in the thinking mode must be passed back to the API.","type":"invalid_request_error"}}

Some gateways report the same failure with different wording: Missing reasoning_content field in the assistant message at message index X or thinking is enabled but reasoning_content is missing in assistant tool call message. The root cause is identical — the reasoning text the model produced on the previous turn was dropped by the client when it assembled the message history.

Why the first turn succeeds but tool calls immediately fail with 400

In turn 1, you send only a user prompt (or system instructions). The model enters thinking mode, generates its internal reasoning process, and then produces either final text or a tool call.

DeepSeek's API contract enforces a strict rule: once thinking mode is active, any subsequent request that replays an assistant message containing tool calls must pass the original reasoning tokens back in that assistant message. Turn 1 does not fail because there is no prior assistant message to validate. The failure triggers at turn 2, right when your client submits the conversation history: the initial user message, the assistant's tool-call response, and the tool execution result.

Standard OpenAI message format strips reasoning_content

The standard OpenAI Chat Completions specification only defines role, content, and tool_calls on assistant messages:

{
  "role": "assistant",
  "content": null,
  "tool_calls": [
    {
      "id": "call_123",
      "type": "function",
      "function": {"name": "get_current_files", "arguments": "{}"}
    }
  ]
}

Most agent frameworks, client plugins, and proxy adapters adhere strictly to the OpenAI schema. When storing turn history or preparing the turn 2 payload, they discard unrecognized top-level fields. The returned reasoning_content string is quietly dropped. Other reasoning models (such as Kimi) enforce a similar requirement and fail with equivalent errors when proxy adapters strip reasoning blocks.

DeepSeek requires the replayed assistant message to retain the reasoning payload alongside the tool calls:

{
  "role": "assistant",
  "content": "",
  "reasoning_content": "Planning the tool call steps...",
  "tool_calls": [
    {
      "id": "call_123",
      "type": "function",
      "function": {"name": "get_current_files", "arguments": "{}"}
    }
  ]
}

On the Anthropic-compatible route the same rule applies in a different shape: DeepSeek expects a type: "thinking" block preserved inside the content array. Whichever route you use, if a proxy layer or client filters that content out, the next request fails with 400.

Upstream validation requires the key to be present on assistant messages in thinking mode. If the original reasoning text was already discarded by an upstream proxy, passing an empty string "reasoning_content": "" satisfies the schema validation on the OpenAI completions route, whereas omitting the field entirely results in an immediate 400 rejection.

Preserving and sending reasoning_content back in code and client configs

If you are orchestrating tool calls directly with the OpenAI Python SDK or an equivalent HTTP client, explicitly preserve reasoning_content when appending the assistant message to your message list:

assistant_msg = response.choices[0].message

messages.append({
    "role": "assistant",
    "content": assistant_msg.content or "",
    "reasoning_content": getattr(assistant_msg, "reasoning_content", ""),
    "tool_calls": [tc.model_dump() for tc in assistant_msg.tool_calls] if assistant_msg.tool_calls else None
})

messages.append({
    "role": "tool",
    "tool_call_id": assistant_msg.tool_calls[0].id,
    "content": "dir1/\nfile1.txt"
})

If you encounter this error in coding agents like Roo Code or Continue, check your provider configuration:

  • In Roo Code, select the native DeepSeek provider profile (v3.36.10 or later) rather than a generic OpenAI-compatible proxy profile. Native profiles preserve reasoning_content during replay, while generic OpenAI proxy paths strip it.
  • In Continue, set provider to deepseek instead of the generic openai provider, which drops non-standard fields when it serializes history.

Switching to DeepSeek partway through an existing session fails the same way: the earlier assistant messages were produced by another model and carry no reasoning field at all. Clear the context and start a new session.

When running tools like Claude Code, Codex, or the OpenAI SDK against upstream models, managing custom endpoint URLs and provider headers across disparate development tools adds operational friction. Routing through AnRouter lets you access DeepSeek and other supported models via standardized endpoints (POST /v1/chat/completions or POST /v1/messages), eliminating base URL sprawl while you keep client payloads compliant with upstream model specs.

Start building on AnRouterOne OpenAI-compatible endpoint for DeepSeek, GLM, MiniMax, MiMo, and Qwen — with usage-based pricing and no per-model contracts.

Get started
All articles