From 02d64c192e25a46caa7cd8c9e560143ebed70544 Mon Sep 17 00:00:00 2001 From: "CJACK." Date: Thu, 2 Apr 2026 18:22:30 +0800 Subject: [PATCH] fix: prioritize quoted functionCall keys in tool sieve --- .../adapter/openai/tool_sieve_functioncall.go | 18 +++++++++--------- internal/adapter/openai/tool_sieve_xml_test.go | 8 ++++++++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/internal/adapter/openai/tool_sieve_functioncall.go b/internal/adapter/openai/tool_sieve_functioncall.go index 66d754e..bfdfaa6 100644 --- a/internal/adapter/openai/tool_sieve_functioncall.go +++ b/internal/adapter/openai/tool_sieve_functioncall.go @@ -5,18 +5,18 @@ import "strings" func findQuotedFunctionCallKeyStart(s string) int { lower := strings.ToLower(s) quotedIdx := findFunctionCallKeyStart(lower, `"functioncall"`) - baretIdx := findFunctionCallKeyStart(lower, "functioncall") + bareIdx := findFunctionCallKeyStart(lower, "functioncall") - switch { - case quotedIdx < 0: - return baretIdx - case baretIdx < 0: + // Prefer quoted JSON keys when present. Bare-key detection is a fallback + // for loose payloads like {functionCall:{...}}. + // + // This avoids anchoring on earlier prose such as: + // "... {note} functionCall: ... {\"functionCall\":{...}}" + // where choosing the earliest bare match can hide the real tool payload. + if quotedIdx >= 0 { return quotedIdx - case quotedIdx < baretIdx: - return quotedIdx - default: - return baretIdx } + return bareIdx } func findFunctionCallKeyStart(lower, key string) int { diff --git a/internal/adapter/openai/tool_sieve_xml_test.go b/internal/adapter/openai/tool_sieve_xml_test.go index a86fe33..e390cd9 100644 --- a/internal/adapter/openai/tool_sieve_xml_test.go +++ b/internal/adapter/openai/tool_sieve_xml_test.go @@ -143,6 +143,14 @@ func TestFindToolSegmentStartDetectsLooseFunctionCallKey(t *testing.T) { } } +func TestFindToolSegmentStartPrefersQuotedFunctionCallOverEarlierBareProse(t *testing.T) { + input := `prefix {note} functionCall: docs hint {"functionCall":{"name":"search_web","args":{"query":"x"}}}` + want := strings.Index(input, `{"functionCall"`) + if got := findToolSegmentStart(input); got != want { + t.Fatalf("expected quoted functionCall JSON start %d, got %d", want, got) + } +} + func TestFindToolSegmentStartIgnoresLooseFunctionCallProse(t *testing.T) { input := "Please explain why functionCall: is used in documentation examples." if got := findToolSegmentStart(input); got != -1 {