fix: classify empty upstream and tighten xml tool-name parsing

This commit is contained in:
CJACK.
2026-04-04 02:14:39 +08:00
parent 95a9d16843
commit 3497d5d019
8 changed files with 126 additions and 23 deletions

View File

@@ -85,7 +85,7 @@ func parseSingleXMLToolCall(block string) (ParsedToolCall, bool) {
}
}
name := strings.TrimSpace(extractXMLToolNameByRegex(inner))
name := ""
params := extractXMLToolParamsByRegex(inner)
dec := xml.NewDecoder(strings.NewReader(block))
inParams := false
@@ -136,9 +136,13 @@ func parseSingleXMLToolCall(block string) (ParsedToolCall, bool) {
}
}
inParams = false
case "tool_name", "name":
case "tool_name", "function_name", "name":
var v string
if err := dec.DecodeElement(&v, &t); err == nil && strings.TrimSpace(v) != "" {
if inParams {
params[t.Name.Local] = strings.TrimSpace(v)
break
}
name = strings.TrimSpace(v)
}
case "input", "arguments", "argument", "args", "params":
@@ -168,12 +172,37 @@ func parseSingleXMLToolCall(block string) (ParsedToolCall, bool) {
}
}
}
if strings.TrimSpace(name) == "" {
name = strings.TrimSpace(extractXMLToolNameByRegex(stripTopLevelXMLParameters(inner)))
}
if strings.TrimSpace(name) == "" {
return ParsedToolCall{}, false
}
return ParsedToolCall{Name: strings.TrimSpace(name), Input: params}, true
}
func stripTopLevelXMLParameters(inner string) string {
out := strings.TrimSpace(inner)
for {
idx := strings.Index(strings.ToLower(out), "<parameters")
if idx < 0 {
return out
}
segment := out[idx:]
segmentLower := strings.ToLower(segment)
openEnd := strings.Index(segmentLower, ">")
if openEnd < 0 {
return out
}
closeIdx := strings.Index(segmentLower, "</parameters>")
if closeIdx < 0 {
return out[:idx]
}
end := idx + closeIdx + len("</parameters>")
out = out[:idx] + out[end:]
}
}
func extractXMLToolNameByRegex(inner string) string {
for _, pattern := range xmlToolNamePatterns {
if m := pattern.FindStringSubmatch(inner); len(m) >= 2 {

View File

@@ -431,6 +431,14 @@ func TestParseToolCallsDoesNotAcceptMismatchedMarkupTags(t *testing.T) {
}
}
func TestParseToolCallsDoesNotTreatParametersFunctionNameAsToolName(t *testing.T) {
text := `<tool_call><parameters><function_name>data_only</function_name><path>README.md</path></parameters></tool_call>`
calls := ParseToolCalls(text, []string{"read_file"})
if len(calls) != 0 {
t.Fatalf("expected no tool call when function_name appears only under parameters, got %#v", calls)
}
}
func TestRepairInvalidJSONBackslashes(t *testing.T) {
tests := []struct {
input string