diff --git a/gr2gw.go b/gr2gw.go index b757651..67ed18e 100644 --- a/gr2gw.go +++ b/gr2gw.go @@ -658,7 +658,10 @@ type ToolTagPair struct { var ToolTagPairs = []ToolTagPair{ {Start: "", End: ""}, {Start: "", End: ""}, + {Start: "", End: ""}, + {Start: "", End: ""}, {Start: "", End: ""}, + {Start: "", End: ""}, {Start: "[TOOL_CALLS]", End: "[/TOOL_CALLS]"}, } @@ -903,6 +906,36 @@ func parseXMLToolCall(block string) ([]ToolCall, bool) { } } + if argsStr != "" && !json.Valid([]byte(argsStr)) { + reTagOpen := regexp.MustCompile(`<([a-zA-Z0-9_-]+)>`) + openMatches := reTagOpen.FindAllStringSubmatchIndex(argsStr, -1) + if len(openMatches) > 0 { + xmlMap := make(map[string]interface{}) + for _, match := range openMatches { + tagName := argsStr[match[2]:match[3]] + closeTag := "" + closeIdx := strings.Index(argsStr[match[1]:], closeTag) + if closeIdx != -1 { + v := strings.TrimSpace(argsStr[match[1] : match[1]+closeIdx]) + if num, err := strconv.ParseFloat(v, 64); err == nil && !strings.HasPrefix(v, "0") { + xmlMap[tagName] = num + } else if v == "true" { + xmlMap[tagName] = true + } else if v == "false" { + xmlMap[tagName] = false + } else { + xmlMap[tagName] = v + } + } + } + if len(xmlMap) > 0 { + if b, err := json.Marshal(xmlMap); err == nil { + argsStr = string(b) + } + } + } + } + if fnName != "" { if argsStr == "" { argsStr = "{}" @@ -976,6 +1009,12 @@ func DetectToolCalls(content string) ([]ToolCall, string, bool) { } if len(calls) > 0 { + remaining = strings.TrimSpace(remaining) + if remaining == "```xml" || remaining == "```" || remaining == "```json" { + remaining = "" + } + reEmptyFence := regexp.MustCompile("(?m)^```(?:xml|json)?\\s*\\n?\\s*```\\s*$") + remaining = strings.TrimSpace(reEmptyFence.ReplaceAllString(remaining, "")) return calls, remaining, true } diff --git a/gr2gw_test.go b/gr2gw_test.go index b48b4cc..0a21dc8 100644 --- a/gr2gw_test.go +++ b/gr2gw_test.go @@ -670,6 +670,22 @@ Some postamble.` if rem4 != "" { t.Errorf("expected empty remaining, got %q", rem4) } + + // 5. XML toolCall with child tags + xmlChildTags := "```xml\n\n get_weather\n \n Paris\n \n\n```" + calls5, rem5, ok5 := DetectToolCalls(xmlChildTags) + if !ok5 || len(calls5) != 1 { + t.Fatalf("expected 1 call from xmlChildTags, got %d", len(calls5)) + } + if calls5[0].Function.Name != "get_weather" { + t.Errorf("expected get_weather, got %q", calls5[0].Function.Name) + } + if !strings.Contains(calls5[0].Function.Arguments, `"Paris"`) { + t.Errorf("expected Paris in arguments, got %s", calls5[0].Function.Arguments) + } + if rem5 != "" { + t.Errorf("expected empty remaining, got %q", rem5) + } } func TestUniversalStreamToolCallFilterVariants(t *testing.T) {