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
2 changes: 2 additions & 0 deletions packages/types/src/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { z } from "zod"
* - `browser_action_launch`: Permission to open or interact with a browser
* - `use_mcp_server`: Permission to use Model Context Protocol (MCP) server functionality
* - `auto_approval_max_req_reached`: Auto-approval limit has been reached, manual approval required
* - `temperature_tool_error`: Tool failed due to high temperature setting, asking user to reduce temperature and retry
*/
export const clineAsks = [
"followup",
Expand All @@ -38,6 +39,7 @@ export const clineAsks = [
"browser_action_launch",
"use_mcp_server",
"auto_approval_max_req_reached",
"temperature_tool_error",
] as const

export const clineAskSchema = z.enum(clineAsks)
Expand Down
20 changes: 20 additions & 0 deletions src/core/tools/applyDiffTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { formatResponse } from "../prompts/responses"
import { fileExistsAtPath } from "../../utils/fs"
import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
import { unescapeHtmlEntities } from "../../utils/text-normalization"
import { isTemperatureRelatedError, getTemperatureErrorMessage } from "./utils/temperatureErrorDetection"

export async function applyDiffToolLegacy(
cline: Task,
Expand Down Expand Up @@ -133,6 +134,25 @@ export async function applyDiffToolLegacy(
await cline.say("diff_error", formattedError)
}

// Check if this is a temperature-related error
if (isTemperatureRelatedError("apply_diff", formattedError, cline)) {
const currentTemperature = cline.apiConfiguration?.modelTemperature ?? 0.0
const temperatureMessage = getTemperatureErrorMessage(currentTemperature)

// Ask user if they want to reduce temperature and retry
const askMessage = JSON.stringify({
tool: "apply_diff",
path: getReadablePath(cline.cwd, relPath),
error: formattedError,
temperatureMessage,
currentTemperature,
})

await cline.ask("temperature_tool_error", askMessage)
cline.recordToolError("apply_diff", formattedError)
return
}

cline.recordToolError("apply_diff", formattedError)

pushToolResult(formattedError)
Expand Down
155 changes: 155 additions & 0 deletions src/core/tools/utils/__tests__/temperatureErrorDetection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { describe, it, expect, vi, beforeEach } from "vitest"
import { isTemperatureRelatedError } from "../temperatureErrorDetection"
import type { Task } from "../../../task/Task"
import type { ProviderSettings } from "@roo-code/types"

describe("temperatureErrorDetection", () => {
let mockTask: Partial<Task>
let mockApiConfiguration: ProviderSettings

beforeEach(() => {
mockApiConfiguration = {
apiProvider: "anthropic",
apiModelId: "claude-3-5-sonnet-20241022",
modelTemperature: 0.7,
} as ProviderSettings

mockTask = {
apiConfiguration: mockApiConfiguration,
}
})

describe("isTemperatureRelatedError", () => {
it("should return false for non-temperature related errors", () => {
const result = isTemperatureRelatedError("write_to_file", "Permission denied", mockTask as Task)
expect(result).toBe(false)
})

it("should return false when temperature is already low (0.2 or below)", () => {
mockApiConfiguration.modelTemperature = 0.2
const result = isTemperatureRelatedError(
"write_to_file",
"Error: content appears to be truncated",
mockTask as Task,
)
expect(result).toBe(false)
})

it("should return false when temperature is undefined (using default)", () => {
mockApiConfiguration.modelTemperature = undefined
const result = isTemperatureRelatedError(
"write_to_file",
"Error: content appears to be truncated",
mockTask as Task,
)
expect(result).toBe(false)
})

it('should return true for "content appears to be truncated" error with high temperature', () => {
const result = isTemperatureRelatedError(
"write_to_file",
"Error: content appears to be truncated",
mockTask as Task,
)
expect(result).toBe(true)
})

it('should return true for "rest of code unchanged" error with high temperature', () => {
const result = isTemperatureRelatedError(
"apply_diff",
'Error: Found "// rest of code unchanged" in the content',
mockTask as Task,
)
expect(result).toBe(true)
})

it('should return true for "previous code" error with high temperature', () => {
const result = isTemperatureRelatedError(
"write_to_file",
'Error: Content contains "// ... previous code ..." placeholder',
mockTask as Task,
)
expect(result).toBe(true)
})

it('should return true for "existing code" error with high temperature', () => {
const result = isTemperatureRelatedError(
"apply_diff",
'Error: Found "// ... existing code ..." in the diff',
mockTask as Task,
)
expect(result).toBe(true)
})

it('should return true for "keep the rest" error with high temperature', () => {
const result = isTemperatureRelatedError(
"write_to_file",
'Error: Content includes "// keep the rest of the file" comment',
mockTask as Task,
)
expect(result).toBe(true)
})

it('should return true for "remaining code" error with high temperature', () => {
const result = isTemperatureRelatedError(
"apply_diff",
'Error: Found "// ... remaining code ..." in the content',
mockTask as Task,
)
expect(result).toBe(true)
})

it("should handle Error objects as well as strings", () => {
const error = new Error("content appears to be truncated")
const result = isTemperatureRelatedError("write_to_file", error, mockTask as Task)
expect(result).toBe(true)
})

it("should be case insensitive when checking error patterns", () => {
const result = isTemperatureRelatedError(
"write_to_file",
"ERROR: CONTENT APPEARS TO BE TRUNCATED",
mockTask as Task,
)
expect(result).toBe(true)
})

it("should return false when temperature is 0", () => {
mockApiConfiguration.modelTemperature = 0
const result = isTemperatureRelatedError(
"write_to_file",
"Error: content appears to be truncated",
mockTask as Task,
)
expect(result).toBe(false)
})

it("should return true when temperature is exactly 0.3", () => {
mockApiConfiguration.modelTemperature = 0.3
const result = isTemperatureRelatedError(
"write_to_file",
"Error: content appears to be truncated",
mockTask as Task,
)
expect(result).toBe(true)
})

it("should work with apply_diff tool", () => {
const result = isTemperatureRelatedError(
"apply_diff",
"Error: content appears to be truncated",
mockTask as Task,
)
expect(result).toBe(true)
})

it("should return false for other tools even with temperature error patterns", () => {
const result = isTemperatureRelatedError(
"read_file",
"Error: content appears to be truncated",
mockTask as Task,
)
expect(result).toBe(false)
})
})
})
93 changes: 93 additions & 0 deletions src/core/tools/utils/temperatureErrorDetection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Utility functions for detecting temperature-related tool errors
*/

import { Task } from "../../task/Task"

/**
* Error patterns that indicate temperature-related issues
*/
const TEMPERATURE_ERROR_PATTERNS = [
// Direct truncation indicators
/content appears to be truncated/i,
/found comments indicating omitted code/i,
/rest of code unchanged/i,
/previous code/i,
/code omitted/i,
/truncated after \d+ lines/i,
/keep the rest/i,

// Common AI placeholder patterns when temperature is high
/\/\/\s*\.\.\./,
/\/\*\s*\.\.\.\s*\*\//,
/\[\s*\.\.\.\s*\]/,
/\{\s*\.\.\.\s*\}/,

// Incomplete content indicators
/incomplete file content/i,
/partial content/i,
/content was cut off/i,
]

/**
* Tool names that commonly experience temperature-related failures
*/
const TEMPERATURE_SENSITIVE_TOOLS = ["write_to_file", "apply_diff"]

/**
* Checks if an error is likely caused by high temperature settings
* @param toolName The name of the tool that failed
* @param error The error message or Error object
* @param task The current task instance to check temperature settings
* @returns True if the error appears to be temperature-related
*/
export function isTemperatureRelatedError(toolName: string, error: string | Error, task: Task): boolean {
// Only check for temperature errors on specific tools
if (!TEMPERATURE_SENSITIVE_TOOLS.includes(toolName)) {
return false
}

// Get current temperature from API configuration
const currentTemperature = task.apiConfiguration?.modelTemperature ?? 0.0

// Only consider it a temperature issue if temperature is above 0.2
if (currentTemperature <= 0.2) {
return false
}

// Check if the user has customized the temperature (not using default)
// Most providers default to 0.0 or 1.0, so anything else is likely custom
const isCustomTemperature = currentTemperature !== 0.0 && currentTemperature !== 1.0

if (!isCustomTemperature) {
return false
}

// Convert error to string for pattern matching
const errorMessage = typeof error === "string" ? error : error.message || ""

// Check if error matches any temperature-related patterns
return TEMPERATURE_ERROR_PATTERNS.some((pattern) => pattern.test(errorMessage))
}

/**
* Gets a user-friendly message explaining the temperature issue
* @param currentTemperature The current temperature setting
* @returns A message explaining the issue
*/
export function getTemperatureErrorMessage(currentTemperature: number): string {
return (
`It looks like the tool failed due to your current temperature setting (${currentTemperature.toFixed(1)}). ` +
`Higher temperature values can cause the AI to generate incomplete or malformed outputs. ` +
`Reducing the temperature to 0.2 often resolves these issues.`
)
}

/**
* Checks if the temperature can be reduced further
* @param currentTemperature The current temperature setting
* @returns True if temperature can be reduced to 0.2
*/
export function canReduceTemperature(currentTemperature: number): boolean {
return currentTemperature > 0.2
}
32 changes: 25 additions & 7 deletions src/core/tools/writeToFileTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { isPathOutsideWorkspace } from "../../utils/pathUtils"
import { detectCodeOmission } from "../../integrations/editor/detect-omission"
import { unescapeHtmlEntities } from "../../utils/text-normalization"
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
import { isTemperatureRelatedError, getTemperatureErrorMessage } from "./utils/temperatureErrorDetection"

export async function writeToFileTool(
cline: Task,
Expand Down Expand Up @@ -172,13 +173,30 @@ export async function writeToFileTool(
if (cline.diffStrategy) {
await cline.diffViewProvider.revertChanges()

pushToolResult(
formatResponse.toolError(
`Content appears to be truncated (file has ${
newContent.split("\n").length
} lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`,
),
)
const errorMessage = `Content appears to be truncated (file has ${
newContent.split("\n").length
} lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`

// Check if this is a temperature-related error
if (isTemperatureRelatedError("write_to_file", errorMessage, cline)) {
const currentTemperature = cline.apiConfiguration?.modelTemperature ?? 0.0
const temperatureMessage = getTemperatureErrorMessage(currentTemperature)

// Ask user if they want to reduce temperature and retry
const askMessage = JSON.stringify({
tool: "write_to_file",
path: getReadablePath(cline.cwd, relPath),
error: errorMessage,
temperatureMessage,
currentTemperature,
})

await cline.ask("temperature_tool_error", askMessage)
cline.recordToolError("write_to_file", errorMessage)
return
}

pushToolResult(formatResponse.toolError(errorMessage))
return
} else {
vscode.window
Expand Down
Loading
Loading