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
57 changes: 57 additions & 0 deletions src/core/prompts/__tests__/responses-openai-compatible.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { describe, it, expect } from "vitest"
import { formatResponse } from "../responses"

describe("formatResponse.noToolsUsed", () => {
it("should return standard message when no apiProvider is specified", () => {
const result = formatResponse.noToolsUsed()

expect(result).toContain("[ERROR] You did not use a tool in your previous response!")
expect(result).toContain("# Reminder: Instructions for Tool Use")
expect(result).not.toContain("OpenAI Compatible")
})

it("should return standard message for non-OpenAI-compatible providers", () => {
const result = formatResponse.noToolsUsed("anthropic")

expect(result).toContain("[ERROR] You did not use a tool in your previous response!")
expect(result).toContain("# Reminder: Instructions for Tool Use")
expect(result).not.toContain("OpenAI Compatible")
})

it("should include OpenAI Compatible specific hints for OpenAI-compatible providers", () => {
// Test with various OpenAI-compatible providers
const openAICompatibleProviders = ["openai", "openai-native", "fireworks", "groq", "ollama"]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it intentional that I'm only testing 5 providers here while the implementation supports 13? I should either test all of them or add a comment explaining why this subset is sufficient for testing.


for (const provider of openAICompatibleProviders) {
const result = formatResponse.noToolsUsed(provider)

expect(result).toContain("[ERROR] You did not use a tool in your previous response!")
expect(result).toContain("# Important Note for OpenAI Compatible Models")
expect(result).toContain("Your model appears to not be using the required XML tool format")
expect(result).toContain("Use XML tags for ALL tool invocations")
expect(result).toContain("Place tool uses at the END of your message")
expect(result).toContain("Use only ONE tool per message")
expect(result).toContain("Follow the exact XML format shown below")
expect(result).toContain("# Reminder: Instructions for Tool Use")
}
})

it("should maintain the same structure with Next Steps section", () => {
const resultStandard = formatResponse.noToolsUsed()
const resultOpenAI = formatResponse.noToolsUsed("openai-compatible")

// Both should have the Next Steps section
expect(resultStandard).toContain("# Next Steps")
expect(resultOpenAI).toContain("# Next Steps")

// Both should mention attempt_completion and ask_followup_question
expect(resultStandard).toContain("attempt_completion")
expect(resultStandard).toContain("ask_followup_question")
expect(resultOpenAI).toContain("attempt_completion")
expect(resultOpenAI).toContain("ask_followup_question")

// Both should end with the automated message note
expect(resultStandard).toContain("(This is an automated message, so do not respond to it conversationally.)")
expect(resultOpenAI).toContain("(This is an automated message, so do not respond to it conversationally.)")
})
})
47 changes: 41 additions & 6 deletions src/core/prompts/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,52 @@ export const formatResponse = {
rooIgnoreError: (path: string) =>
`Access to ${path} is blocked by the .rooignore file settings. You must try to continue in the task without using this file, or ask the user to update the .rooignore file.`,

noToolsUsed: () =>
`[ERROR] You did not use a tool in your previous response! Please retry with a tool use.
noToolsUsed: (apiProvider?: string) => {
const baseMessage = `[ERROR] You did not use a tool in your previous response! Please retry with a tool use.`

// List of providers that use OpenAI-compatible APIs
const openAICompatibleProviders = [
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I notice I've duplicated this openAICompatibleProviders array in multiple places (here, in Task.ts twice, and partially in tests). Should I consider extracting this to a shared constant to avoid maintenance issues? Maybe in a constants file or the types package?

"openai",
"openai-native",
"fireworks",
"groq",
"sambanova",
"chutes",
"roo",
"zai",
"io-intelligence",
"deepseek",
"moonshot",
"doubao",
"litellm",
"lmstudio",
"ollama",
]

let providerSpecificHint = ""
if (apiProvider && openAICompatibleProviders.includes(apiProvider)) {
providerSpecificHint = `

# Important Note for OpenAI Compatible Models

Your model appears to not be using the required XML tool format. Make sure to:
1. Use XML tags for ALL tool invocations (not JSON or function calls)
2. Place tool uses at the END of your message
3. Use only ONE tool per message
4. Follow the exact XML format shown below`
}

return `${baseMessage}${providerSpecificHint}

${toolUseInstructionsReminder}

# Next Steps

If you have completed the user's task, use the attempt_completion tool.
If you require additional information from the user, use the ask_followup_question tool.
Otherwise, if you have not completed the task and do not need additional information, then proceed with the next step of the task.
(This is an automated message, so do not respond to it conversationally.)`,
If you have completed the user's task, use the attempt_completion tool.
If you require additional information from the user, use the ask_followup_question tool.
Otherwise, if you have not completed the task and do not need additional information, then proceed with the next step of the task.
(This is an automated message, so do not respond to it conversationally.)`
},

tooManyMistakes: (feedback?: string) =>
`You seem to be having trouble proceeding. The user has provided the following feedback to help guide you:\n<feedback>\n${feedback}\n</feedback>`,
Expand Down
54 changes: 48 additions & 6 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1510,7 +1510,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
// the user hits max requests and denies resetting the count.
break
} else {
nextUserContent = [{ type: "text", text: formatResponse.noToolsUsed() }]
nextUserContent = [
{ type: "text", text: formatResponse.noToolsUsed(this.apiConfiguration.apiProvider) },
]
this.consecutiveMistakeCount++
}
}
Expand All @@ -1537,10 +1539,47 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
}

if (this.consecutiveMistakeLimit > 0 && this.consecutiveMistakeCount >= this.consecutiveMistakeLimit) {
const { response, text, images } = await this.ask(
"mistake_limit_reached",
t("common:errors.mistake_limit_guidance"),
)
// Provide more specific guidance for OpenAI-style API providers
const openAICompatibleProviders = [
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've duplicated the same provider list here again. This really should be a shared constant. Also, this error message construction (lines 1568-1579) could be extracted to a helper function for better testability.

"openai",
"openai-native",
"fireworks",
"groq",
"sambanova",
"chutes",
"roo",
"zai",
"io-intelligence",
"deepseek",
"moonshot",
"doubao",
"litellm",
"lmstudio",
"ollama",
]
const isOpenAICompatible =
this.apiConfiguration.apiProvider &&
openAICompatibleProviders.includes(this.apiConfiguration.apiProvider)
const modelId = getModelId(this.apiConfiguration)

let guidanceMessage = t("common:errors.mistake_limit_guidance")

if (isOpenAICompatible) {
guidanceMessage = `The model appears to be having difficulty with tool usage. This often happens with OpenAI-compatible API providers when the model doesn't properly format tool calls using XML tags.

Common issues with ${modelId || "this model"}:
1. The model may not be following the XML tool format correctly
2. The model might be responding conversationally instead of using tools
3. The model's output format may be incompatible with Roo Code's expectations

Try these solutions:
• Break down your request into smaller, more specific steps
• Be more explicit about what you want to accomplish
• Try a different model that better supports tool usage
• Ensure your API endpoint is properly configured`
}

const { response, text, images } = await this.ask("mistake_limit_reached", guidanceMessage)

if (response === "messageResponse") {
currentUserContent.push(
Expand Down Expand Up @@ -2108,7 +2147,10 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
const didToolUse = this.assistantMessageContent.some((block) => block.type === "tool_use")

if (!didToolUse) {
this.userMessageContent.push({ type: "text", text: formatResponse.noToolsUsed() })
this.userMessageContent.push({
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another duplication of the provider list. At this point, I'm just copy-pasting my own mistakes. Definitely needs to be DRY'd up.

type: "text",
text: formatResponse.noToolsUsed(this.apiConfiguration.apiProvider),
})
this.consecutiveMistakeCount++
}

Expand Down
Loading