diff --git a/backend/internal/service/gateway_request_test.go b/backend/internal/service/gateway_request_test.go index 40bd1186..045dc66c 100644 --- a/backend/internal/service/gateway_request_test.go +++ b/backend/internal/service/gateway_request_test.go @@ -379,6 +379,37 @@ func TestFilterThinkingBlocksForRetry_RemovesRedactedThinkingAndKeepsValidConten require.Equal(t, "Visible", content0["text"]) } +func TestFilterThinkingBlocksForRetry_DropsThinkingBlockWithEmptyContent(t *testing.T) { + // 跨模型场景:其他模型回过的 assistant 历史里携带了 type=thinking 但 thinking 字段为空, + // 喂给开启 extended thinking 的 claude 时上游会报: + // "messages.1.content.0.thinking: each thinking block must contain thinking" + // 重试应当把空 thinking 块丢弃,并保留其它有效内容。 + input := []byte(`{ + "thinking":{"type":"enabled","budget_tokens":1024}, + "messages":[ + {"role":"user","content":[{"type":"text","text":"Hi"}]}, + {"role":"assistant","content":[ + {"type":"thinking","thinking":"","signature":"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, "top-level thinking should be removed") + + msgs := req["messages"].([]any) + assistant := msgs[1].(map[string]any) + content := assistant["content"].([]any) + require.Len(t, content, 1, "empty thinking block should be dropped, only text remains") + require.Equal(t, "text", content[0].(map[string]any)["type"]) + require.Equal(t, "Answer", content[0].(map[string]any)["text"]) +} + func TestFilterThinkingBlocksForRetry_EmptyContentGetsPlaceholder(t *testing.T) { input := []byte(`{ "thinking":{"type":"enabled"}, diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go index afc19adc..0caff3a7 100644 --- a/backend/internal/service/gateway_service.go +++ b/backend/internal/service/gateway_service.go @@ -6765,6 +6765,15 @@ func (s *GatewayService) isThinkingBlockSignatureError(respBody []byte) bool { return true } + // 检测 thinking block 缺少 thinking 字段的错误(跨模型切换时常见: + // 其他模型回过的 assistant 历史里有 type=thinking 但没有 thinking 文本, + // 喂给开启 extended thinking 的 claude 时会被拒) + // 例如: "messages.1.content.0.thinking: each thinking block must contain thinking" + if strings.Contains(msg, "thinking block must contain") { + logger.LegacyPrintf("service.gateway", "[SignatureCheck] Detected thinking block missing content error") + return true + } + return false }