fix: 修复 Claude API 400 错误:tool_result/tool_use 不匹配问题

错误信息:
     messages.14.content.0: unexpected tool_use_id found in tool_result blocks: toolu_01Ekn6YJMk7yt7hNcn4PZxtM.
     Each tool_result block must have a corresponding tool_use block in the previous message.
根本原因:
     文件: src/services/claudeRelayService.js 中的 _enforceCacheControlLimit() 方法
原实现问题:
     1. 当 cache_control 块超过 4 个时,直接删除整个内容块(splice)
     2. 这会删除 tool_use 块,导致后续的 tool_result 找不到对应的 tool_use_id
     3. 也会删除用户的文本消息,导致上下文丢失
重要背景(官方文档确认)
     根据 Claude API 官方文档:
     - 最多可定义 4 个 cache_control 断点
     - 如果超过限制,API 不会报错,只是静默地忽略多余的断点
     - "20 个块回溯窗口" 是缓存命中检查的范围,与断点数量限制无关
     因此,这个函数的原始设计(删除内容块)是不必要且有害的。
修复方案:
     保留函数但修改行为:只删除 cache_control 属性,保留内容本身
修改位置;
     文件: src/services/claudeRelayService.js
修改内容:
     将 removeFromMessages() 和 removeFromSystem() 函数从"删除整个内容块"改为"只删除 cache_control 属性":
     // 修改前:直接删除整个内容块
     message.content.splice(contentIndex, 1)
     // 修改后:只删除 cache_control 属性,保留内容
     delete contentItem.cache_control
效果对比;
     | 场景         | 修复前            | 修复后            |
     |------------|----------------|----------------|
     | 用户文本消息     |  整个消息被删除      |  保留消息,只移除缓存标记 |
     | tool_use 块 |  被删除导致 400 错误 |  保留完整内容       |
     | system 提示词 |  整个提示词被删除     |  保留提示词内容      |
     | 缓存功能       | ⚠️ 强制限制        |  降级(不缓存但内容完整) |
This commit is contained in:
Dave
2025-11-29 17:46:48 +08:00
parent 876b126ce0
commit 249e256360

View File

@@ -789,7 +789,8 @@ class ClaudeRelayService {
return total
}
const removeFromMessages = () => {
// 只移除 cache_control 属性,保留内容本身,避免丢失用户消息
const removeCacheControlFromMessages = () => {
if (!Array.isArray(body.messages)) {
return false
}
@@ -803,12 +804,8 @@ class ClaudeRelayService {
for (let contentIndex = 0; contentIndex < message.content.length; contentIndex += 1) {
const contentItem = message.content[contentIndex]
if (contentItem && contentItem.cache_control) {
message.content.splice(contentIndex, 1)
if (message.content.length === 0) {
body.messages.splice(messageIndex, 1)
}
// 只删除 cache_control 属性,保留内容
delete contentItem.cache_control
return true
}
}
@@ -817,7 +814,8 @@ class ClaudeRelayService {
return false
}
const removeFromSystem = () => {
// 只移除 cache_control 属性,保留 system 内容
const removeCacheControlFromSystem = () => {
if (!Array.isArray(body.system)) {
return false
}
@@ -825,12 +823,8 @@ class ClaudeRelayService {
for (let index = 0; index < body.system.length; index += 1) {
const systemItem = body.system[index]
if (systemItem && systemItem.cache_control) {
body.system.splice(index, 1)
if (body.system.length === 0) {
delete body.system
}
// 只删除 cache_control 属性,保留内容
delete systemItem.cache_control
return true
}
}
@@ -841,12 +835,13 @@ class ClaudeRelayService {
let total = countCacheControlBlocks()
while (total > MAX_CACHE_CONTROL_BLOCKS) {
if (removeFromMessages()) {
// 优先从 messages 中移除 cache_control再从 system 中移除
if (removeCacheControlFromMessages()) {
total -= 1
continue
}
if (removeFromSystem()) {
if (removeCacheControlFromSystem()) {
total -= 1
continue
}