Repository navigation
fix(litellm): fail fast on non-transient errors - #314
Merged
Merged
Conversation
Every exception other than a pooled 429 went through the same retry loop, so an invalid key, an unknown model or a malformed request was retried MAX_TRANSLATION_ATTEMPTS times with a 2s sleep each before giving up. The provider now returns None on the first non-transient LiteLLM error, matching the HTTP providers' handling of non-retryable 4xx: - AuthenticationError, PermissionDeniedError, NotFoundError, BadRequestError (and its subclasses), UnprocessableEntityError and BudgetExceededError are matched by qualname across the MRO, since litellm is a lazy import and its classes cannot be referenced at load. - Any other exception carrying a non-retryable `status_code` (e.g. the generic APIError with a 402) also fails fast via is_retryable_http_status(). - ContextWindowExceededError is always surfaced as ContextOverflowError, even when its message matches none of the overflow keywords, so the chunking layer can still shrink and retry. The KeyPool 429 rotation, the backoff on _TRANSIENT_EXCEPTIONS and the plain retry for unclassified errors are unchanged. Tests: the attempt-count assertions now read the constant bound in the provider module. Another test reloads src.config, which made test_rate_limit_without_pool_keeps_plain_backoff fail in a full run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
Author
|
Reviewed and merging. Checking the MRO by qualname is the right call while litellm stays a lazy import, and putting the transient classes first means a 429 or a timeout can never be mistaken for a client error. Raising ContextWindowExceededError as ContextOverflowError before the fail-fast check keeps the chunk-shrinking retry working, which was the one thing I wanted to be sure of. The LiteLLM tests pass locally (25 passed) and the Docker build is green. This will go out with the next release. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The LiteLLM provider retried every error that was not a pooled 429. An invalid key, an unknown model or a malformed request was retried
MAX_TRANSLATION_ATTEMPTStimes with a 2s sleep each before returningNone. The HTTP providers (OpenAI, Gemini, Mistral) already fail fast on non-retryable 4xx; this brings LiteLLM in line.AuthenticationError,PermissionDeniedError,NotFoundError,BadRequestErrorand its subclasses such asContentPolicyViolationError/UnsupportedParamsError,UnprocessableEntityError,BudgetExceededError) returnNoneon the first attempt. Classification is by qualname across the MRO becauselitellmis an optional lazy import.status_code(for example the genericAPIErrorwith a 402) also fails fast, reusingis_retryable_http_status().ContextWindowExceededErroris always raised asContextOverflowError, even when its message matches none of the overflow keywords, so the chunking layer can still shrink and retry. It is not swallowed by the fail-fast path even though it derives fromBadRequestError.handle_rate_limit(), exponential backoff for_TRANSIENT_EXCEPTIONS, and the plain 2s retry for unclassified exceptions.LiteLLM stays CLI-only; nothing in the web UI changes.
Backlog note: item 6 of 3.5 in
docs/BACKLOG.md(key poolpeek()withoutacquire()) was already fixed by #273, so it is not touched here.Tests
New cases in
tests/unit/test_litellm_provider.pyuse fakelitellm.exceptions.*classes that mirror the real hierarchy:APIErrorwith 402 fails fast, with 502 is retriedAPIConnectionErrorandTimeoutare still retried up to the limitContextWindowExceededErrorwith a keyword-free message raisesContextOverflowError8 of the new cases fail against the previous
litellm.py.The attempt-count assertions now read
MAX_TRANSLATION_ATTEMPTSfrom the provider module instead ofsrc.config. Another test reloadssrc.config, which made the existingtest_rate_limit_without_pool_keeps_plain_backofffail in a full-suite run (2 vs 3).Full suite: 2617 passed, 6 failed. All 6 failures are in
tests/characterization/test_progress_baseline.py(stale goldens, already failing onmain, unrelated to LiteLLM).🤖 Generated with Claude Code