API Error: 400 The `content[].thinking` in the thinking mode must be passed back to the API.
目前已确认,该问题不是普通的 thinking 缓存失效,而是:
DeepSeek 返回了一个“存在但内容为空”的 thinking block,CCX 将其当成“不存在 thinking block”并丢弃,导致下一轮工具结果请求没有完整回传原始 assistant content。
因此,这个错误主要发生在:
thinking="" + tool_use
普通文本回复、非空 thinking,以及没有工具调用的请求通常不会触发,所以表面上看起来是“偶发错误”。
环境
CCX:v2.9.37
Claude Code:2.1.215
系统:Windows 10.0.26200.8875
reasoningMapping:各模型均为 high
稳定触发链路
1. DeepSeek 返回 assistant response
结构类似:
{
"role": "assistant",
"content": [
{
"type": "thinking",
"thinking": ""
},
{
"type": "tool_use",
"id": "tool_xxx",
"name": "some_tool",
"input": {}
}
]
}
注意:这里并不是没有 thinking block,而是:
thinking block 存在,但 thinking 字段为空字符串
2. Claude Code 执行工具
Claude Code 执行对应工具,然后发起下一轮包含 tool_result 的请求。
3. CCX 处理历史 assistant content
CCX 当前会过滤掉:
{
"type": "thinking",
"thinking": ""
}
于是发给 DeepSeek 的历史 assistant content 中只剩下 tool_use。
4. DeepSeek 拒绝下一轮请求
DeepSeek 检测到 thinking 模式下,前一轮 assistant response 的 thinking block 没有被完整回传,因此返回:
HTTP 400
The `content[].thinking` in the thinking mode must be passed back to the API.
期望行为
CCX 应区分以下三种状态:
1. response 中完全没有 thinking block
2. response 中存在 thinking block,但 thinking == ""
3. response 中存在非空 thinking block
目前第 1 和第 2 种状态被折叠成了同一个状态。
当 DeepSeek 原始 assistant response 中确实存在:
{
"type": "thinking",
"thinking": ""
}
并且后面跟有 tool_use 时,下一轮包含 tool_result 的请求应继续原样携带该 thinking block。
期望发送给 DeepSeek 的历史 assistant content 类似:
{
"role": "assistant",
"content": [
{
"type": "thinking",
"thinking": ""
},
{
"type": "tool_use",
"id": "tool_xxx",
"name": "some_tool",
"input": {}
}
]
}
而不是只保留:
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "tool_xxx",
"name": "some_tool",
"input": {}
}
]
}
建议实现
1. Collector 记录 thinking block 是否实际出现
ClaudeStreamCollector 不应只通过最终拼接后的 thinking 字符串是否为空来判断 thinking 是否存在。
建议额外记录类似状态:
ThinkingBlockSeen bool
ThinkingText string
收到 thinking block 时:
ThinkingBlockSeen = true
即使:
ThinkingText == ""
也必须保留“该 block 确实出现过”的事实。
2. 缓存区分 absent 和 present-but-empty
缓存结构需要能够表达:
absent
present-empty
present-non-empty
实现方式可以是:
增加 thinking_block_present 字段
使用 nullable 状态
使用明确的结构体字段
使用内部 sentinel,但序列化回请求时恢复为空字符串
不建议继续仅通过:
strings.TrimSpace(thinking) != ""
判断是否应该缓存。
3. 下一轮按原始结构回传
当满足以下条件时:
- assistant response 中实际出现过 thinking block
- response 同时包含 tool_use
- 下一轮请求包含对应 tool_result
- session 和 assistant/tool-use fingerprint 匹配
CCX 应在历史 assistant content 中恢复:
{
"type": "thinking",
"thinking": ""
}
并尽可能保持原始 content block 顺序。
4. 不要对完全缺失 thinking 的响应伪造 block
以下情况:
{
"role": "assistant",
"content": [
{
"type": "tool_use"
}
]
}
与空 thinking block 不同。
如果上游响应里完全没有 thinking block,CCX 不应仅因为出现 tool_use 就自动伪造:
{
"type": "thinking",
"thinking": ""
}
修复重点应是保留 block 的“存在性”,而不是为所有工具调用添加空 thinking。
5. 不要让通用空文本清理误删 thinking block
stripEmptyTextBlocks 可以继续处理普通的:
{
"type": "text",
"text": ""
}
但 thinking 是协议状态的一部分,不应与普通空 text block 使用完全相同的过滤语义。
问题描述
Claude Code 通过 CCX 接入 DeepSeek Anthropic Messages 兼容接口并开启 thinking 后,会间歇性出现以下错误: