From 7c29ffbe7300539e3db8b187c20eef97c1afeae9 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Wed, 1 Oct 2025 07:01:37 +0000 Subject: [PATCH] fix: enhance tool use instructions for Ollama models - Add enhanced XML formatting instructions for local models (Ollama/LMStudio) - Include explicit examples of correct and incorrect tool usage - Add mandatory requirements to prevent models from adding explanatory text - Pass apiProvider through system prompt generation chain - Add comprehensive tests for the new functionality This addresses the issue where local Ollama models were not properly following the XML tool format, causing them to hallucinate code or enter infinite loops instead of using the codebase_search tool. Fixes #8430 --- .../sections/__tests__/tool-use.spec.ts | 98 +++++++++++++++++++ src/core/prompts/sections/tool-use.ts | 36 ++++++- src/core/prompts/system.ts | 5 +- src/core/task/Task.ts | 1 + src/core/webview/generateSystemPrompt.ts | 3 + 5 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 src/core/prompts/sections/__tests__/tool-use.spec.ts diff --git a/src/core/prompts/sections/__tests__/tool-use.spec.ts b/src/core/prompts/sections/__tests__/tool-use.spec.ts new file mode 100644 index 0000000000..d1b2f543bb --- /dev/null +++ b/src/core/prompts/sections/__tests__/tool-use.spec.ts @@ -0,0 +1,98 @@ +import { describe, it, expect } from "vitest" +import { getSharedToolUseSection } from "../tool-use" + +describe("getSharedToolUseSection", () => { + describe("base functionality", () => { + it("should return base tool use section when no apiProvider is provided", () => { + const result = getSharedToolUseSection() + + expect(result).toContain("TOOL USE") + expect(result).toContain("Tool uses are formatted using XML-style tags") + expect(result).toContain("") + expect(result).not.toContain("CRITICAL: Tool Use Requirements") + expect(result).not.toContain("MANDATORY") + }) + + it("should return base tool use section for non-local providers", () => { + const providers = ["anthropic", "openai", "openrouter", "bedrock"] + + providers.forEach((provider) => { + const result = getSharedToolUseSection(provider) + + expect(result).toContain("TOOL USE") + expect(result).toContain("Tool uses are formatted using XML-style tags") + expect(result).not.toContain("CRITICAL: Tool Use Requirements") + expect(result).not.toContain("MANDATORY") + }) + }) + }) + + describe("local model enhancements", () => { + it("should include enhanced instructions for ollama provider", () => { + const result = getSharedToolUseSection("ollama") + + // Check base content is still there + expect(result).toContain("TOOL USE") + expect(result).toContain("Tool uses are formatted using XML-style tags") + + // Check enhanced instructions + expect(result).toContain("CRITICAL: Tool Use Requirements for Your Response") + expect(result).toContain("MANDATORY") + expect(result).toContain("Every response MUST contain EXACTLY ONE tool use") + expect(result).toContain("DO NOT") + expect(result).toContain("Write explanations or text outside of the tool XML tags") + expect(result).toContain("Guess file locations or code content") + expect(result).toContain("ALWAYS") + expect(result).toContain("Start with codebase_search tool when exploring code") + + // Check examples + expect(result).toContain("Example of a CORRECT response") + expect(result).toContain("") + expect(result).toContain("main function entry point") + expect(result).toContain("") + + expect(result).toContain("Example of an INCORRECT response") + expect(result).toContain("I'll search for the main function") + + // Check final reminder + expect(result).toContain("Remember: Your ENTIRE response should be the tool XML, nothing else") + }) + + it("should include enhanced instructions for lmstudio provider", () => { + const result = getSharedToolUseSection("lmstudio") + + // Check base content is still there + expect(result).toContain("TOOL USE") + expect(result).toContain("Tool uses are formatted using XML-style tags") + + // Check enhanced instructions (same as ollama) + expect(result).toContain("CRITICAL: Tool Use Requirements for Your Response") + expect(result).toContain("MANDATORY") + expect(result).toContain("Every response MUST contain EXACTLY ONE tool use") + expect(result).toContain("DO NOT") + expect(result).toContain("ALWAYS") + expect(result).toContain("Example of a CORRECT response") + expect(result).toContain("Example of an INCORRECT response") + expect(result).toContain("Remember: Your ENTIRE response should be the tool XML, nothing else") + }) + }) + + describe("formatting and structure", () => { + it("should maintain proper formatting with line breaks", () => { + const result = getSharedToolUseSection("ollama") + + // Check that there are proper line breaks between sections + expect(result).toMatch(/TOOL USE\n\n/) + expect(result).toMatch(/# Tool Use Formatting\n\n/) + expect(result).toMatch(/# CRITICAL: Tool Use Requirements/) + }) + + it("should have consistent XML examples", () => { + const result = getSharedToolUseSection("ollama") + + // Check XML structure is properly formatted + expect(result).toMatch(/\nvalue1<\/parameter1_name>/) + expect(result).toMatch(/\n/) + }) + }) +}) diff --git a/src/core/prompts/sections/tool-use.ts b/src/core/prompts/sections/tool-use.ts index 28d47d0985..9a2af41f9c 100644 --- a/src/core/prompts/sections/tool-use.ts +++ b/src/core/prompts/sections/tool-use.ts @@ -1,5 +1,8 @@ -export function getSharedToolUseSection(): string { - return `==== +export function getSharedToolUseSection(apiProvider?: string): string { + // Enhanced instructions for local models that may struggle with tool formatting + const isLocalModel = apiProvider === "ollama" || apiProvider === "lmstudio" + + const baseSection = `==== TOOL USE @@ -16,4 +19,33 @@ Tool uses are formatted using XML-style tags. The tool name itself becomes the X Always use the actual tool name as the XML tag name for proper parsing and execution.` + + if (isLocalModel) { + return ( + baseSection + + ` + +# CRITICAL: Tool Use Requirements for Your Response + +**MANDATORY**: Every response MUST contain EXACTLY ONE tool use in the XML format shown above. +**DO NOT**: Write explanations or text outside of the tool XML tags. +**DO NOT**: Guess file locations or code content - use the appropriate search tools first. +**ALWAYS**: Start with codebase_search tool when exploring code for the first time. + +Example of a CORRECT response (using codebase_search): + +main function entry point + + +Example of an INCORRECT response (this will fail): +I'll search for the main function in your codebase. + +main function + + +Remember: Your ENTIRE response should be the tool XML, nothing else.` + ) + } + + return baseSection } diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 3cc327c815..f2eafa978b 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -62,6 +62,7 @@ async function generatePrompt( settings?: SystemPromptSettings, todoList?: TodoItem[], modelId?: string, + apiProvider?: string, ): Promise { if (!context) { throw new Error("Extension context is required for generating system prompt") @@ -92,7 +93,7 @@ async function generatePrompt( ${markdownFormattingSection()} -${getSharedToolUseSection()} +${getSharedToolUseSection(apiProvider)} ${getToolDescriptionsForMode( mode, @@ -153,6 +154,7 @@ export const SYSTEM_PROMPT = async ( settings?: SystemPromptSettings, todoList?: TodoItem[], modelId?: string, + apiProvider?: string, ): Promise => { if (!context) { throw new Error("Extension context is required for generating system prompt") @@ -225,5 +227,6 @@ ${customInstructions}` settings, todoList, modelId, + apiProvider, ) } diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 851df91e6c..1b7989b3ab 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -2445,6 +2445,7 @@ export class Task extends EventEmitter implements TaskLike { }, undefined, // todoList this.api.getModel().id, + this.apiConfiguration.apiProvider, ) })() } diff --git a/src/core/webview/generateSystemPrompt.ts b/src/core/webview/generateSystemPrompt.ts index a639d8a960..0e64de4958 100644 --- a/src/core/webview/generateSystemPrompt.ts +++ b/src/core/webview/generateSystemPrompt.ts @@ -89,6 +89,9 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web .getConfiguration("roo-cline") .get("newTaskRequireTodos", false), }, + undefined, // todoList + undefined, // modelId + apiConfiguration?.apiProvider, ) return systemPrompt