package toolcall import "strings" // BuildToolCallInstructions generates the unified tool-calling instruction block // used by all adapters (OpenAI, Claude, Gemini). It uses attention-optimized // structure: rules → negative examples → positive examples → anchor. // // The toolNames slice should contain the actual tool names available in the // current request; the function picks real names for examples. func BuildToolCallInstructions(toolNames []string) string { // Pick real tool names for examples; fall back to generic names. ex1 := "read_file" ex2 := "write_to_file" ex3 := "ask_followup_question" used := map[string]bool{} for _, n := range toolNames { switch { // Read/query-type tools case !used["ex1"] && matchAny(n, "read_file", "list_files", "search_files", "Read", "Glob"): ex1 = n used["ex1"] = true // Write/execute-type tools case !used["ex2"] && matchAny(n, "write_to_file", "apply_diff", "execute_command", "exec_command", "Write", "Edit", "MultiEdit", "Bash"): ex2 = n used["ex2"] = true // Interactive/meta tools case !used["ex3"] && matchAny(n, "ask_followup_question", "attempt_completion", "update_todo_list", "Task"): ex3 = n used["ex3"] = true } } ex1Params := exampleReadParams(ex1) ex2Params := exampleWriteOrExecParams(ex2) ex3Params := exampleInteractiveParams(ex3) return `TOOL CALL FORMAT — FOLLOW EXACTLY: When calling tools, emit ONLY raw XML at the very end of your response. No text before, no text after, no markdown fences. TOOL_NAME_HERE {"key":"value"} RULES: 1) When calling tools, you MUST use the XML format. 2) No text is allowed AFTER the XML block. 3) MUST be a single-line strict JSON object. Use double quotes. 4) Multiple tools must be inside the same root. 5) Do NOT wrap XML in markdown fences (` + "```" + `). 6) Do NOT invent parameters. Use only the provided schema. 7) CRITICAL: Do NOT use native tool markers like "<|Tool|>" or "<|tool|>". 8) CRITICAL: Do NOT output role markers like "<|System|>", "<|User|>", or "<|Assistant|>". 9) CRITICAL: Do NOT output internal monologues (e.g. "I will list files now..."). Just output your answer or the XML. ❌ WRONG — Do NOT do these: Wrong 1 — mixed text after XML: ... I hope this helps. Wrong 2 — function-call syntax: Grep({"pattern": "token"}) Wrong 3 — missing wrapper: ` + ex1 + `{} Wrong 4 — Markdown code fences: ` + "```xml" + ` ... ` + "```" + ` Wrong 5 — native tool tokens: <|Tool|>call_some_tool{"param":1}<|Tool|> Wrong 6 — role markers in response: <|Assistant|> Here is the result... Remember: The ONLY valid way to use tools is the XML block at the end of your response. ✅ CORRECT EXAMPLES: Example A — Single tool: ` + ex1 + ` ` + ex1Params + ` Example B — Two tools in parallel: ` + ex1 + ` ` + ex1Params + ` ` + ex2 + ` ` + ex2Params + ` Example C — Tool with complex nested JSON parameters: ` + ex3 + ` ` + ex3Params + ` Remember: Output ONLY the ... XML block when calling tools.` } func matchAny(name string, candidates ...string) bool { for _, c := range candidates { if name == c { return true } } return false } func exampleReadParams(name string) string { switch strings.TrimSpace(name) { case "Read": return `{"file_path":"README.md"}` case "Glob": return `{"pattern":"**/*.go","path":"."}` default: return `{"path":"src/main.go"}` } } func exampleWriteOrExecParams(name string) string { switch strings.TrimSpace(name) { case "Bash", "execute_command": return `{"command":"pwd"}` case "exec_command": return `{"cmd":"pwd"}` case "Edit": return `{"file_path":"README.md","old_string":"foo","new_string":"bar"}` case "MultiEdit": return `{"file_path":"README.md","edits":[{"old_string":"foo","new_string":"bar"}]}` default: return `{"path":"output.txt","content":"Hello world"}` } } func exampleInteractiveParams(name string) string { switch strings.TrimSpace(name) { case "Task": return `{"description":"Investigate flaky tests","prompt":"Run targeted tests and summarize failures"}` default: return `{"question":"Which approach do you prefer?","follow_up":[{"text":"Option A"},{"text":"Option B"}]}` } }