From 1d3fe3f02a71d41c4b1f340ac397df0a613cf360 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 14 Jan 2026 22:39:29 +0000 Subject: [PATCH] fix(functions): do not duplicate function when valid JSON is inside XML tags Signed-off-by: Ettore Di Giacinto --- pkg/functions/parse.go | 16 ++++++++++++---- pkg/functions/parse_test.go | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/pkg/functions/parse.go b/pkg/functions/parse.go index 39f91480ab7b..9f14208f1f6d 100644 --- a/pkg/functions/parse.go +++ b/pkg/functions/parse.go @@ -1492,7 +1492,7 @@ func ParseFunctionCall(llmresult string, functionConfig FunctionsConfig) []FuncC results := []FuncCallResults{} llmResults := []string{} - returnResult := func(results []string) (result []FuncCallResults, e error) { + extractJSON := func(results []string) (result []FuncCallResults, e error) { // As we have to change the result before processing, we can't stream the answer token-by-token (yet?) result = make([]FuncCallResults, 0) @@ -1593,7 +1593,7 @@ func ParseFunctionCall(llmresult string, functionConfig FunctionsConfig) []FuncC if len(llmResults) == 0 { llmResults = append(llmResults, llmresult) } - results, _ = returnResult(llmResults) + results, _ = extractJSON(llmResults) } // Determine which XML format to use (if any) @@ -1632,8 +1632,16 @@ func ParseFunctionCall(llmresult string, functionConfig FunctionsConfig) []FuncC // But skip if JSONRegexMatch or ResponseRegex was used (they already extracted the content) xmlResults, err := ParseXML(llmresult, xmlFormat) if err == nil && len(xmlResults) > 0 { - xlog.Debug("Found additional XML tool calls alongside JSON", "xml_count", len(xmlResults)) - results = append(results, xmlResults...) + // Check if JSON is inside XML tags, if so, skip it + for _, result := range xmlResults { + jsonResults, _ := extractJSON([]string{result.Name}) + if len(jsonResults) > 0 { + xlog.Debug("Found valid JSON inside XML tags, skipping XML parsing", "json_count", len(jsonResults)) + } else { + xlog.Debug("Found additional XML tool calls alongside JSON", "xml_count", len(xmlResults)) + results = append(results, xmlResults...) + } + } } } diff --git a/pkg/functions/parse_test.go b/pkg/functions/parse_test.go index 050b54cbdce1..de08fffc3f6d 100644 --- a/pkg/functions/parse_test.go +++ b/pkg/functions/parse_test.go @@ -820,6 +820,23 @@ Final text` Expect(results[0].Name).To(Equal("first")) Expect(results[1].Name).To(Equal("second")) }) + + It("should not duplicate parse JSON inside tool_call tags", func() { + // This test reproduces a bug where JSON inside tags + // gets parsed twice: once as JSON (correctly) and once as XML (incorrectly) + // The XML parser should not run when JSON parsing already found valid results + input := ` +{"name": "get_current_weather", "arguments": {"location": "Beijing", "unit": "celsius"}} +` + + results := ParseFunctionCall(input, functionConfig) + // Should only have 1 result, not 2 (one correct + one malformed) + Expect(results).To(HaveLen(1), "Should not create duplicate entries when JSON is inside XML tags") + Expect(results[0].Name).To(Equal("get_current_weather")) + Expect(results[0].Arguments).To(Equal(`{"location":"Beijing","unit":"celsius"}`)) + // Verify the name is not the entire JSON object (which would indicate malformed XML parsing) + Expect(results[0].Name).NotTo(ContainSubstring(`{"name"`), "Function name should not contain JSON object") + }) }) Context("Iterative Parser (ChatMsgParser)", func() {