fix: harden functionCall key detection in tool sieve

This commit is contained in:
CJACK.
2026-04-02 19:43:47 +08:00
parent 02d64c192e
commit 39f6e066d6
2 changed files with 63 additions and 8 deletions

View File

@@ -7,16 +7,16 @@ func findQuotedFunctionCallKeyStart(s string) int {
quotedIdx := findFunctionCallKeyStart(lower, `"functioncall"`)
bareIdx := findFunctionCallKeyStart(lower, "functioncall")
// 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 {
if quotedIdx < 0 {
return bareIdx
}
if bareIdx < 0 {
return quotedIdx
}
return bareIdx
if bareIdx < quotedIdx {
return bareIdx
}
return quotedIdx
}
func findFunctionCallKeyStart(lower, key string) int {
@@ -26,6 +26,10 @@ func findFunctionCallKeyStart(lower, key string) int {
return -1
}
idx := from + rel
if isInsideJSONString(lower, idx) {
from = idx + 1
continue
}
if !hasJSONObjectContextPrefix(lower[:idx]) {
from = idx + 1
continue
@@ -39,6 +43,14 @@ func findFunctionCallKeyStart(lower, key string) int {
j++
}
if j < len(lower) && lower[j] == ':' {
k := j + 1
for k < len(lower) && (lower[k] == ' ' || lower[k] == '\t' || lower[k] == '\r' || lower[k] == '\n') {
k++
}
if k < len(lower) && lower[k] != '{' {
from = idx + 1
continue
}
return idx
}
from = idx + 1
@@ -46,6 +58,26 @@ func findFunctionCallKeyStart(lower, key string) int {
return -1
}
func isInsideJSONString(s string, idx int) bool {
inString := false
escaped := false
for i := 0; i < idx; i++ {
c := s[i]
if escaped {
escaped = false
continue
}
if c == '\\' && inString {
escaped = true
continue
}
if c == '"' {
inString = !inString
}
}
return inString
}
func hasJSONObjectContextPrefix(prefix string) bool {
return strings.LastIndex(prefix, "{") >= 0
}

View File

@@ -0,0 +1,23 @@
package openai
import "testing"
func TestFindQuotedFunctionCallKeyStart_PrefersEarlierBareKey(t *testing.T) {
input := `{functionCall:{"name":"a","arguments":"{}"},"message":"literal text: \"functionCall\": not a key"}`
got := findQuotedFunctionCallKeyStart(input)
want := 1
if got != want {
t.Fatalf("findQuotedFunctionCallKeyStart() = %d, want %d", got, want)
}
}
func TestFindQuotedFunctionCallKeyStart_PrefersEarlierQuotedKey(t *testing.T) {
input := `{"functionCall":{"name":"a","arguments":"{}"},"note":"functionCall appears in prose"}`
got := findQuotedFunctionCallKeyStart(input)
want := 1
if got != want {
t.Fatalf("findQuotedFunctionCallKeyStart() = %d, want %d", got, want)
}
}