fix: 让消息 cache_control 改写默认关闭

This commit is contained in:
shaw
2026-05-11 21:26:41 +08:00
parent 297b54d066
commit 9377c96746
15 changed files with 226 additions and 30 deletions
+40 -18
View File
@@ -1251,13 +1251,11 @@ func (s *GatewayService) applyClaudeCodeOAuthMimicryToBody(
body, _ = normalizeClaudeOAuthRequestBody(body, model, normalizeOpts)
// Phase D+E+F: messages cache 策略 + 工具名混淆 + tools[-1] 断点
// 对齐 Parrot transform_request 里剩余的字段级改写。三步顺序有语义约束:
// 1) strip:先清除客户端的 messages[*].cache_control(多轮稳定性)
// 2) breakpoints:再注入 2 个断点(最后一条 + 倒数第二个 user turn
// 3) tool rewrite:最后改 tools[*].name / tool_choice.name 并在 tools[-1]
// 对齐 Parrot transform_request 里剩余的字段级改写。顺序有语义约束:
// 1) messages cache:仅在配置开启时清除客户端断点并注入代理断点
// 2) tool rewrite:最后改 tools[*].name / tool_choice.name 并在 tools[-1]
// 上打断点;mapping 存入 gin.Context 供响应侧 bytes.Replace 还原。
body = stripMessageCacheControl(body)
body = addMessageCacheBreakpoints(body)
body = s.rewriteMessageCacheControlIfEnabled(ctx, body)
if rw := buildToolNameRewriteFromBody(body); rw != nil {
body = applyToolNameRewriteToBody(body, rw)
@@ -4108,7 +4106,7 @@ type cacheControlPath struct {
log string
}
func collectCacheControlPaths(body []byte) (invalidThinking []cacheControlPath, messagePaths []string, systemPaths []string) {
func collectCacheControlPaths(body []byte) (invalidThinking []cacheControlPath, messagePaths []string, toolPaths []string, systemPaths []string) {
system := gjson.GetBytes(body, "system")
if system.IsArray() {
sysIndex := 0
@@ -4157,17 +4155,29 @@ func collectCacheControlPaths(body []byte) (invalidThinking []cacheControlPath,
})
}
return invalidThinking, messagePaths, systemPaths
tools := gjson.GetBytes(body, "tools")
if tools.IsArray() {
toolIndex := 0
tools.ForEach(func(_, tool gjson.Result) bool {
if tool.Get("cache_control").Exists() {
toolPaths = append(toolPaths, fmt.Sprintf("tools.%d.cache_control", toolIndex))
}
toolIndex++
return true
})
}
return invalidThinking, messagePaths, toolPaths, systemPaths
}
// enforceCacheControlLimit 强制执行 cache_control 块数量限制(最多 4 个)
// 超限时优先从 messages 中移除 cache_control,保护 system 中的缓存控制
// 超限时优先移除工具断点,再移除 messages 断点,最后才移除 system 断点。
func enforceCacheControlLimit(body []byte) []byte {
if len(body) == 0 {
return body
}
invalidThinking, messagePaths, systemPaths := collectCacheControlPaths(body)
invalidThinking, messagePaths, toolPaths, systemPaths := collectCacheControlPaths(body)
out := body
modified := false
@@ -4185,7 +4195,7 @@ func enforceCacheControlLimit(body []byte) []byte {
logger.LegacyPrintf("service.gateway", "%s", item.log)
}
count := len(messagePaths) + len(systemPaths)
count := len(messagePaths) + len(toolPaths) + len(systemPaths)
if count <= maxCacheControlBlocks {
if modified {
return out
@@ -4193,8 +4203,22 @@ func enforceCacheControlLimit(body []byte) []byte {
return body
}
// 超限:优先从 messages 中移除,从 system 中移除
// 超限:优先从 tools 中移除,再从 messages 中移除,最后才从 system 中移除
remaining := count - maxCacheControlBlocks
for i := len(toolPaths) - 1; i >= 0 && remaining > 0; i-- {
path := toolPaths[i]
if !gjson.GetBytes(out, path).Exists() {
continue
}
next, ok := deleteJSONPathBytes(out, path)
if !ok {
continue
}
out = next
modified = true
remaining--
}
for _, path := range messagePaths {
if remaining <= 0 {
break
@@ -4418,11 +4442,10 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
body, reqModel = normalizeClaudeOAuthRequestBody(body, reqModel, normalizeOpts)
// D/E/F: messages cache 策略 + 工具名混淆 + tools[-1] 断点
// D/E/F: 可选 messages cache 策略 + 工具名混淆 + tools[-1] 断点
// 与 forward_as_chat_completions / forward_as_responses 路径对齐,
// 保证原生 /v1/messages 路径也经过完整的 Parrot 字段级改写。
body = stripMessageCacheControl(body)
body = addMessageCacheBreakpoints(body)
// 原生 /v1/messages 路径也走同一套可配置字段级改写。
body = s.rewriteMessageCacheControlIfEnabled(ctx, body)
if rw := buildToolNameRewriteFromBody(body); rw != nil {
body = applyToolNameRewriteToBody(body, rw)
c.Set(toolNameRewriteKey, rw)
@@ -8819,8 +8842,7 @@ func (s *GatewayService) ForwardCountTokens(ctx context.Context, c *gin.Context,
normalizeOpts := claudeOAuthNormalizeOptions{stripSystemCacheControl: true}
body, reqModel = normalizeClaudeOAuthRequestBody(body, reqModel, normalizeOpts)
body = stripMessageCacheControl(body)
body = addMessageCacheBreakpoints(body)
body = s.rewriteMessageCacheControlIfEnabled(ctx, body)
if rw := buildToolNameRewriteFromBody(body); rw != nil {
body = applyToolNameRewriteToBody(body, rw)
} else {