mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-04 08:25:26 +08:00
Fix XML tool-call parsing for fenced markdown examples
This commit is contained in:
@@ -39,6 +39,11 @@ func parseToolCallsDetailedXMLOnly(text string) ToolCallParseResult {
|
||||
return result
|
||||
}
|
||||
result.SawToolCallSyntax = looksLikeToolCallSyntax(trimmed)
|
||||
trimmed = stripFencedCodeBlocks(trimmed)
|
||||
trimmed = strings.TrimSpace(trimmed)
|
||||
if trimmed == "" {
|
||||
return result
|
||||
}
|
||||
|
||||
parsed := parseXMLToolCalls(trimmed)
|
||||
if len(parsed) == 0 {
|
||||
@@ -83,3 +88,55 @@ func looksLikeToolCallSyntax(text string) bool {
|
||||
strings.Contains(lower, "<new_task") ||
|
||||
strings.Contains(lower, "<result")
|
||||
}
|
||||
|
||||
func stripFencedCodeBlocks(text string) string {
|
||||
if text == "" {
|
||||
return ""
|
||||
}
|
||||
var b strings.Builder
|
||||
b.Grow(len(text))
|
||||
|
||||
lines := strings.SplitAfter(text, "\n")
|
||||
inFence := false
|
||||
fenceMarker := ""
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimLeft(line, " \t")
|
||||
if !inFence {
|
||||
if marker, ok := parseFenceOpen(trimmed); ok {
|
||||
inFence = true
|
||||
fenceMarker = marker
|
||||
continue
|
||||
}
|
||||
b.WriteString(line)
|
||||
continue
|
||||
}
|
||||
|
||||
if isFenceClose(trimmed, fenceMarker) {
|
||||
inFence = false
|
||||
fenceMarker = ""
|
||||
}
|
||||
}
|
||||
|
||||
if inFence {
|
||||
return ""
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func parseFenceOpen(line string) (string, bool) {
|
||||
if strings.HasPrefix(line, "```") {
|
||||
return "```", true
|
||||
}
|
||||
if strings.HasPrefix(line, "~~~") {
|
||||
return "~~~", true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func isFenceClose(line, marker string) bool {
|
||||
if marker == "" || !strings.HasPrefix(line, marker) {
|
||||
return false
|
||||
}
|
||||
rest := strings.TrimSpace(strings.TrimPrefix(line, marker))
|
||||
return rest == ""
|
||||
}
|
||||
|
||||
@@ -455,3 +455,22 @@ func TestParseToolCallsUnescapesHTMLEntityArguments(t *testing.T) {
|
||||
t.Fatalf("expected html entities to be unescaped in command, got %q", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseToolCallsIgnoresXMLInsideFencedCodeBlock(t *testing.T) {
|
||||
text := "Here is an example:\n```xml\n<tool_call><tool_name>read_file</tool_name><parameters>{\"path\":\"README.md\"}</parameters></tool_call>\n```\nDo not execute it."
|
||||
res := ParseToolCallsDetailed(text, []string{"read_file"})
|
||||
if len(res.Calls) != 0 {
|
||||
t.Fatalf("expected no parsed calls for fenced example, got %#v", res.Calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseToolCallsParsesOnlyNonFencedXMLToolCall(t *testing.T) {
|
||||
text := "```xml\n<tool_call><tool_name>read_file</tool_name><parameters>{\"path\":\"README.md\"}</parameters></tool_call>\n```\n<tool_call><tool_name>search</tool_name><parameters>{\"q\":\"golang\"}</parameters></tool_call>"
|
||||
res := ParseToolCallsDetailed(text, []string{"read_file", "search"})
|
||||
if len(res.Calls) != 1 {
|
||||
t.Fatalf("expected exactly one parsed call outside fence, got %#v", res.Calls)
|
||||
}
|
||||
if res.Calls[0].Name != "search" {
|
||||
t.Fatalf("expected non-fenced tool call to be parsed, got %#v", res.Calls[0])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user