mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-13 04:38:00 +08:00
1
This commit is contained in:
@@ -27,6 +27,8 @@ RULES:
|
||||
7) Numbers, booleans, and null stay plain text.
|
||||
8) Use only the parameter names in the tool schema. Do not invent fields.
|
||||
9) Do NOT wrap XML in markdown fences. Do NOT output explanations, role markers, or internal monologue.
|
||||
10) If you call a tool, the first non-whitespace characters of that tool block must be exactly <tool_calls>.
|
||||
11) Never omit the opening <tool_calls> tag, even if you already plan to close with </tool_calls>.
|
||||
|
||||
PARAMETER SHAPES:
|
||||
- string => <parameter name="x"><![CDATA[value]]></parameter>
|
||||
@@ -42,6 +44,9 @@ Wrong 2 — Markdown code fences:
|
||||
` + "```xml" + `
|
||||
<tool_calls>...</tool_calls>
|
||||
` + "```" + `
|
||||
Wrong 3 — missing opening wrapper:
|
||||
<invoke name="TOOL_NAME">...</invoke>
|
||||
</tool_calls>
|
||||
|
||||
Remember: The ONLY valid way to use tools is the <tool_calls>...</tool_calls> XML block at the end of your response.
|
||||
|
||||
|
||||
@@ -109,6 +109,16 @@ func TestBuildToolCallInstructions_WriteUsesFilePathAndContent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildToolCallInstructions_AnchorsMissingOpeningWrapperFailureMode(t *testing.T) {
|
||||
out := BuildToolCallInstructions([]string{"read_file"})
|
||||
if !strings.Contains(out, "Never omit the opening <tool_calls> tag") {
|
||||
t.Fatalf("expected explicit missing-opening-tag warning, got: %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "Wrong 3 — missing opening wrapper") {
|
||||
t.Fatalf("expected missing-opening-wrapper negative example, got: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func findInvokeBlocks(text, name string) []string {
|
||||
open := `<invoke name="` + name + `">`
|
||||
remaining := text
|
||||
|
||||
@@ -11,9 +11,17 @@ var xmlToolCallsWrapperPattern = regexp.MustCompile(`(?is)<tool_calls\b[^>]*>\s*
|
||||
var xmlInvokePattern = regexp.MustCompile(`(?is)<invoke\b([^>]*)>\s*(.*?)\s*</invoke>`)
|
||||
var xmlParameterPattern = regexp.MustCompile(`(?is)<parameter\b([^>]*)>\s*(.*?)\s*</parameter>`)
|
||||
var xmlAttrPattern = regexp.MustCompile(`(?is)\b([a-z0-9_:-]+)\s*=\s*("([^"]*)"|'([^']*)')`)
|
||||
var xmlToolCallsClosePattern = regexp.MustCompile(`(?is)</tool_calls>`)
|
||||
var xmlInvokeStartPattern = regexp.MustCompile(`(?is)<invoke\b[^>]*\bname\s*=\s*("([^"]*)"|'([^']*)')`)
|
||||
|
||||
func parseXMLToolCalls(text string) []ParsedToolCall {
|
||||
wrappers := xmlToolCallsWrapperPattern.FindAllStringSubmatch(text, -1)
|
||||
if len(wrappers) == 0 {
|
||||
repaired := repairMissingXMLToolCallsOpeningWrapper(text)
|
||||
if repaired != text {
|
||||
wrappers = xmlToolCallsWrapperPattern.FindAllStringSubmatch(repaired, -1)
|
||||
}
|
||||
}
|
||||
if len(wrappers) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -36,6 +44,28 @@ func parseXMLToolCalls(text string) []ParsedToolCall {
|
||||
return out
|
||||
}
|
||||
|
||||
func repairMissingXMLToolCallsOpeningWrapper(text string) string {
|
||||
lower := strings.ToLower(text)
|
||||
if strings.Contains(lower, "<tool_calls") {
|
||||
return text
|
||||
}
|
||||
|
||||
closeMatches := xmlToolCallsClosePattern.FindAllStringIndex(text, -1)
|
||||
if len(closeMatches) == 0 {
|
||||
return text
|
||||
}
|
||||
invokeLoc := xmlInvokeStartPattern.FindStringIndex(text)
|
||||
if invokeLoc == nil {
|
||||
return text
|
||||
}
|
||||
closeLoc := closeMatches[len(closeMatches)-1]
|
||||
if invokeLoc[0] >= closeLoc[0] {
|
||||
return text
|
||||
}
|
||||
|
||||
return text[:invokeLoc[0]] + "<tool_calls>" + text[invokeLoc[0]:closeLoc[0]] + "</tool_calls>" + text[closeLoc[1]:]
|
||||
}
|
||||
|
||||
func parseSingleXMLToolCall(block []string) (ParsedToolCall, bool) {
|
||||
if len(block) < 3 {
|
||||
return ParsedToolCall{}, false
|
||||
|
||||
@@ -175,6 +175,26 @@ func TestParseToolCallsRejectsBareInvokeWithoutToolCallsWrapper(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseToolCallsRepairsMissingOpeningToolCallsWrapperWhenClosingTagExists(t *testing.T) {
|
||||
text := `Before tool call
|
||||
<invoke name="read_file"><parameter name="path">README.md</parameter></invoke>
|
||||
</tool_calls>
|
||||
after`
|
||||
res := ParseToolCallsDetailed(text, []string{"read_file"})
|
||||
if len(res.Calls) != 1 {
|
||||
t.Fatalf("expected repaired wrapper to parse exactly one call, got %#v", res)
|
||||
}
|
||||
if res.Calls[0].Name != "read_file" {
|
||||
t.Fatalf("expected repaired wrapper to preserve tool name, got %#v", res.Calls[0])
|
||||
}
|
||||
if got, _ := res.Calls[0].Input["path"].(string); got != "README.md" {
|
||||
t.Fatalf("expected repaired wrapper to preserve args, got %#v", res.Calls[0].Input)
|
||||
}
|
||||
if !res.SawToolCallSyntax {
|
||||
t.Fatalf("expected repaired wrapper to mark tool syntax seen, got %#v", res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseToolCallsRejectsLegacyCanonicalBody(t *testing.T) {
|
||||
text := `<tool_calls><invoke name="read_file"><tool_name>read_file</tool_name><param>{"path":"README.md"}</param></invoke></tool_calls>`
|
||||
calls := ParseToolCalls(text, []string{"read_file"})
|
||||
|
||||
Reference in New Issue
Block a user