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:
83
backend/internal/service/antigravity_gateway_service_test.go
Normal file
83
backend/internal/service/antigravity_gateway_service_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/antigravity"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestStripSignatureSensitiveBlocksFromClaudeRequest(t *testing.T) {
|
||||
req := &antigravity.ClaudeRequest{
|
||||
Model: "claude-sonnet-4-5",
|
||||
Thinking: &antigravity.ThinkingConfig{
|
||||
Type: "enabled",
|
||||
BudgetTokens: 1024,
|
||||
},
|
||||
Messages: []antigravity.ClaudeMessage{
|
||||
{
|
||||
Role: "assistant",
|
||||
Content: json.RawMessage(`[
|
||||
{"type":"thinking","thinking":"secret plan","signature":""},
|
||||
{"type":"tool_use","id":"t1","name":"Bash","input":{"command":"ls"}}
|
||||
]`),
|
||||
},
|
||||
{
|
||||
Role: "user",
|
||||
Content: json.RawMessage(`[
|
||||
{"type":"tool_result","tool_use_id":"t1","content":"ok","is_error":false},
|
||||
{"type":"redacted_thinking","data":"..."}
|
||||
]`),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
changed, err := stripSignatureSensitiveBlocksFromClaudeRequest(req)
|
||||
require.NoError(t, err)
|
||||
require.True(t, changed)
|
||||
require.Nil(t, req.Thinking)
|
||||
|
||||
require.Len(t, req.Messages, 2)
|
||||
|
||||
var blocks0 []map[string]any
|
||||
require.NoError(t, json.Unmarshal(req.Messages[0].Content, &blocks0))
|
||||
require.Len(t, blocks0, 2)
|
||||
require.Equal(t, "text", blocks0[0]["type"])
|
||||
require.Equal(t, "secret plan", blocks0[0]["text"])
|
||||
require.Equal(t, "text", blocks0[1]["type"])
|
||||
|
||||
var blocks1 []map[string]any
|
||||
require.NoError(t, json.Unmarshal(req.Messages[1].Content, &blocks1))
|
||||
require.Len(t, blocks1, 1)
|
||||
require.Equal(t, "text", blocks1[0]["type"])
|
||||
require.NotEmpty(t, blocks1[0]["text"])
|
||||
}
|
||||
|
||||
func TestStripThinkingFromClaudeRequest_DoesNotDowngradeTools(t *testing.T) {
|
||||
req := &antigravity.ClaudeRequest{
|
||||
Model: "claude-sonnet-4-5",
|
||||
Thinking: &antigravity.ThinkingConfig{
|
||||
Type: "enabled",
|
||||
BudgetTokens: 1024,
|
||||
},
|
||||
Messages: []antigravity.ClaudeMessage{
|
||||
{
|
||||
Role: "assistant",
|
||||
Content: json.RawMessage(`[{"type":"thinking","thinking":"secret plan"},{"type":"tool_use","id":"t1","name":"Bash","input":{"command":"ls"}}]`),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
changed, err := stripThinkingFromClaudeRequest(req)
|
||||
require.NoError(t, err)
|
||||
require.True(t, changed)
|
||||
require.Nil(t, req.Thinking)
|
||||
|
||||
var blocks []map[string]any
|
||||
require.NoError(t, json.Unmarshal(req.Messages[0].Content, &blocks))
|
||||
require.Len(t, blocks, 2)
|
||||
require.Equal(t, "text", blocks[0]["type"])
|
||||
require.Equal(t, "secret plan", blocks[0]["text"])
|
||||
require.Equal(t, "tool_use", blocks[1]["type"])
|
||||
}
|
||||
Reference in New Issue
Block a user