mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
fix: improve tool call failure handling and error recovery
- Enhanced error handling in presentAssistantMessage.ts with better tool error tracking - Improved ToolRepetitionDetector with contextual failure history and intelligent error messages - Added enhanced logging and debugging information for tool failures - Updated error messages to be more helpful and actionable with specific suggestions - Added telemetry tracking for tool validation and execution failures - Improved consecutive mistake handling with better user guidance Fixes #5927
This commit is contained in:
parent
90148401e9
commit
038aeecd5b
4 changed files with 146 additions and 5 deletions
|
|
@ -2,6 +2,7 @@ import cloneDeep from "clone-deep"
|
|||
import { serializeError } from "serialize-error"
|
||||
|
||||
import type { ToolName, ClineAsk, ToolProgressStatus } from "@roo-code/types"
|
||||
import { TelemetryEventName } from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
import { defaultModeSlug, getModeBySlug } from "../../shared/modes"
|
||||
|
|
@ -307,6 +308,19 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
const handleError = async (action: string, error: Error) => {
|
||||
const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}`
|
||||
|
||||
// Record the tool error for tracking and telemetry
|
||||
cline.recordToolError(block.name as ToolName, error.message)
|
||||
|
||||
// Capture detailed telemetry for tool failures using existing event
|
||||
TelemetryService.instance.captureEvent(TelemetryEventName.DIFF_APPLICATION_ERROR, {
|
||||
taskId: cline.taskId,
|
||||
toolName: block.name,
|
||||
action,
|
||||
errorMessage: error.message,
|
||||
errorType: error.constructor.name,
|
||||
consecutiveMistakes: cline.consecutiveMistakeCount,
|
||||
})
|
||||
|
||||
await cline.say(
|
||||
"error",
|
||||
`Error ${action}:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`,
|
||||
|
|
@ -364,7 +378,22 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
)
|
||||
} catch (error) {
|
||||
cline.consecutiveMistakeCount++
|
||||
pushToolResult(formatResponse.toolError(error.message))
|
||||
// Record the validation error for tracking
|
||||
cline.recordToolError(block.name as ToolName, `Validation failed: ${error.message}`)
|
||||
|
||||
// Capture telemetry for validation failures using existing event
|
||||
TelemetryService.instance.captureEvent(TelemetryEventName.SCHEMA_VALIDATION_ERROR, {
|
||||
taskId: cline.taskId,
|
||||
toolName: block.name,
|
||||
errorMessage: error.message,
|
||||
consecutiveMistakes: cline.consecutiveMistakeCount,
|
||||
mode: mode ?? defaultModeSlug,
|
||||
})
|
||||
|
||||
// Provide more helpful error message for tool validation failures
|
||||
const enhancedErrorMessage = `Tool validation failed for ${block.name}: ${error.message}\n\nThis may indicate:\n- Invalid parameters provided to the tool\n- Tool not allowed in current mode\n- Missing required parameters\n\nPlease review the tool usage and try again with correct parameters.`
|
||||
|
||||
pushToolResult(formatResponse.toolError(enhancedErrorMessage))
|
||||
break
|
||||
}
|
||||
|
||||
|
|
@ -399,6 +428,9 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
TelemetryService.instance.captureConsecutiveMistakeError(cline.taskId)
|
||||
}
|
||||
|
||||
// Capture telemetry for tool repetition using existing event
|
||||
TelemetryService.instance.captureConsecutiveMistakeError(cline.taskId)
|
||||
|
||||
// Return tool result message about the repetition
|
||||
pushToolResult(
|
||||
formatResponse.toolError(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,31 @@ export const formatResponse = {
|
|||
toolApprovedWithFeedback: (feedback?: string) =>
|
||||
`The user approved this operation and provided the following context:\n<feedback>\n${feedback}\n</feedback>`,
|
||||
|
||||
toolError: (error?: string) => `The tool execution failed with the following error:\n<error>\n${error}\n</error>`,
|
||||
toolError: (error?: string) => {
|
||||
const baseMessage = `The tool execution failed with the following error:\n<error>\n${error}\n</error>`
|
||||
|
||||
// Add helpful suggestions based on common error patterns
|
||||
let suggestions = ""
|
||||
if (error) {
|
||||
const lowerError = error.toLowerCase()
|
||||
|
||||
if (lowerError.includes("permission") || lowerError.includes("access denied")) {
|
||||
suggestions =
|
||||
"\n\nSuggestions:\n• Check file permissions\n• Ensure the file is not locked by another process\n• Try using a different file path"
|
||||
} else if (lowerError.includes("not found") || lowerError.includes("no such file")) {
|
||||
suggestions =
|
||||
"\n\nSuggestions:\n• Verify the file path is correct\n• Use list_files to check available files\n• Create the file first if it doesn't exist"
|
||||
} else if (lowerError.includes("validation failed")) {
|
||||
suggestions =
|
||||
"\n\nSuggestions:\n• Check that all required parameters are provided\n• Verify parameter formats match expectations\n• Review the tool documentation for correct usage"
|
||||
} else if (lowerError.includes("syntax error") || lowerError.includes("invalid")) {
|
||||
suggestions =
|
||||
"\n\nSuggestions:\n• Check the syntax of your input\n• Verify all brackets, quotes, and tags are properly closed\n• Try simplifying the operation"
|
||||
}
|
||||
}
|
||||
|
||||
return baseMessage + suggestions
|
||||
},
|
||||
|
||||
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.`,
|
||||
|
|
@ -30,8 +54,17 @@ If you require additional information from the user, use the ask_followup_questi
|
|||
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>`,
|
||||
tooManyMistakes: (feedback?: string) => {
|
||||
let message = `You seem to be having trouble proceeding. This may indicate:\n• Tool parameters are incorrect\n• The approach needs to be changed\n• The task should be broken into smaller steps\n• A different tool might be more appropriate`
|
||||
|
||||
if (feedback) {
|
||||
message += `\n\nThe user has provided the following feedback to help guide you:\n<feedback>\n${feedback}\n</feedback>`
|
||||
} else {
|
||||
message += `\n\nConsider:\n• Re-reading files to understand current state\n• Using simpler, more targeted operations\n• Asking for clarification if the task is unclear`
|
||||
}
|
||||
|
||||
return message
|
||||
},
|
||||
|
||||
missingToolParameterError: (paramName: string) =>
|
||||
`Missing value for required parameter '${paramName}'. Please retry with complete response.\n\n${toolUseInstructionsReminder}`,
|
||||
|
|
|
|||
|
|
@ -1932,9 +1932,21 @@ export class Task extends EventEmitter<ClineEvents> {
|
|||
|
||||
this.toolUsage[toolName].failures++
|
||||
|
||||
// Record the failure in the repetition detector for better context
|
||||
this.toolRepetitionDetector.recordToolFailure(toolName, error)
|
||||
|
||||
if (error) {
|
||||
this.emit("taskToolFailed", this.taskId, toolName, error)
|
||||
}
|
||||
|
||||
// Add enhanced logging for debugging tool failures
|
||||
console.debug(`[Task ${this.taskId}] Tool failure recorded:`, {
|
||||
toolName,
|
||||
error,
|
||||
totalFailures: this.toolUsage[toolName].failures,
|
||||
totalAttempts: this.toolUsage[toolName].attempts,
|
||||
consecutiveMistakes: this.consecutiveMistakeCount,
|
||||
})
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ export class ToolRepetitionDetector {
|
|||
private previousToolCallJson: string | null = null
|
||||
private consecutiveIdenticalToolCallCount: number = 0
|
||||
private readonly consecutiveIdenticalToolCallLimit: number
|
||||
private toolFailureHistory: Array<{ toolName: string; timestamp: number; reason?: string }> = []
|
||||
private readonly maxHistorySize = 10
|
||||
|
||||
/**
|
||||
* Creates a new ToolRepetitionDetector
|
||||
|
|
@ -48,6 +50,10 @@ export class ToolRepetitionDetector {
|
|||
this.consecutiveIdenticalToolCallLimit > 0 &&
|
||||
this.consecutiveIdenticalToolCallCount >= this.consecutiveIdenticalToolCallLimit
|
||||
) {
|
||||
// Generate enhanced error message with context
|
||||
const recentFailures = this.getRecentFailureContext(currentToolCallBlock.name)
|
||||
const contextMessage = this.generateContextualErrorMessage(currentToolCallBlock.name, recentFailures)
|
||||
|
||||
// Reset counters to allow recovery if user guides the AI past this point
|
||||
this.consecutiveIdenticalToolCallCount = 0
|
||||
this.previousToolCallJson = null
|
||||
|
|
@ -57,7 +63,7 @@ export class ToolRepetitionDetector {
|
|||
allowExecution: false,
|
||||
askUser: {
|
||||
messageKey: "mistake_limit_reached",
|
||||
messageDetail: t("tools:toolRepetitionLimitReached", { toolName: currentToolCallBlock.name }),
|
||||
messageDetail: contextMessage,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -66,6 +72,64 @@ export class ToolRepetitionDetector {
|
|||
return { allowExecution: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a tool failure for better context in future error messages
|
||||
*/
|
||||
public recordToolFailure(toolName: string, reason?: string): void {
|
||||
this.toolFailureHistory.push({
|
||||
toolName,
|
||||
timestamp: Date.now(),
|
||||
reason,
|
||||
})
|
||||
|
||||
// Keep history size manageable
|
||||
if (this.toolFailureHistory.length > this.maxHistorySize) {
|
||||
this.toolFailureHistory.shift()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets recent failure context for a specific tool
|
||||
*/
|
||||
private getRecentFailureContext(toolName: string): Array<{ reason?: string; timestamp: number }> {
|
||||
const fiveMinutesAgo = Date.now() - 5 * 60 * 1000
|
||||
return this.toolFailureHistory
|
||||
.filter((failure) => failure.toolName === toolName && failure.timestamp > fiveMinutesAgo)
|
||||
.map((failure) => ({ reason: failure.reason, timestamp: failure.timestamp }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a contextual error message based on recent failures
|
||||
*/
|
||||
private generateContextualErrorMessage(
|
||||
toolName: string,
|
||||
recentFailures: Array<{ reason?: string; timestamp: number }>,
|
||||
): string {
|
||||
let message = t("tools:toolRepetitionLimitReached", { toolName })
|
||||
|
||||
if (recentFailures.length > 0) {
|
||||
message += "\n\nRecent issues with this tool:"
|
||||
const uniqueReasons = [...new Set(recentFailures.map((f) => f.reason).filter(Boolean))]
|
||||
|
||||
if (uniqueReasons.length > 0) {
|
||||
message += "\n" + uniqueReasons.map((reason) => `• ${reason}`).join("\n")
|
||||
}
|
||||
|
||||
message += "\n\nSuggestions:"
|
||||
message += "\n• Try a different approach or tool"
|
||||
message += "\n• Check if the parameters are correct"
|
||||
message += "\n• Consider breaking down the task into smaller steps"
|
||||
|
||||
if (toolName === "apply_diff" || toolName === "write_to_file") {
|
||||
message += "\n• Use read_file first to understand the current file content"
|
||||
} else if (toolName === "execute_command") {
|
||||
message += "\n• Verify the command syntax and file paths"
|
||||
}
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a ToolUse object into a canonical JSON string for comparison
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue