fix(antigravity): 与 proxycast 保持一致的 thought_signature 处理
- function_call 无条件添加 dummy thought_signature(与 proxycast 一致) - thinking block 在 thinking 模式下统一使用 dummy signature 替换历史无效 signature - 添加测试用例:TestClaudeMessagesWithInvalidThinkingSignature
This commit is contained in:
@@ -630,3 +630,102 @@ func testClaudeThinkingWithToolHistory(t *testing.T, model string) {
|
|||||||
}
|
}
|
||||||
t.Logf("✅ thinking 模式工具调用测试通过, id=%v", result["id"])
|
t.Logf("✅ thinking 模式工具调用测试通过, id=%v", result["id"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestClaudeMessagesWithInvalidThinkingSignature 测试历史 thinking block 带有无效 signature 的场景
|
||||||
|
// 验证:系统应使用 dummy signature 替换历史的无效 signature
|
||||||
|
func TestClaudeMessagesWithInvalidThinkingSignature(t *testing.T) {
|
||||||
|
models := []string{
|
||||||
|
"claude-haiku-4-5-20251001", // gemini-3-flash
|
||||||
|
}
|
||||||
|
for i, model := range models {
|
||||||
|
if i > 0 {
|
||||||
|
time.Sleep(testInterval)
|
||||||
|
}
|
||||||
|
t.Run(model+"_无效thinking签名", func(t *testing.T) {
|
||||||
|
testClaudeWithInvalidThinkingSignature(t, model)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testClaudeWithInvalidThinkingSignature(t *testing.T, model string) {
|
||||||
|
url := baseURL + "/v1/messages"
|
||||||
|
|
||||||
|
// 模拟历史对话包含 thinking block 带有无效/过期的 signature
|
||||||
|
payload := map[string]any{
|
||||||
|
"model": model,
|
||||||
|
"max_tokens": 200,
|
||||||
|
"stream": false,
|
||||||
|
// 开启 thinking 模式
|
||||||
|
"thinking": map[string]any{
|
||||||
|
"type": "enabled",
|
||||||
|
"budget_tokens": 1024,
|
||||||
|
},
|
||||||
|
"messages": []any{
|
||||||
|
map[string]any{
|
||||||
|
"role": "user",
|
||||||
|
"content": "What is 2+2?",
|
||||||
|
},
|
||||||
|
// assistant 消息包含 thinking block 和无效 signature
|
||||||
|
map[string]any{
|
||||||
|
"role": "assistant",
|
||||||
|
"content": []map[string]any{
|
||||||
|
{
|
||||||
|
"type": "thinking",
|
||||||
|
"thinking": "Let me calculate 2+2...",
|
||||||
|
"signature": "invalid_expired_signature_abc123", // 模拟过期的 signature
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "2+2 equals 4.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]any{
|
||||||
|
"role": "user",
|
||||||
|
"content": "What is 3+3?",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
body, _ := json.Marshal(payload)
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("POST", url, bytes.NewReader(body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", "Bearer "+claudeAPIKey)
|
||||||
|
req.Header.Set("anthropic-version", "2023-06-01")
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: 60 * time.Second}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("请求失败: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, _ := io.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
// 400 错误说明 signature 处理失败
|
||||||
|
if resp.StatusCode == 400 {
|
||||||
|
t.Fatalf("无效 thinking signature 处理失败,收到 400 错误: %s", string(respBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == 503 {
|
||||||
|
t.Skipf("账号暂时不可用 (503): %s", string(respBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == 429 {
|
||||||
|
t.Skipf("请求被限流 (429): %s", string(respBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
t.Fatalf("HTTP %d: %s", resp.StatusCode, string(respBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
var result map[string]any
|
||||||
|
if err := json.Unmarshal(respBody, &result); err != nil {
|
||||||
|
t.Fatalf("解析响应失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result["type"] != "message" {
|
||||||
|
t.Errorf("期望 type=message, 得到 %v", result["type"])
|
||||||
|
}
|
||||||
|
t.Logf("✅ 无效 thinking signature 处理测试通过, id=%v", result["id"])
|
||||||
|
}
|
||||||
|
|||||||
@@ -139,8 +139,12 @@ func buildContents(messages []ClaudeMessage, toolIDToName map[string]string, isT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !hasThoughtPart && len(parts) > 0 {
|
if !hasThoughtPart && len(parts) > 0 {
|
||||||
// 在开头添加 dummy thinking block
|
// 在开头添加 dummy thinking block(需要 signature)
|
||||||
parts = append([]GeminiPart{{Text: "Thinking...", Thought: true}}, parts...)
|
parts = append([]GeminiPart{{
|
||||||
|
Text: "Thinking...",
|
||||||
|
Thought: true,
|
||||||
|
ThoughtSignature: dummyThoughtSignature,
|
||||||
|
}}, parts...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,8 +196,10 @@ func buildParts(content json.RawMessage, toolIDToName map[string]string, isThink
|
|||||||
Text: block.Thinking,
|
Text: block.Thinking,
|
||||||
Thought: true,
|
Thought: true,
|
||||||
}
|
}
|
||||||
if block.Signature != "" {
|
// 历史 thinking block 的 signature 可能已过期,统一使用 dummy signature
|
||||||
part.ThoughtSignature = block.Signature
|
// 参考: https://ai.google.dev/gemini-api/docs/thought-signatures
|
||||||
|
if isThinkingEnabled {
|
||||||
|
part.ThoughtSignature = dummyThoughtSignature
|
||||||
}
|
}
|
||||||
parts = append(parts, part)
|
parts = append(parts, part)
|
||||||
|
|
||||||
@@ -213,18 +219,14 @@ func buildParts(content json.RawMessage, toolIDToName map[string]string, isThink
|
|||||||
toolIDToName[block.ID] = block.Name
|
toolIDToName[block.ID] = block.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 与 proxycast 保持一致:function_call 无条件添加 thought_signature
|
||||||
part := GeminiPart{
|
part := GeminiPart{
|
||||||
FunctionCall: &GeminiFunctionCall{
|
FunctionCall: &GeminiFunctionCall{
|
||||||
Name: block.Name,
|
Name: block.Name,
|
||||||
Args: block.Input,
|
Args: block.Input,
|
||||||
ID: block.ID,
|
ID: block.ID,
|
||||||
},
|
},
|
||||||
}
|
ThoughtSignature: dummyThoughtSignature,
|
||||||
// Gemini 3 要求 thinking 模式下 functionCall 必须有 thought_signature
|
|
||||||
if block.Signature != "" {
|
|
||||||
part.ThoughtSignature = block.Signature
|
|
||||||
} else if isThinkingEnabled {
|
|
||||||
part.ThoughtSignature = dummyThoughtSignature
|
|
||||||
}
|
}
|
||||||
parts = append(parts, part)
|
parts = append(parts, part)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user