mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-11 22:51:26 +00:00
fix: handle context window exceeded errors during context condensing
When the selected condensing model has a smaller context window than the current conversation, the API throws an error that was previously unhandled. This caused the UI to hang with "Condensing Chat" forever. Changes: - Add try-catch around the streaming API call in summarizeConversation() - Detect context window exceeded errors and return user-friendly error message - Add new translation key for context window exceeded error - Add tests for error handling behavior Fixes #10781
This commit is contained in:
parent
9bf7173725
commit
3c3ef359bd
3 changed files with 152 additions and 9 deletions
|
|
@ -1484,6 +1484,122 @@ describe("summarizeConversation", () => {
|
|||
|
||||
expect(result.error).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should return context window exceeded error when API throws context length error", async () => {
|
||||
const messages: ApiMessage[] = [
|
||||
{ role: "user", content: "Hello", ts: 1 },
|
||||
{ role: "assistant", content: "Hi there", ts: 2 },
|
||||
{ role: "user", content: "How are you?", ts: 3 },
|
||||
{ role: "assistant", content: "I'm good", ts: 4 },
|
||||
{ role: "user", content: "What's new?", ts: 5 },
|
||||
{ role: "assistant", content: "Not much", ts: 6 },
|
||||
{ role: "user", content: "Tell me more", ts: 7 },
|
||||
]
|
||||
|
||||
// Create a stream that throws a context length exceeded error
|
||||
// eslint-disable-next-line require-yield
|
||||
const errorStream = (async function* () {
|
||||
throw new Error(
|
||||
"400 This endpoint's maximum context length is 131072 tokens. However, you requested about 145406 tokens.",
|
||||
)
|
||||
})()
|
||||
|
||||
mockApiHandler.createMessage = vi.fn().mockReturnValue(errorStream) as any
|
||||
|
||||
const result = await summarizeConversation(
|
||||
messages,
|
||||
mockApiHandler,
|
||||
defaultSystemPrompt,
|
||||
taskId,
|
||||
DEFAULT_PREV_CONTEXT_TOKENS,
|
||||
)
|
||||
|
||||
// Should return original messages with error
|
||||
expect(result.messages).toEqual(messages)
|
||||
expect(result.summary).toBe("")
|
||||
expect(result.error).toBeTruthy()
|
||||
// The error message comes from the translation key
|
||||
expect(result.error).toContain("condense_context_window_exceeded")
|
||||
expect(result.newContextTokens).toBeUndefined()
|
||||
})
|
||||
|
||||
it("should return context window exceeded error for various context length error messages", async () => {
|
||||
const messages: ApiMessage[] = [
|
||||
{ role: "user", content: "Hello", ts: 1 },
|
||||
{ role: "assistant", content: "Hi there", ts: 2 },
|
||||
{ role: "user", content: "How are you?", ts: 3 },
|
||||
{ role: "assistant", content: "I'm good", ts: 4 },
|
||||
{ role: "user", content: "What's new?", ts: 5 },
|
||||
{ role: "assistant", content: "Not much", ts: 6 },
|
||||
{ role: "user", content: "Tell me more", ts: 7 },
|
||||
]
|
||||
|
||||
// Test various error message patterns
|
||||
const errorPatterns = [
|
||||
"context_length_exceeded",
|
||||
"maximum context length exceeded",
|
||||
"prompt is too long",
|
||||
"too many tokens in your request",
|
||||
]
|
||||
|
||||
for (const errorPattern of errorPatterns) {
|
||||
vi.clearAllMocks()
|
||||
|
||||
// eslint-disable-next-line require-yield
|
||||
const errorStream = (async function* () {
|
||||
throw new Error(errorPattern)
|
||||
})()
|
||||
|
||||
mockApiHandler.createMessage = vi.fn().mockReturnValue(errorStream) as any
|
||||
|
||||
const result = await summarizeConversation(
|
||||
messages,
|
||||
mockApiHandler,
|
||||
defaultSystemPrompt,
|
||||
taskId,
|
||||
DEFAULT_PREV_CONTEXT_TOKENS,
|
||||
)
|
||||
|
||||
expect(result.error).toBeTruthy()
|
||||
// The error message comes from the translation key
|
||||
expect(result.error).toContain("condense_context_window_exceeded")
|
||||
}
|
||||
})
|
||||
|
||||
it("should return generic error for non-context-window API errors", async () => {
|
||||
const messages: ApiMessage[] = [
|
||||
{ role: "user", content: "Hello", ts: 1 },
|
||||
{ role: "assistant", content: "Hi there", ts: 2 },
|
||||
{ role: "user", content: "How are you?", ts: 3 },
|
||||
{ role: "assistant", content: "I'm good", ts: 4 },
|
||||
{ role: "user", content: "What's new?", ts: 5 },
|
||||
{ role: "assistant", content: "Not much", ts: 6 },
|
||||
{ role: "user", content: "Tell me more", ts: 7 },
|
||||
]
|
||||
|
||||
// Create a stream that throws a generic API error
|
||||
// eslint-disable-next-line require-yield
|
||||
const errorStream = (async function* () {
|
||||
throw new Error("Internal server error")
|
||||
})()
|
||||
|
||||
mockApiHandler.createMessage = vi.fn().mockReturnValue(errorStream) as any
|
||||
|
||||
const result = await summarizeConversation(
|
||||
messages,
|
||||
mockApiHandler,
|
||||
defaultSystemPrompt,
|
||||
taskId,
|
||||
DEFAULT_PREV_CONTEXT_TOKENS,
|
||||
)
|
||||
|
||||
// Should return original messages with error
|
||||
expect(result.messages).toEqual(messages)
|
||||
expect(result.summary).toBe("")
|
||||
expect(result.error).toBeTruthy()
|
||||
expect(result.error).toContain("Internal server error")
|
||||
expect(result.newContextTokens).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("summarizeConversation with custom settings", () => {
|
||||
|
|
|
|||
|
|
@ -319,19 +319,45 @@ export async function summarizeConversation(
|
|||
}
|
||||
}
|
||||
|
||||
const stream = handlerToUse.createMessage(promptToUse, requestMessages)
|
||||
|
||||
let summary = ""
|
||||
let cost = 0
|
||||
let outputTokens = 0
|
||||
|
||||
for await (const chunk of stream) {
|
||||
if (chunk.type === "text") {
|
||||
summary += chunk.text
|
||||
} else if (chunk.type === "usage") {
|
||||
// Record final usage chunk only
|
||||
cost = chunk.totalCost ?? 0
|
||||
outputTokens = chunk.outputTokens ?? 0
|
||||
try {
|
||||
const stream = handlerToUse.createMessage(promptToUse, requestMessages)
|
||||
|
||||
for await (const chunk of stream) {
|
||||
if (chunk.type === "text") {
|
||||
summary += chunk.text
|
||||
} else if (chunk.type === "usage") {
|
||||
// Record final usage chunk only
|
||||
cost = chunk.totalCost ?? 0
|
||||
outputTokens = chunk.outputTokens ?? 0
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
|
||||
// Check for context window exceeded error
|
||||
if (
|
||||
errorMessage.includes("context_length_exceeded") ||
|
||||
errorMessage.includes("context length") ||
|
||||
errorMessage.includes("maximum context") ||
|
||||
errorMessage.includes("too long") ||
|
||||
errorMessage.includes("too many tokens")
|
||||
) {
|
||||
return {
|
||||
...response,
|
||||
cost,
|
||||
error: t("common:errors.condense_context_window_exceeded"),
|
||||
}
|
||||
}
|
||||
|
||||
// Return generic error for other failures
|
||||
return {
|
||||
...response,
|
||||
cost,
|
||||
error: `${t("common:errors.condense_failed")}: ${errorMessage}`,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@
|
|||
"condensed_recently": "Context was condensed recently; skipping this attempt",
|
||||
"condense_handler_invalid": "API handler for condensing context is invalid",
|
||||
"condense_context_grew": "Context size increased during condensing; skipping this attempt",
|
||||
"condense_context_window_exceeded": "The selected condensing model's context window is too small for the current conversation. Please select a model with a larger context window or manually reduce the conversation size first.",
|
||||
"url_timeout": "The website took too long to load (timeout). This could be due to a slow connection, heavy website, or the site being temporarily unavailable. You can try again later or check if the URL is correct.",
|
||||
"url_not_found": "The website address could not be found. Please check if the URL is correct and try again.",
|
||||
"no_internet": "No internet connection. Please check your network connection and try again.",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue