diff --git a/src/core/prompts/__tests__/responses-openai-compatible.spec.ts b/src/core/prompts/__tests__/responses-openai-compatible.spec.ts new file mode 100644 index 0000000000..f4b0ac93be --- /dev/null +++ b/src/core/prompts/__tests__/responses-openai-compatible.spec.ts @@ -0,0 +1,52 @@ +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 when apiProvider is openai-compatible", () => { + const result = formatResponse.noToolsUsed("openai-compatible") + + 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.)") + }) +}) diff --git a/src/core/prompts/responses.ts b/src/core/prompts/responses.ts index 3f38789fdc..2e66d1dbaa 100644 --- a/src/core/prompts/responses.ts +++ b/src/core/prompts/responses.ts @@ -18,17 +18,33 @@ 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.` + + let providerSpecificHint = "" + if (apiProvider === "openai-compatible") { + 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\n${feedback}\n`, diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 34f3218236..96baed4c8c 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1510,7 +1510,9 @@ export class Task extends EventEmitter 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++ } } @@ -1537,10 +1539,28 @@ export class Task extends EventEmitter 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 Compatible providers + const isOpenAICompatible = this.apiConfiguration.apiProvider === "openai-compatible" + 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 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 OpenAI Compatible endpoint is properly configured` + } + + const { response, text, images } = await this.ask("mistake_limit_reached", guidanceMessage) if (response === "messageResponse") { currentUserContent.push( @@ -2108,7 +2128,10 @@ export class Task extends EventEmitter 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({ + type: "text", + text: formatResponse.noToolsUsed(this.apiConfiguration.apiProvider), + }) this.consecutiveMistakeCount++ }