fix(core): catch bare openai.APIError in handle_llm_error (#9468)

* fix(core): catch bare openai.APIError in handle_llm_error fallthrough

openai.APIError raised during streaming (e.g. OpenRouter credit
exhaustion) is not an APIStatusError, so it skipped the catch-all
at the end and fell through to LLMError("Unhandled"). Now bare
APIErrors that aren't context window overflows are mapped to
LLMBadRequestError.

Datadog: https://us5.datadoghq.com/error-tracking/issue/7a2c356c-0849-11f1-be66-da7ad0900000

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* feat(core): add LLMInsufficientCreditsError for BYOK credit exhaustion

Adds dedicated error type for insufficient credits/quota across all
providers (OpenAI, Anthropic, Google). Returns HTTP 402 with
BYOK-aware messaging instead of generic 400.

- New LLMInsufficientCreditsError class and PAYMENT_REQUIRED ErrorCode
- is_insufficient_credits_message() helper detecting credit/quota strings
- All 3 provider clients detect 402 status + credit keywords
- FastAPI handler returns 402 with "your API key" vs generic messaging
- 5 new parametrized tests covering OpenRouter, OpenAI, and negative case

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

---------

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Kian Jones
2026-02-12 15:49:21 -08:00
committed by Caren Thomas
parent cfd2ca3102
commit 80f34f134d
7 changed files with 144 additions and 3 deletions

View File

@@ -54,6 +54,7 @@ from letta.errors import (
LLMAuthenticationError,
LLMBadRequestError,
LLMError,
LLMInsufficientCreditsError,
LLMProviderOverloaded,
LLMRateLimitError,
LLMTimeoutError,
@@ -705,6 +706,24 @@ def create_application() -> "FastAPI":
},
)
@app.exception_handler(LLMInsufficientCreditsError)
async def llm_insufficient_credits_handler(request: Request, exc: LLMInsufficientCreditsError):
is_byok = exc.details.get("is_byok") if isinstance(exc.details, dict) else None
if is_byok:
message = "Insufficient credits on your API key. Please add credits with your LLM provider."
else:
message = "Insufficient credits for LLM request. Please check your account."
return JSONResponse(
status_code=402,
content={
"error": {
"type": "llm_insufficient_credits",
"message": message,
"detail": str(exc),
}
},
)
@app.exception_handler(LLMAuthenticationError)
async def llm_auth_error_handler(request: Request, exc: LLMAuthenticationError):
return JSONResponse(