mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
feat: improve context overflow error detection for third-party proxies
- Add checkIsLiteLLMContextWindowError() for LiteLLM proxy errors - Add checkIsGenericContextWindowError() as catch-all for various error formats - Add checkMessageForContextOverflow() helper for pattern matching - Support Chinese, Japanese, and Korean error messages - Handle string errors, nested error structures, and cause chains - Add comprehensive tests for new error detection functions Addresses issue #10246 where third-party API proxies return plain text error messages that were not being detected as context overflow errors.
This commit is contained in:
parent
1e71015e54
commit
e0d0d565de
2 changed files with 394 additions and 7 deletions
|
|
@ -106,13 +106,15 @@ describe("checkContextWindowExceededError", () => {
|
|||
expect(checkContextWindowExceededError(error)).toBe(false)
|
||||
})
|
||||
|
||||
it("should not detect errors with different status codes", () => {
|
||||
it("should detect errors with different status codes via generic check", () => {
|
||||
// Note: The generic check now catches context overflow messages regardless of status code
|
||||
// This is intentional to handle third-party proxies that may return different status codes
|
||||
const error = {
|
||||
status: 500,
|
||||
message: "context length exceeded",
|
||||
}
|
||||
|
||||
expect(checkContextWindowExceededError(error)).toBe(false)
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -179,7 +181,9 @@ describe("checkContextWindowExceededError", () => {
|
|||
expect(checkContextWindowExceededError(error)).toBe(false)
|
||||
})
|
||||
|
||||
it("should not detect errors with different error types", () => {
|
||||
it("should detect errors with different error types via generic check", () => {
|
||||
// Note: The generic check now catches context overflow messages regardless of error type
|
||||
// This is intentional to handle third-party proxies that may return different error structures
|
||||
const error = {
|
||||
error: {
|
||||
error: {
|
||||
|
|
@ -189,7 +193,7 @@ describe("checkContextWindowExceededError", () => {
|
|||
},
|
||||
}
|
||||
|
||||
expect(checkContextWindowExceededError(error)).toBe(false)
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -269,7 +273,9 @@ describe("checkContextWindowExceededError", () => {
|
|||
expect(checkContextWindowExceededError(error)).toBe(false)
|
||||
})
|
||||
|
||||
it("should handle errors that throw during property access", () => {
|
||||
it("should handle errors that throw during property access via generic check", () => {
|
||||
// Note: The generic check now catches context overflow messages even when some properties throw
|
||||
// This is intentional to handle edge cases where error objects have problematic getters
|
||||
const error = {
|
||||
get status() {
|
||||
throw new Error("Property access error")
|
||||
|
|
@ -277,7 +283,7 @@ describe("checkContextWindowExceededError", () => {
|
|||
message: "context length exceeded",
|
||||
}
|
||||
|
||||
expect(checkContextWindowExceededError(error)).toBe(false)
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it("should handle mixed provider error structures", () => {
|
||||
|
|
@ -326,4 +332,244 @@ describe("checkContextWindowExceededError", () => {
|
|||
expect(checkContextWindowExceededError(error3)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("LiteLLM errors", () => {
|
||||
it("should detect LiteLLM context window error with standard message", () => {
|
||||
const error = {
|
||||
message: "context length exceeded for this model",
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect LiteLLM error with nested error structure", () => {
|
||||
const error = {
|
||||
error: {
|
||||
message: "maximum context length is 4096 tokens",
|
||||
},
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect LiteLLM error with deeply nested structure", () => {
|
||||
const error = {
|
||||
error: {
|
||||
error: {
|
||||
message: "input is too long for this model",
|
||||
},
|
||||
},
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect LiteLLM error with detail field", () => {
|
||||
const error = {
|
||||
detail: "request is too large, please reduce input size",
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect LiteLLM error with context_length_exceeded type", () => {
|
||||
const error = {
|
||||
error: {
|
||||
type: "context_length_exceeded",
|
||||
message: "The request exceeds the maximum context",
|
||||
},
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect LiteLLM error with context_length_exceeded code", () => {
|
||||
const error = {
|
||||
error: {
|
||||
code: "context_length_exceeded",
|
||||
message: "Error processing request",
|
||||
},
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect various LiteLLM context error patterns", () => {
|
||||
const patterns = [
|
||||
"context length exceeded",
|
||||
"maximum token limit reached",
|
||||
"too many tokens in input",
|
||||
"input is too long",
|
||||
"exceeds max context size",
|
||||
"request is too large",
|
||||
"prompt is too long",
|
||||
]
|
||||
|
||||
patterns.forEach((pattern) => {
|
||||
const error = {
|
||||
message: pattern,
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it("should detect Chinese context error messages", () => {
|
||||
const patterns = ["输入超长了", "超出上下文限制", "请求太长了", "上下文超出限制"]
|
||||
|
||||
patterns.forEach((pattern) => {
|
||||
const error = {
|
||||
message: pattern,
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it("should not detect non-context LiteLLM errors", () => {
|
||||
const error = {
|
||||
message: "Invalid API key provided",
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Generic context window errors", () => {
|
||||
it("should detect string error with context overflow message", () => {
|
||||
const error = "context length exceeded for this request"
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect error with body field containing context message", () => {
|
||||
const error = {
|
||||
body: "The input exceeds the maximum token limit",
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect error with text field containing context message", () => {
|
||||
const error = {
|
||||
text: "Please reduce the length of your input",
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect error with data field containing context message", () => {
|
||||
const error = {
|
||||
data: "token count exceeded the maximum allowed",
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect error with stringified error field", () => {
|
||||
const error = {
|
||||
error: "context window overflow detected",
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect error with cause chain", () => {
|
||||
const error = {
|
||||
message: "Request failed",
|
||||
cause: {
|
||||
message: "context length exceeded",
|
||||
},
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect various generic context error patterns", () => {
|
||||
const patterns = [
|
||||
"context length exceeded",
|
||||
"context window overflow",
|
||||
"maximum token count reached",
|
||||
"too many tokens",
|
||||
"input is too long",
|
||||
"exceeds the max length",
|
||||
"request is too large",
|
||||
"prompt is too long",
|
||||
"token limit exceeded",
|
||||
"reduce the length of your input",
|
||||
]
|
||||
|
||||
patterns.forEach((pattern) => {
|
||||
const error = {
|
||||
message: pattern,
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it("should detect Chinese context error messages in generic check", () => {
|
||||
const patterns = ["输入超长", "超出长度限制", "上下文太长", "令牌超出限制"]
|
||||
|
||||
patterns.forEach((pattern) => {
|
||||
const error = {
|
||||
message: pattern,
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it("should detect Japanese context error messages", () => {
|
||||
const patterns = ["コンテキストが長すぎます", "入力が超過しました", "リクエストが制限を超えました"]
|
||||
|
||||
patterns.forEach((pattern) => {
|
||||
const error = {
|
||||
message: pattern,
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it("should detect Korean context error messages", () => {
|
||||
// These patterns match the regex: /(?:컨텍스트|입력|요청).*(?:너무\s*길|초과|제한)/
|
||||
const patterns = ["컨텍스트 너무 길다", "입력 초과", "요청 제한"]
|
||||
|
||||
patterns.forEach((pattern) => {
|
||||
const error = {
|
||||
message: pattern,
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it("should not detect non-context generic errors", () => {
|
||||
const error = {
|
||||
message: "Network connection failed",
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(false)
|
||||
})
|
||||
|
||||
it("should not detect string errors without context keywords", () => {
|
||||
const error = "Invalid authentication credentials"
|
||||
expect(checkContextWindowExceededError(error)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Edge cases for new functions", () => {
|
||||
it("should handle string input with context overflow message", () => {
|
||||
expect(checkContextWindowExceededError("context length exceeded")).toBe(true)
|
||||
})
|
||||
|
||||
it("should handle string input without context overflow message", () => {
|
||||
expect(checkContextWindowExceededError("some other error")).toBe(false)
|
||||
})
|
||||
|
||||
it("should handle deeply nested cause chain", () => {
|
||||
const error = {
|
||||
message: "Outer error",
|
||||
cause: {
|
||||
message: "Middle error",
|
||||
cause: {
|
||||
message: "context length exceeded",
|
||||
},
|
||||
},
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
|
||||
it("should handle error with multiple message sources", () => {
|
||||
const error = {
|
||||
message: "Generic error",
|
||||
error: {
|
||||
message: "context window exceeded",
|
||||
},
|
||||
detail: "Some detail",
|
||||
}
|
||||
expect(checkContextWindowExceededError(error)).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ export function checkContextWindowExceededError(error: unknown): boolean {
|
|||
checkIsOpenAIContextWindowError(error) ||
|
||||
checkIsOpenRouterContextWindowError(error) ||
|
||||
checkIsAnthropicContextWindowError(error) ||
|
||||
checkIsCerebrasContextWindowError(error)
|
||||
checkIsCerebrasContextWindowError(error) ||
|
||||
checkIsLiteLLMContextWindowError(error) ||
|
||||
checkIsGenericContextWindowError(error)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -112,3 +114,142 @@ function checkIsCerebrasContextWindowError(response: unknown): boolean {
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for LiteLLM and third-party proxy context window errors.
|
||||
* LiteLLM is commonly used as a proxy for various LLM providers and may return
|
||||
* different error formats than the original providers.
|
||||
*/
|
||||
function checkIsLiteLLMContextWindowError(error: unknown): boolean {
|
||||
try {
|
||||
if (!error || typeof error !== "object") {
|
||||
return false
|
||||
}
|
||||
|
||||
const err = error as Record<string, any>
|
||||
|
||||
// LiteLLM may wrap errors in different structures
|
||||
const message: string = String(
|
||||
err.message || err.error?.message || err.error?.error?.message || err.detail || "",
|
||||
)
|
||||
const status = err.status ?? err.code ?? err.error?.status ?? err.response?.status ?? err.statusCode
|
||||
|
||||
// LiteLLM-specific error patterns for context window exceeded
|
||||
const LITELLM_CONTEXT_PATTERNS = [
|
||||
/\bcontext\s*(?:length|window)\s*(?:exceeded|too\s*long|limit)/i,
|
||||
/\bmax(?:imum)?\s*(?:context|token)\s*(?:length|limit|size)/i,
|
||||
/\btoo\s*(?:many|long)\s*(?:tokens?|input)/i,
|
||||
/\binput\s*(?:is\s*)?too\s*long/i,
|
||||
/\bexceeds?\s*(?:the\s*)?(?:max(?:imum)?|context)\s*(?:token|length|limit)/i,
|
||||
/\brequest\s*(?:is\s*)?too\s*large/i,
|
||||
/\bprompt\s*(?:is\s*)?too\s*long/i,
|
||||
// Chinese error messages (common in Chinese LLM proxies)
|
||||
/超长|超出.*(?:长度|限制|上下文)/,
|
||||
/(?:上下文|输入|请求).*(?:太长|过长|超出)/,
|
||||
] as const
|
||||
|
||||
// Check if message matches any context window pattern
|
||||
if (LITELLM_CONTEXT_PATTERNS.some((pattern) => pattern.test(message))) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Also check for status 400 with context-related keywords
|
||||
if (String(status) === "400" && LITELLM_CONTEXT_PATTERNS.some((pattern) => pattern.test(message))) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for LiteLLM-specific error structure
|
||||
if (err.error?.type === "context_length_exceeded" || err.error?.code === "context_length_exceeded") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for generic context window errors that may come from various sources.
|
||||
* This is a catch-all for error messages that indicate context overflow but
|
||||
* don't match provider-specific patterns.
|
||||
*
|
||||
* This handles cases where:
|
||||
* - Third-party proxies return plain text errors
|
||||
* - Error messages are wrapped in unexpected structures
|
||||
* - JSON parsing fails and the raw error text contains context overflow info
|
||||
*/
|
||||
function checkIsGenericContextWindowError(error: unknown): boolean {
|
||||
try {
|
||||
// Handle string errors (e.g., from JSON parse failures)
|
||||
if (typeof error === "string") {
|
||||
return checkMessageForContextOverflow(error)
|
||||
}
|
||||
|
||||
if (!error || typeof error !== "object") {
|
||||
return false
|
||||
}
|
||||
|
||||
const err = error as Record<string, any>
|
||||
|
||||
// Collect all possible message sources
|
||||
const messageSources = [
|
||||
err.message,
|
||||
err.error?.message,
|
||||
err.error?.error?.message,
|
||||
err.detail,
|
||||
err.body,
|
||||
err.text,
|
||||
err.data,
|
||||
// Handle cases where the error might be a stringified JSON
|
||||
typeof err.error === "string" ? err.error : null,
|
||||
]
|
||||
|
||||
// Check each message source for context overflow patterns
|
||||
for (const source of messageSources) {
|
||||
if (source && typeof source === "string" && checkMessageForContextOverflow(source)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check for cause chain (some errors wrap the original error)
|
||||
if (err.cause && checkIsGenericContextWindowError(err.cause)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to check if a message string indicates context overflow.
|
||||
* Used by multiple error checking functions.
|
||||
*/
|
||||
function checkMessageForContextOverflow(message: string): boolean {
|
||||
// Comprehensive patterns for context window/length exceeded errors
|
||||
const GENERIC_CONTEXT_PATTERNS = [
|
||||
// English patterns
|
||||
/\bcontext\s*(?:length|window|size)\s*(?:exceeded|too\s*long|limit|overflow)/i,
|
||||
/\bmax(?:imum)?\s*(?:context|token|input)\s*(?:length|limit|size|count)/i,
|
||||
/\btoo\s*(?:many|long)\s*(?:tokens?|input|characters?)/i,
|
||||
/\binput\s*(?:is\s*)?too\s*(?:long|large)/i,
|
||||
/\bexceeds?\s*(?:the\s*)?(?:max(?:imum)?|context|token)\s*(?:length|limit|size)/i,
|
||||
/\brequest\s*(?:is\s*)?too\s*(?:large|long)/i,
|
||||
/\bprompt\s*(?:is\s*)?too\s*(?:long|large)/i,
|
||||
/\btoken\s*(?:count|limit)\s*exceeded/i,
|
||||
/\b(?:input|prompt|request)\s*(?:length|size)\s*(?:exceeded|too\s*(?:long|large))/i,
|
||||
/\breduce\s*(?:the\s*)?(?:length|size|tokens?)/i,
|
||||
// Chinese patterns (common in Chinese LLM services)
|
||||
/超长|超出.*(?:长度|限制|上下文|令牌)/,
|
||||
/(?:上下文|输入|请求|提示).*(?:太长|过长|超出|超过)/,
|
||||
/(?:长度|大小|令牌).*(?:超出|超过|限制)/,
|
||||
// Japanese patterns
|
||||
/(?:コンテキスト|入力|リクエスト).*(?:長すぎ|超過|制限)/,
|
||||
// Korean patterns
|
||||
/(?:컨텍스트|입력|요청).*(?:너무\s*길|초과|제한)/,
|
||||
] as const
|
||||
|
||||
return GENERIC_CONTEXT_PATTERNS.some((pattern) => pattern.test(message))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue