fix(backend): 改进 thinking/tool block 签名处理和重试策略
主要改动: - request_transformer: thinking block 缺少签名时降级为文本而非丢弃,保留内容并在上层禁用 thinking mode - antigravity_gateway_service: 新增两阶段降级策略,先处理 thinking blocks,如仍失败且涉及 tool 签名错误则进一步降级 tool blocks - gateway_request: 新增 FilterSignatureSensitiveBlocksForRetry 函数,支持将 tool_use/tool_result 降级为文本 - gateway_request: 改进 FilterThinkingBlocksForRetry,禁用顶层 thinking 配置以避免结构约束冲突 - gateway_service: 实现保守的两阶段重试逻辑,优先保留内容,仅在必要时降级工具调用 - 新增 antigravity_gateway_service_test.go 测试签名块剥离逻辑 - 更新相关测试用例以验证降级行为 此修复解决了跨平台/账户切换时历史消息签名失效导致的请求失败问题。
This commit is contained in:
@@ -151,3 +151,125 @@ func TestFilterThinkingBlocks(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterThinkingBlocksForRetry_DisablesThinkingAndPreservesAsText(t *testing.T) {
|
||||
input := []byte(`{
|
||||
"model":"claude-3-5-sonnet-20241022",
|
||||
"thinking":{"type":"enabled","budget_tokens":1024},
|
||||
"messages":[
|
||||
{"role":"user","content":[{"type":"text","text":"Hi"}]},
|
||||
{"role":"assistant","content":[
|
||||
{"type":"thinking","thinking":"Let me think...","signature":"bad_sig"},
|
||||
{"type":"text","text":"Answer"}
|
||||
]}
|
||||
]
|
||||
}`)
|
||||
|
||||
out := FilterThinkingBlocksForRetry(input)
|
||||
|
||||
var req map[string]any
|
||||
require.NoError(t, json.Unmarshal(out, &req))
|
||||
_, hasThinking := req["thinking"]
|
||||
require.False(t, hasThinking)
|
||||
|
||||
msgs, ok := req["messages"].([]any)
|
||||
require.True(t, ok)
|
||||
require.Len(t, msgs, 2)
|
||||
|
||||
assistant := msgs[1].(map[string]any)
|
||||
content := assistant["content"].([]any)
|
||||
require.Len(t, content, 2)
|
||||
|
||||
first := content[0].(map[string]any)
|
||||
require.Equal(t, "text", first["type"])
|
||||
require.Equal(t, "Let me think...", first["text"])
|
||||
}
|
||||
|
||||
func TestFilterThinkingBlocksForRetry_DisablesThinkingEvenWithoutThinkingBlocks(t *testing.T) {
|
||||
input := []byte(`{
|
||||
"model":"claude-3-5-sonnet-20241022",
|
||||
"thinking":{"type":"enabled","budget_tokens":1024},
|
||||
"messages":[
|
||||
{"role":"user","content":[{"type":"text","text":"Hi"}]},
|
||||
{"role":"assistant","content":[{"type":"text","text":"Prefill"}]}
|
||||
]
|
||||
}`)
|
||||
|
||||
out := FilterThinkingBlocksForRetry(input)
|
||||
|
||||
var req map[string]any
|
||||
require.NoError(t, json.Unmarshal(out, &req))
|
||||
_, hasThinking := req["thinking"]
|
||||
require.False(t, hasThinking)
|
||||
}
|
||||
|
||||
func TestFilterThinkingBlocksForRetry_RemovesRedactedThinkingAndKeepsValidContent(t *testing.T) {
|
||||
input := []byte(`{
|
||||
"thinking":{"type":"enabled","budget_tokens":1024},
|
||||
"messages":[
|
||||
{"role":"assistant","content":[
|
||||
{"type":"redacted_thinking","data":"..."},
|
||||
{"type":"text","text":"Visible"}
|
||||
]}
|
||||
]
|
||||
}`)
|
||||
|
||||
out := FilterThinkingBlocksForRetry(input)
|
||||
|
||||
var req map[string]any
|
||||
require.NoError(t, json.Unmarshal(out, &req))
|
||||
_, hasThinking := req["thinking"]
|
||||
require.False(t, hasThinking)
|
||||
|
||||
msgs := req["messages"].([]any)
|
||||
content := msgs[0].(map[string]any)["content"].([]any)
|
||||
require.Len(t, content, 1)
|
||||
require.Equal(t, "text", content[0].(map[string]any)["type"])
|
||||
require.Equal(t, "Visible", content[0].(map[string]any)["text"])
|
||||
}
|
||||
|
||||
func TestFilterThinkingBlocksForRetry_EmptyContentGetsPlaceholder(t *testing.T) {
|
||||
input := []byte(`{
|
||||
"thinking":{"type":"enabled"},
|
||||
"messages":[
|
||||
{"role":"assistant","content":[{"type":"redacted_thinking","data":"..."}]}
|
||||
]
|
||||
}`)
|
||||
|
||||
out := FilterThinkingBlocksForRetry(input)
|
||||
|
||||
var req map[string]any
|
||||
require.NoError(t, json.Unmarshal(out, &req))
|
||||
msgs := req["messages"].([]any)
|
||||
content := msgs[0].(map[string]any)["content"].([]any)
|
||||
require.Len(t, content, 1)
|
||||
require.Equal(t, "text", content[0].(map[string]any)["type"])
|
||||
require.NotEmpty(t, content[0].(map[string]any)["text"])
|
||||
}
|
||||
|
||||
func TestFilterSignatureSensitiveBlocksForRetry_DowngradesTools(t *testing.T) {
|
||||
input := []byte(`{
|
||||
"thinking":{"type":"enabled","budget_tokens":1024},
|
||||
"messages":[
|
||||
{"role":"assistant","content":[
|
||||
{"type":"tool_use","id":"t1","name":"Bash","input":{"command":"ls"}},
|
||||
{"type":"tool_result","tool_use_id":"t1","content":"ok","is_error":false}
|
||||
]}
|
||||
]
|
||||
}`)
|
||||
|
||||
out := FilterSignatureSensitiveBlocksForRetry(input)
|
||||
|
||||
var req map[string]any
|
||||
require.NoError(t, json.Unmarshal(out, &req))
|
||||
_, hasThinking := req["thinking"]
|
||||
require.False(t, hasThinking)
|
||||
|
||||
msgs := req["messages"].([]any)
|
||||
content := msgs[0].(map[string]any)["content"].([]any)
|
||||
require.Len(t, content, 2)
|
||||
require.Equal(t, "text", content[0].(map[string]any)["type"])
|
||||
require.Equal(t, "text", content[1].(map[string]any)["type"])
|
||||
require.Contains(t, content[0].(map[string]any)["text"], "tool_use")
|
||||
require.Contains(t, content[1].(map[string]any)["text"], "tool_result")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user