Skip to content

Commit 73449bf

Browse files
committed
fix: handle local LLM crashes in Orchestrator mode
- Add retry mechanism for empty model responses (max 3 retries) - Detect and handle connection errors common with local LLMs (Jan.ai, LM Studio) - Provide orchestrator-specific guidance for complex prompts - Add simplification hints after first retry in orchestrator mode - Improve error messages with actionable troubleshooting steps - Add comprehensive test coverage for error scenarios Fixes #8948
1 parent 4a096e1 commit 73449bf

File tree

2 files changed

+862
-13
lines changed

2 files changed

+862
-13
lines changed

src/core/task/Task.ts

Lines changed: 123 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2232,14 +2232,32 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
22322232
`[Task#${this.taskId}.${this.instanceId}] Stream failed, will retry: ${streamingFailedMessage}`,
22332233
)
22342234

2235+
// Check if this is a local LLM connection error (common with Jan.ai, LM Studio, etc.)
2236+
const isConnectionError =
2237+
streamingFailedMessage?.toLowerCase().includes("connection") ||
2238+
streamingFailedMessage?.toLowerCase().includes("tcp") ||
2239+
streamingFailedMessage?.toLowerCase().includes("proxy") ||
2240+
streamingFailedMessage?.toLowerCase().includes("502")
2241+
2242+
// Get current mode for context
2243+
const currentMode = await this.getTaskMode()
2244+
const isOrchestratorMode = currentMode === "orchestrator"
2245+
22352246
// Apply exponential backoff similar to first-chunk errors when auto-resubmit is enabled
22362247
const stateForBackoff = await this.providerRef.deref()?.getState()
22372248
if (stateForBackoff?.autoApprovalEnabled && stateForBackoff?.alwaysApproveResubmit) {
2238-
await this.backoffAndAnnounce(
2239-
currentItem.retryAttempt ?? 0,
2240-
error,
2241-
streamingFailedMessage,
2242-
)
2249+
// Provide more helpful message for local LLM errors
2250+
let enhancedMessage = streamingFailedMessage
2251+
if (isConnectionError) {
2252+
enhancedMessage =
2253+
`Connection to local LLM failed${isOrchestratorMode ? " (Orchestrator mode may exceed model capacity)" : ""}. Please ensure:\n` +
2254+
`1. Your local LLM server (Jan.ai/LM Studio) is running\n` +
2255+
`2. The model is loaded and ready\n` +
2256+
`3. The API endpoint is correctly configured\n\n` +
2257+
`Original error: ${streamingFailedMessage}`
2258+
}
2259+
2260+
await this.backoffAndAnnounce(currentItem.retryAttempt ?? 0, error, enhancedMessage)
22432261

22442262
// Check if task was aborted during the backoff
22452263
if (this.abort) {
@@ -2253,6 +2271,20 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
22532271
}
22542272
}
22552273

2274+
// For connection errors after multiple retries, provide guidance
2275+
const maxConnectionRetries = 2
2276+
if (isConnectionError && (currentItem.retryAttempt ?? 0) >= maxConnectionRetries) {
2277+
await this.say(
2278+
"error",
2279+
`Persistent connection issues with local LLM detected. ${isOrchestratorMode ? "Orchestrator mode requires substantial model capacity. " : ""}Please:\n` +
2280+
`1. Restart your local LLM server\n` +
2281+
`2. ${isOrchestratorMode ? "Consider switching to a simpler mode or using a cloud-based model" : "Check your model is properly loaded"}\n` +
2282+
`3. Verify firewall/antivirus isn't blocking connections`,
2283+
)
2284+
// Don't retry further for persistent connection issues
2285+
break
2286+
}
2287+
22562288
// Push the same content back onto the stack to retry, incrementing the retry attempt counter
22572289
stack.push({
22582290
userContent: currentUserContent,
@@ -2395,15 +2427,93 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
23952427
// If there's no assistant_responses, that means we got no text
23962428
// or tool_use content blocks from API which we should assume is
23972429
// an error.
2398-
await this.say(
2399-
"error",
2400-
"Unexpected API Response: The language model did not provide any assistant messages. This may indicate an issue with the API or the model's output.",
2401-
)
24022430

2403-
await this.addToApiConversationHistory({
2404-
role: "assistant",
2405-
content: [{ type: "text", text: "Failure: I did not provide a response." }],
2406-
})
2431+
// Track empty response retries
2432+
const emptyResponseRetryKey = "emptyResponseRetries"
2433+
const maxEmptyResponseRetries = 3
2434+
const currentRetries = (currentItem as any)[emptyResponseRetryKey] || 0
2435+
2436+
// Get current mode to provide better error context
2437+
const currentMode = await this.getTaskMode()
2438+
const isOrchestratorMode = currentMode === "orchestrator"
2439+
2440+
if (currentRetries < maxEmptyResponseRetries) {
2441+
// Log the retry attempt
2442+
console.warn(
2443+
`[Task#${this.taskId}] Empty response from model (attempt ${currentRetries + 1}/${maxEmptyResponseRetries})` +
2444+
(isOrchestratorMode
2445+
? " in Orchestrator mode - may be due to model limitations with complex prompts"
2446+
: ""),
2447+
)
2448+
2449+
// Provide user feedback about the retry
2450+
const retryMessage = isOrchestratorMode
2451+
? `The model returned an empty response. This can happen with local models in Orchestrator mode due to complex prompts. Retrying with simplified approach (attempt ${currentRetries + 1}/${maxEmptyResponseRetries})...`
2452+
: `The model returned an empty response. Retrying (attempt ${currentRetries + 1}/${maxEmptyResponseRetries})...`
2453+
2454+
await this.say("api_req_retry_delayed", retryMessage, undefined, false)
2455+
2456+
// For Orchestrator mode on retry, suggest switching to a simpler mode
2457+
let modifiedUserContent = [...currentUserContent]
2458+
if (isOrchestratorMode && currentRetries >= 1) {
2459+
// Add a hint to simplify the response
2460+
modifiedUserContent.push({
2461+
type: "text",
2462+
text: "\n\n[System: The model is having difficulty with complex orchestration. Please provide a simpler, more direct response focusing on the immediate task.]",
2463+
})
2464+
}
2465+
2466+
// Add a small delay before retry to avoid rapid-fire requests
2467+
await delay(2000)
2468+
2469+
// Push retry onto stack with incremented counter
2470+
stack.push({
2471+
userContent: modifiedUserContent,
2472+
includeFileDetails: false,
2473+
retryAttempt: currentItem.retryAttempt,
2474+
[emptyResponseRetryKey]: currentRetries + 1,
2475+
} as any)
2476+
2477+
continue
2478+
} else {
2479+
// After max retries, provide helpful error message
2480+
const errorMessage = isOrchestratorMode
2481+
? "The model repeatedly failed to provide a response. This often happens with local LLM models in Orchestrator mode due to the complexity of multi-step planning. Consider:\n" +
2482+
"1. Switching to a simpler mode like 'code' or 'architect'\n" +
2483+
"2. Using a more capable model (GPT-4, Claude, etc.)\n" +
2484+
"3. Ensuring your local model has sufficient context window (40k+ tokens recommended for Orchestrator mode)"
2485+
: "The model repeatedly failed to provide a response. This may indicate:\n" +
2486+
"1. Model compatibility issues with the current prompt format\n" +
2487+
"2. Insufficient model context window for the conversation\n" +
2488+
"3. Network or API connectivity problems\n" +
2489+
"Please try a different model or simplify your request."
2490+
2491+
await this.say("error", errorMessage)
2492+
2493+
// Add to conversation history to maintain state
2494+
await this.addToApiConversationHistory({
2495+
role: "assistant",
2496+
content: [
2497+
{
2498+
type: "text",
2499+
text: "I was unable to generate a response after multiple attempts. Please try a different approach or model.",
2500+
},
2501+
],
2502+
})
2503+
2504+
// For Orchestrator mode, suggest mode switch
2505+
if (isOrchestratorMode) {
2506+
const { response } = await this.ask(
2507+
"mistake_limit_reached",
2508+
"The Orchestrator mode is having difficulty with this model. Would you like to switch to a simpler mode like 'code' or continue with a different approach?",
2509+
)
2510+
2511+
if (response === "messageResponse") {
2512+
// User provided guidance, continue
2513+
continue
2514+
}
2515+
}
2516+
}
24072517
}
24082518

24092519
// If we reach here without continuing, return false (will always be false for now)

0 commit comments

Comments
 (0)