Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions src/core/prompts/sections/__tests__/tool-use.spec.ts
Original file line number Diff line number Diff line change
@@ -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("<actual_tool_name>")
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("<codebase_search>")
expect(result).toContain("<query>main function entry point</query>")
expect(result).toContain("</codebase_search>")

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", () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Consider adding test coverage for provider case/alias normalization (e.g., 'LMStudio' and 'lm-studio') to prevent regressions. Example to add near the existing lmstudio test:

it('should include enhanced instructions for LMStudio (case-insensitive and alias)', () => {
  const result1 = getSharedToolUseSection('LMStudio')
  const result2 = getSharedToolUseSection('lm-studio')
  ;[result1, result2].forEach((result) => {
    expect(result).toContain('CRITICAL: Tool Use Requirements for Your Response')
    expect(result).toContain('Every response MUST contain EXACTLY ONE tool use')
  })
})

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(/<actual_tool_name>\n<parameter1_name>value1<\/parameter1_name>/)
expect(result).toMatch(/<codebase_search>\n<query>/)
})
})
})
36 changes: 34 additions & 2 deletions src/core/prompts/sections/tool-use.ts
Original file line number Diff line number Diff line change
@@ -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"
Comment on lines +2 to +3
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Normalize provider for robustness and accept common alias. This avoids missing enhanced instructions when the provider value differs in case or includes a hyphen (e.g., 'LMStudio', 'lm-studio'). Suggested minimal change below:

Suggested change
// Enhanced instructions for local models that may struggle with tool formatting
const isLocalModel = apiProvider === "ollama" || apiProvider === "lmstudio"
// Enhanced instructions for local models that may struggle with tool formatting
const provider = (apiProvider ?? '').toLowerCase()
const isLocalModel = provider === 'ollama' || provider === 'lmstudio' || provider === 'lm-studio'


const baseSection = `====

TOOL USE

Expand All @@ -16,4 +19,33 @@ Tool uses are formatted using XML-style tags. The tool name itself becomes the X
</actual_tool_name>

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):
<codebase_search>
<query>main function entry point</query>
</codebase_search>

Example of an INCORRECT response (this will fail):
I'll search for the main function in your codebase.
<codebase_search>
<query>main function</query>
</codebase_search>

Remember: Your ENTIRE response should be the tool XML, nothing else.`
)
}

return baseSection
}
5 changes: 4 additions & 1 deletion src/core/prompts/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ async function generatePrompt(
settings?: SystemPromptSettings,
todoList?: TodoItem[],
modelId?: string,
apiProvider?: string,
): Promise<string> {
if (!context) {
throw new Error("Extension context is required for generating system prompt")
Expand Down Expand Up @@ -92,7 +93,7 @@ async function generatePrompt(

${markdownFormattingSection()}

${getSharedToolUseSection()}
${getSharedToolUseSection(apiProvider)}

${getToolDescriptionsForMode(
mode,
Expand Down Expand Up @@ -153,6 +154,7 @@ export const SYSTEM_PROMPT = async (
settings?: SystemPromptSettings,
todoList?: TodoItem[],
modelId?: string,
apiProvider?: string,
): Promise<string> => {
if (!context) {
throw new Error("Extension context is required for generating system prompt")
Expand Down Expand Up @@ -225,5 +227,6 @@ ${customInstructions}`
settings,
todoList,
modelId,
apiProvider,
)
}
1 change: 1 addition & 0 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2445,6 +2445,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
},
undefined, // todoList
this.api.getModel().id,
this.apiConfiguration.apiProvider,
)
})()
}
Expand Down
3 changes: 3 additions & 0 deletions src/core/webview/generateSystemPrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web
.getConfiguration("roo-cline")
.get<boolean>("newTaskRequireTodos", false),
},
undefined, // todoList
undefined, // modelId
apiConfiguration?.apiProvider,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Optional: If provider normalization is handled in src/core/prompts/sections/tool-use.ts, no action needed here. Otherwise, consider normalizing apiConfiguration?.apiProvider at the call-site to reduce duplication.

)

return systemPrompt
Expand Down
Loading