diff --git a/internal/adapter/openai/prompt_build_test.go b/internal/adapter/openai/prompt_build_test.go index 223689b..3b85be3 100644 --- a/internal/adapter/openai/prompt_build_test.go +++ b/internal/adapter/openai/prompt_build_test.go @@ -86,6 +86,12 @@ func TestBuildOpenAIFinalPrompt_VercelPreparePathKeepsFinalAnswerInstruction(t * if !strings.Contains(finalPrompt, "Do NOT wrap the XML in markdown code fences") { t.Fatalf("vercel prepare finalPrompt missing no-fence xml instruction: %q", finalPrompt) } + if !strings.Contains(finalPrompt, "If a tool call fails, is rejected, or returns no usable result") { + t.Fatalf("vercel prepare finalPrompt missing failure recovery instruction: %q", finalPrompt) + } + if !strings.Contains(finalPrompt, "Never claim that a tool was run") { + t.Fatalf("vercel prepare finalPrompt missing no-fabrication instruction: %q", finalPrompt) + } if strings.Contains(finalPrompt, "```json") { t.Fatalf("vercel prepare finalPrompt should not require fenced tool calls: %q", finalPrompt) } diff --git a/internal/util/tool_prompt.go b/internal/util/tool_prompt.go index 13ea906..bec9490 100644 --- a/internal/util/tool_prompt.go +++ b/internal/util/tool_prompt.go @@ -36,7 +36,7 @@ func BuildToolCallInstructions(toolNames []string) string { 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. +When calling tools, the final block of your response MUST be raw XML. Do not use markdown fences. Do not put any text after the XML block. @@ -46,14 +46,17 @@ When calling tools, emit ONLY raw XML at the very end of your response. No text RULES: -1) Output ONLY the XML above when calling tools. Do NOT mix tool XML with regular text. +1) When calling tools, the final block of your response MUST be the XML above. Any explanatory text must appear before that block, never after it. 2) MUST contain a strict JSON object. All JSON keys and strings use double quotes. 3) Multiple tools → multiple blocks inside ONE root. 4) Do NOT wrap the XML in markdown code fences (no triple backticks). 5) After receiving a tool result, use it directly. Only call another tool if the result is insufficient. -6) If you want to say something AND call a tool, output text first, then the XML block on its own. +6) If you want to say something AND call a tool, output text first, then the XML block on its own. Do not narrate pseudo-tool calls in plain text. 7) Parameters MUST use the exact field names from the selected tool schema. 8) CRITICAL: Do NOT invent or add any extra fields (such as "_raw", "_xml"). Use ONLY the fields strictly defined in the schema. Extra fields will cause execution failure. +9) Never claim that a tool was run, or that you saw search/browser/file results, unless you actually received a tool result for that tool call. +10) If a tool call fails, is rejected, or returns no usable result: do NOT fabricate the missing result. Either retry once with corrected parameters when the fix is obvious, or ask the user / answer with the limitation. +11) Never output internal tool-planning traces such as "let me search", "calling tool", "", "", or fake tool transcripts unless you are emitting a real tool call block at the end. ❌ WRONG — Do NOT do these: Wrong 1 — mixed text and XML: @@ -64,6 +67,11 @@ Wrong 3 — missing wrapper: ` + ex1 + `{} Wrong 4 — extra/invented fields: {"_raw": "...", "command": "ls"} +Wrong 5 — fake tool execution/result narration: + Let me search the web first. + Search results show the answer is 42. +Wrong 6 — tool failed, but model pretends it succeeded: + The tool returned three skills, so install review-implementing. ✅ CORRECT EXAMPLES: @@ -96,7 +104,7 @@ Example C — Tool with complex nested JSON parameters: -Remember: Output ONLY the ... XML block when calling tools.` +Remember: when calling tools, end with ONE real ... XML block and do not invent tool results.` } func matchAny(name string, candidates ...string) bool { diff --git a/internal/util/tool_prompt_test.go b/internal/util/tool_prompt_test.go index e10f176..e4d6ae4 100644 --- a/internal/util/tool_prompt_test.go +++ b/internal/util/tool_prompt_test.go @@ -24,3 +24,26 @@ func TestBuildToolCallInstructions_ExecuteCommandUsesCommandExample(t *testing.T t.Fatalf("expected command parameter example for execute_command, got: %s", out) } } + +func TestBuildToolCallInstructions_IncludesFailureRecoveryAndNoFabricationRules(t *testing.T) { + out := BuildToolCallInstructions([]string{"WebSearch"}) + if !strings.Contains(out, "Never claim that a tool was run") { + t.Fatalf("expected no-fabrication guard, got: %s", out) + } + if !strings.Contains(out, "If a tool call fails, is rejected, or returns no usable result") { + t.Fatalf("expected failure recovery rule, got: %s", out) + } + if !strings.Contains(out, "Never output internal tool-planning traces") { + t.Fatalf("expected no internal trace rule, got: %s", out) + } +} + +func TestBuildToolCallInstructions_AllowsTextBeforeXMLButNotAfter(t *testing.T) { + out := BuildToolCallInstructions([]string{"read_file"}) + if !strings.Contains(out, "Any explanatory text must appear before that block, never after it.") { + t.Fatalf("expected clarified xml placement rule, got: %s", out) + } + if strings.Contains(out, "No text before, no text after") { + t.Fatalf("expected contradictory no-text-before wording removed, got: %s", out) + } +}