Skip to content

Commit 17a378a

Browse files
committed
feat: add automatic temperature reduction and retry for tool failures
- Add temperature error detection utility to identify temperature-related failures - Add new "temperature_tool_error" ClineAsk type for UI interaction - Modify writeToFileTool and applyDiffTool to detect temperature errors - Add UI handler in ChatView to show temperature reduction option - Implement retry mechanism that reduces temperature to 0.2 and retries - Add comprehensive unit and integration tests Fixes #6156
1 parent 1e17b3b commit 17a378a

File tree

9 files changed

+733
-8
lines changed

9 files changed

+733
-8
lines changed

packages/types/src/message.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { z } from "zod"
2424
* - `browser_action_launch`: Permission to open or interact with a browser
2525
* - `use_mcp_server`: Permission to use Model Context Protocol (MCP) server functionality
2626
* - `auto_approval_max_req_reached`: Auto-approval limit has been reached, manual approval required
27+
* - `temperature_tool_error`: Tool failed due to high temperature setting, asking user to reduce temperature and retry
2728
*/
2829
export const clineAsks = [
2930
"followup",
@@ -38,6 +39,7 @@ export const clineAsks = [
3839
"browser_action_launch",
3940
"use_mcp_server",
4041
"auto_approval_max_req_reached",
42+
"temperature_tool_error",
4143
] as const
4244

4345
export const clineAskSchema = z.enum(clineAsks)

src/core/tools/applyDiffTool.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { formatResponse } from "../prompts/responses"
1212
import { fileExistsAtPath } from "../../utils/fs"
1313
import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
1414
import { unescapeHtmlEntities } from "../../utils/text-normalization"
15+
import { isTemperatureRelatedError, getTemperatureErrorMessage } from "./utils/temperatureErrorDetection"
1516

1617
export async function applyDiffToolLegacy(
1718
cline: Task,
@@ -133,6 +134,25 @@ export async function applyDiffToolLegacy(
133134
await cline.say("diff_error", formattedError)
134135
}
135136

137+
// Check if this is a temperature-related error
138+
if (isTemperatureRelatedError("apply_diff", formattedError, cline)) {
139+
const currentTemperature = cline.apiConfiguration?.modelTemperature ?? 0.0
140+
const temperatureMessage = getTemperatureErrorMessage(currentTemperature)
141+
142+
// Ask user if they want to reduce temperature and retry
143+
const askMessage = JSON.stringify({
144+
tool: "apply_diff",
145+
path: getReadablePath(cline.cwd, relPath),
146+
error: formattedError,
147+
temperatureMessage,
148+
currentTemperature,
149+
})
150+
151+
await cline.ask("temperature_tool_error", askMessage)
152+
cline.recordToolError("apply_diff", formattedError)
153+
return
154+
}
155+
136156
cline.recordToolError("apply_diff", formattedError)
137157

138158
pushToolResult(formattedError)
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { describe, it, expect, vi, beforeEach } from "vitest"
2+
import { isTemperatureRelatedError } from "../temperatureErrorDetection"
3+
import type { Task } from "../../../task/Task"
4+
import type { ProviderSettings } from "@roo-code/types"
5+
6+
describe("temperatureErrorDetection", () => {
7+
let mockTask: Partial<Task>
8+
let mockApiConfiguration: ProviderSettings
9+
10+
beforeEach(() => {
11+
mockApiConfiguration = {
12+
apiProvider: "anthropic",
13+
apiModelId: "claude-3-5-sonnet-20241022",
14+
modelTemperature: 0.7,
15+
} as ProviderSettings
16+
17+
mockTask = {
18+
apiConfiguration: mockApiConfiguration,
19+
}
20+
})
21+
22+
describe("isTemperatureRelatedError", () => {
23+
it("should return false for non-temperature related errors", () => {
24+
const result = isTemperatureRelatedError("write_to_file", "Permission denied", mockTask as Task)
25+
expect(result).toBe(false)
26+
})
27+
28+
it("should return false when temperature is already low (0.2 or below)", () => {
29+
mockApiConfiguration.modelTemperature = 0.2
30+
const result = isTemperatureRelatedError(
31+
"write_to_file",
32+
"Error: content appears to be truncated",
33+
mockTask as Task,
34+
)
35+
expect(result).toBe(false)
36+
})
37+
38+
it("should return false when temperature is undefined (using default)", () => {
39+
mockApiConfiguration.modelTemperature = undefined
40+
const result = isTemperatureRelatedError(
41+
"write_to_file",
42+
"Error: content appears to be truncated",
43+
mockTask as Task,
44+
)
45+
expect(result).toBe(false)
46+
})
47+
48+
it('should return true for "content appears to be truncated" error with high temperature', () => {
49+
const result = isTemperatureRelatedError(
50+
"write_to_file",
51+
"Error: content appears to be truncated",
52+
mockTask as Task,
53+
)
54+
expect(result).toBe(true)
55+
})
56+
57+
it('should return true for "rest of code unchanged" error with high temperature', () => {
58+
const result = isTemperatureRelatedError(
59+
"apply_diff",
60+
'Error: Found "// rest of code unchanged" in the content',
61+
mockTask as Task,
62+
)
63+
expect(result).toBe(true)
64+
})
65+
66+
it('should return true for "previous code" error with high temperature', () => {
67+
const result = isTemperatureRelatedError(
68+
"write_to_file",
69+
'Error: Content contains "// ... previous code ..." placeholder',
70+
mockTask as Task,
71+
)
72+
expect(result).toBe(true)
73+
})
74+
75+
it('should return true for "existing code" error with high temperature', () => {
76+
const result = isTemperatureRelatedError(
77+
"apply_diff",
78+
'Error: Found "// ... existing code ..." in the diff',
79+
mockTask as Task,
80+
)
81+
expect(result).toBe(true)
82+
})
83+
84+
it('should return true for "keep the rest" error with high temperature', () => {
85+
const result = isTemperatureRelatedError(
86+
"write_to_file",
87+
'Error: Content includes "// keep the rest of the file" comment',
88+
mockTask as Task,
89+
)
90+
expect(result).toBe(true)
91+
})
92+
93+
it('should return true for "remaining code" error with high temperature', () => {
94+
const result = isTemperatureRelatedError(
95+
"apply_diff",
96+
'Error: Found "// ... remaining code ..." in the content',
97+
mockTask as Task,
98+
)
99+
expect(result).toBe(true)
100+
})
101+
102+
it("should handle Error objects as well as strings", () => {
103+
const error = new Error("content appears to be truncated")
104+
const result = isTemperatureRelatedError("write_to_file", error, mockTask as Task)
105+
expect(result).toBe(true)
106+
})
107+
108+
it("should be case insensitive when checking error patterns", () => {
109+
const result = isTemperatureRelatedError(
110+
"write_to_file",
111+
"ERROR: CONTENT APPEARS TO BE TRUNCATED",
112+
mockTask as Task,
113+
)
114+
expect(result).toBe(true)
115+
})
116+
117+
it("should return false when temperature is 0", () => {
118+
mockApiConfiguration.modelTemperature = 0
119+
const result = isTemperatureRelatedError(
120+
"write_to_file",
121+
"Error: content appears to be truncated",
122+
mockTask as Task,
123+
)
124+
expect(result).toBe(false)
125+
})
126+
127+
it("should return true when temperature is exactly 0.3", () => {
128+
mockApiConfiguration.modelTemperature = 0.3
129+
const result = isTemperatureRelatedError(
130+
"write_to_file",
131+
"Error: content appears to be truncated",
132+
mockTask as Task,
133+
)
134+
expect(result).toBe(true)
135+
})
136+
137+
it("should work with apply_diff tool", () => {
138+
const result = isTemperatureRelatedError(
139+
"apply_diff",
140+
"Error: content appears to be truncated",
141+
mockTask as Task,
142+
)
143+
expect(result).toBe(true)
144+
})
145+
146+
it("should return false for other tools even with temperature error patterns", () => {
147+
const result = isTemperatureRelatedError(
148+
"read_file",
149+
"Error: content appears to be truncated",
150+
mockTask as Task,
151+
)
152+
expect(result).toBe(false)
153+
})
154+
})
155+
})
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* Utility functions for detecting temperature-related tool errors
3+
*/
4+
5+
import { Task } from "../../task/Task"
6+
7+
/**
8+
* Error patterns that indicate temperature-related issues
9+
*/
10+
const TEMPERATURE_ERROR_PATTERNS = [
11+
// Direct truncation indicators
12+
/content appears to be truncated/i,
13+
/found comments indicating omitted code/i,
14+
/rest of code unchanged/i,
15+
/previous code/i,
16+
/code omitted/i,
17+
/truncated after \d+ lines/i,
18+
/keep the rest/i,
19+
20+
// Common AI placeholder patterns when temperature is high
21+
/\/\/\s*\.\.\./,
22+
/\/\*\s*\.\.\.\s*\*\//,
23+
/\[\s*\.\.\.\s*\]/,
24+
/\{\s*\.\.\.\s*\}/,
25+
26+
// Incomplete content indicators
27+
/incomplete file content/i,
28+
/partial content/i,
29+
/content was cut off/i,
30+
]
31+
32+
/**
33+
* Tool names that commonly experience temperature-related failures
34+
*/
35+
const TEMPERATURE_SENSITIVE_TOOLS = ["write_to_file", "apply_diff"]
36+
37+
/**
38+
* Checks if an error is likely caused by high temperature settings
39+
* @param toolName The name of the tool that failed
40+
* @param error The error message or Error object
41+
* @param task The current task instance to check temperature settings
42+
* @returns True if the error appears to be temperature-related
43+
*/
44+
export function isTemperatureRelatedError(toolName: string, error: string | Error, task: Task): boolean {
45+
// Only check for temperature errors on specific tools
46+
if (!TEMPERATURE_SENSITIVE_TOOLS.includes(toolName)) {
47+
return false
48+
}
49+
50+
// Get current temperature from API configuration
51+
const currentTemperature = task.apiConfiguration?.modelTemperature ?? 0.0
52+
53+
// Only consider it a temperature issue if temperature is above 0.2
54+
if (currentTemperature <= 0.2) {
55+
return false
56+
}
57+
58+
// Check if the user has customized the temperature (not using default)
59+
// Most providers default to 0.0 or 1.0, so anything else is likely custom
60+
const isCustomTemperature = currentTemperature !== 0.0 && currentTemperature !== 1.0
61+
62+
if (!isCustomTemperature) {
63+
return false
64+
}
65+
66+
// Convert error to string for pattern matching
67+
const errorMessage = typeof error === "string" ? error : error.message || ""
68+
69+
// Check if error matches any temperature-related patterns
70+
return TEMPERATURE_ERROR_PATTERNS.some((pattern) => pattern.test(errorMessage))
71+
}
72+
73+
/**
74+
* Gets a user-friendly message explaining the temperature issue
75+
* @param currentTemperature The current temperature setting
76+
* @returns A message explaining the issue
77+
*/
78+
export function getTemperatureErrorMessage(currentTemperature: number): string {
79+
return (
80+
`It looks like the tool failed due to your current temperature setting (${currentTemperature.toFixed(1)}). ` +
81+
`Higher temperature values can cause the AI to generate incomplete or malformed outputs. ` +
82+
`Reducing the temperature to 0.2 often resolves these issues.`
83+
)
84+
}
85+
86+
/**
87+
* Checks if the temperature can be reduced further
88+
* @param currentTemperature The current temperature setting
89+
* @returns True if temperature can be reduced to 0.2
90+
*/
91+
export function canReduceTemperature(currentTemperature: number): boolean {
92+
return currentTemperature > 0.2
93+
}

src/core/tools/writeToFileTool.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { isPathOutsideWorkspace } from "../../utils/pathUtils"
1414
import { detectCodeOmission } from "../../integrations/editor/detect-omission"
1515
import { unescapeHtmlEntities } from "../../utils/text-normalization"
1616
import { DEFAULT_WRITE_DELAY_MS } from "@roo-code/types"
17+
import { isTemperatureRelatedError, getTemperatureErrorMessage } from "./utils/temperatureErrorDetection"
1718

1819
export async function writeToFileTool(
1920
cline: Task,
@@ -172,13 +173,30 @@ export async function writeToFileTool(
172173
if (cline.diffStrategy) {
173174
await cline.diffViewProvider.revertChanges()
174175

175-
pushToolResult(
176-
formatResponse.toolError(
177-
`Content appears to be truncated (file has ${
178-
newContent.split("\n").length
179-
} 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.`,
180-
),
181-
)
176+
const errorMessage = `Content appears to be truncated (file has ${
177+
newContent.split("\n").length
178+
} 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.`
179+
180+
// Check if this is a temperature-related error
181+
if (isTemperatureRelatedError("write_to_file", errorMessage, cline)) {
182+
const currentTemperature = cline.apiConfiguration?.modelTemperature ?? 0.0
183+
const temperatureMessage = getTemperatureErrorMessage(currentTemperature)
184+
185+
// Ask user if they want to reduce temperature and retry
186+
const askMessage = JSON.stringify({
187+
tool: "write_to_file",
188+
path: getReadablePath(cline.cwd, relPath),
189+
error: errorMessage,
190+
temperatureMessage,
191+
currentTemperature,
192+
})
193+
194+
await cline.ask("temperature_tool_error", askMessage)
195+
cline.recordToolError("write_to_file", errorMessage)
196+
return
197+
}
198+
199+
pushToolResult(formatResponse.toolError(errorMessage))
182200
return
183201
} else {
184202
vscode.window

0 commit comments

Comments
 (0)