mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-05 00:45:29 +08:00
fix(claude): guard thinking tool-call fallback when final text exists
- only parse tool_calls from thinking when finalText is empty - apply the same guard in stream runtime finalizer - add regression tests for non-stream and stream paths
This commit is contained in:
@@ -209,6 +209,40 @@ func TestHandleClaudeStreamRealtimeToolDetectionFromThinkingFallback(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleClaudeStreamRealtimeSkipsThinkingFallbackWhenFinalTextExists(t *testing.T) {
|
||||
h := &Handler{}
|
||||
resp := makeClaudeSSEHTTPResponse(
|
||||
`data: {"p":"response/thinking_content","v":"{\"tool_calls\":[{\"name\":\"search\""}`,
|
||||
`data: {"p":"response/thinking_content","v":",\"input\":{\"q\":\"go\"}}]}"}`,
|
||||
`data: {"p":"response/content","v":"normal answer"}`,
|
||||
`data: [DONE]`,
|
||||
)
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodPost, "/anthropic/v1/messages", nil)
|
||||
|
||||
h.handleClaudeStreamRealtime(rec, req, resp, "claude-sonnet-4-5", []any{map[string]any{"role": "user", "content": "use tool"}}, true, false, []string{"search"})
|
||||
|
||||
frames := parseClaudeFrames(t, rec.Body.String())
|
||||
for _, f := range findClaudeFrames(frames, "content_block_start") {
|
||||
contentBlock, _ := f.Payload["content_block"].(map[string]any)
|
||||
if contentBlock["type"] == "tool_use" {
|
||||
t.Fatalf("unexpected tool_use block when final text exists, body=%s", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
foundEndTurn := false
|
||||
for _, f := range findClaudeFrames(frames, "message_delta") {
|
||||
delta, _ := f.Payload["delta"].(map[string]any)
|
||||
if delta["stop_reason"] == "end_turn" {
|
||||
foundEndTurn = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundEndTurn {
|
||||
t.Fatalf("expected stop_reason=end_turn, body=%s", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleClaudeStreamRealtimeUpstreamErrorEvent(t *testing.T) {
|
||||
h := &Handler{}
|
||||
resp := makeClaudeSSEHTTPResponse(
|
||||
|
||||
@@ -46,7 +46,7 @@ func (s *claudeStreamRuntime) finalize(stopReason string) {
|
||||
|
||||
if s.bufferToolContent {
|
||||
detected := util.ParseToolCalls(finalText, s.toolNames)
|
||||
if len(detected) == 0 && finalThinking != "" {
|
||||
if len(detected) == 0 && finalText == "" && finalThinking != "" {
|
||||
detected = util.ParseToolCalls(finalThinking, s.toolNames)
|
||||
}
|
||||
if len(detected) > 0 {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
func BuildMessageResponse(messageID, model string, normalizedMessages []any, finalThinking, finalText string, toolNames []string) map[string]any {
|
||||
detected := util.ParseToolCalls(finalText, toolNames)
|
||||
if len(detected) == 0 && finalThinking != "" {
|
||||
if len(detected) == 0 && finalText == "" && finalThinking != "" {
|
||||
detected = util.ParseToolCalls(finalThinking, toolNames)
|
||||
}
|
||||
content := make([]map[string]any, 0, 4)
|
||||
|
||||
@@ -27,3 +27,36 @@ func TestBuildMessageResponseDetectsToolCallsFromThinkingFallback(t *testing.T)
|
||||
t.Fatalf("expected tool name search, got=%#v", last["name"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMessageResponseSkipsThinkingFallbackWhenFinalTextExists(t *testing.T) {
|
||||
resp := BuildMessageResponse(
|
||||
"msg_1",
|
||||
"claude-sonnet-4-5",
|
||||
[]any{map[string]any{"role": "user", "content": "hi"}},
|
||||
`{"tool_calls":[{"name":"search","input":{"q":"go"}}]}`,
|
||||
"normal answer",
|
||||
[]string{"search"},
|
||||
)
|
||||
|
||||
if resp["stop_reason"] != "end_turn" {
|
||||
t.Fatalf("expected stop_reason=end_turn, got=%#v", resp["stop_reason"])
|
||||
}
|
||||
|
||||
content, _ := resp["content"].([]map[string]any)
|
||||
foundText := false
|
||||
foundTool := false
|
||||
for _, block := range content {
|
||||
if block["type"] == "text" && block["text"] == "normal answer" {
|
||||
foundText = true
|
||||
}
|
||||
if block["type"] == "tool_use" {
|
||||
foundTool = true
|
||||
}
|
||||
}
|
||||
if !foundText {
|
||||
t.Fatalf("expected text block with finalText, got=%#v", resp["content"])
|
||||
}
|
||||
if foundTool {
|
||||
t.Fatalf("unexpected tool_use block when finalText exists, got=%#v", resp["content"])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user