AnRouter
工程实践

解决 DeepSeek 400 报错:The reasoning_content in the thinking mode must be passed back to the API

DeepSeek 思考模式要求多轮对话中上一步 assistant 产生的思考过程必须原样带回,而标准客户端在回传工具调用历史时默认丢弃了该字段。

你在用 Claude Code、Roo Code、Continue 或 OpenAI SDK 调用 DeepSeek 思考模型(如 deepseek-v4.1-flash-1017)时,第一轮提问完全正常, 但只要模型触发了工具调用(Tool Call)或进入第二轮对话,接口立即返回 400:

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

在某些网关中,该报错也写作 Missing reasoning_content field in the assistant message at message index X 或 thinking is enabled but reasoning_content is missing in assistant tool call message。根因完全一致:上一步模型输出的思考内容,被客户端在拼装历史消息时丢掉了。

为什么第一轮正常,一进入工具调用就报 400

第一轮请求里只有用户消息(或再加上系统指令)。模型进入思考模式,生成内部推理过程, 然后输出最终文本或一条工具调用指令。

DeepSeek 的接口约定了一条硬规则:思考模式一旦开启,后续请求中凡是带工具调用的 assistant 历史消息,都必须把当初那段推理内容原样带回。第一轮之所以不报错,是因为还没有需要校验的 assistant 历史消息。错误触发在第二轮——客户端提交完整对话历史的那一刻:最初的用户消息、 assistant 的工具调用响应,以及工具执行结果。

OpenAI 标准消息格式把 reasoning_content 剥离了

标准 OpenAI Chat Completions 规范在 assistant 消息上只定义了 role、 content 和 tool_calls 三个字段:

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

绝大多数 Agent 框架、客户端插件和代理适配层都严格遵循 OpenAI schema。它们在保存对话历史 或拼装第二轮请求体时,会直接丢弃不认识的顶层字段,返回的 reasoning_content 字符串就这样被悄悄剥离。其他推理模型(如 Kimi)有相同约束,代理层过滤掉推理块时会报出等价的错误。

DeepSeek 要求回传的 assistant 消息在工具调用之外还要保留推理内容:

{
  "role": "assistant",
  "content": "",
  "reasoning_content": "正在规划工具调用步骤...",
  "tool_calls": [
    {
      "id": "call_123",
      "type": "function",
      "function": {"name": "get_current_files", "arguments": "{}"}
    }
  ]
}

走 Anthropic 协议端点时,同一条规则换了个形状:DeepSeek 要求在 content 数组里 保留 type: "thinking" 块。无论走哪条路由,只要代理层或客户端把这部分内容过滤掉, 下一轮请求必报 400。

上游校验只要求思考模式下的 assistant 消息带有这个键。如果原始推理文本已经被上游代理丢弃, 在 OpenAI completions 路由上回传空字符串 "reasoning_content": "" 也能通过 schema 校验;完全省略该字段则会被立即拒绝。

在代码和客户端配置里把字段带回

如果你用 OpenAI Python SDK 或等价的 HTTP 客户端自己调度工具循环,把 assistant 消息追加进 历史时必须显式保留 reasoning_content:

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"
})

如果是在 Roo Code、Continue 这类编程 Agent 里遇到这个报错,检查你的 provider 配置:

  • Roo Code 里选择内置的 DeepSeek provider(v3.36.10 及以上),不要用通用的 OpenAI 兼容代理配置。内置 provider 在回放历史时会保留 reasoning_content, 通用 OpenAI 代理路径则会剥离。
  • Continue 里把 provider 设为 deepseek,不要用通用的 openai provider——后者在序列化历史时会丢掉非标准字段。

会话中途从其他模型切到 DeepSeek 也会同样失败:早先的 assistant 消息由另一个模型产生, 根本不带推理字段。这种情况只能清空上下文、重开会话。

在 Claude Code、Codex 或 OpenAI SDK 之间切换调用上游模型时,为每个工具单独维护 base URL 和认证头是实打实的运维负担。通过 AnRouter 接入 DeepSeek 及其他在售模型,可以用标准端点(POST /v1/chat/completions 或 POST /v1/messages)统一接入,省掉 base URL 的碎片化管理;请求消息体仍需你在 客户端按上游模型规范保证完整。

开始使用 AnRouter一个 OpenAI 兼容端点接入 DeepSeek、GLM、MiniMax、MiMo、Qwen,按用量计费,无需逐个模型签约。

立即开始
返回文章列表