mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
fix(router): stamp actual retry count on exception when retry_policy sets retries to 0
When retry_policy (e.g. TimeoutErrorRetries=0) prevents retries, the router raises immediately without entering the retry loop, so the post-loop setattr that records actual retry counts was never reached. Meanwhile the @client decorator in utils.py unconditionally injected litellm.num_retries (e.g. 3) onto the exception, causing misleading "LiteLLM Retried: 3 times" in error messages even though zero retries occurred. Changes: - router.py: stamp max_retries=0 / num_retries=0 on the exception in the else-raise path so error messages always reflect the real retry count - utils.py: skip the num_retries injection for router/proxy calls; the router already manages retry counts itself via the post-loop setattr - exceptions.py: change `if self.num_retries` / `if self.max_retries` to `is not None` checks so a count of 0 is displayed rather than suppressed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3d2b8fed32
commit
672698c5eb
3 changed files with 83 additions and 67 deletions
|
|
@ -60,17 +60,17 @@ class AuthenticationError(openai.AuthenticationError): # type: ignore
|
|||
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
def __repr__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -106,17 +106,17 @@ class NotFoundError(openai.NotFoundError): # type: ignore
|
|||
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
def __repr__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -157,17 +157,17 @@ class BadRequestError(openai.BadRequestError): # type: ignore
|
|||
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
def __repr__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -220,17 +220,17 @@ class UnprocessableEntityError(openai.UnprocessableEntityError): # type: ignore
|
|||
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
def __repr__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -266,17 +266,17 @@ class Timeout(openai.APITimeoutError): # type: ignore
|
|||
# custom function to convert to str
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
def __repr__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -305,17 +305,17 @@ class PermissionDeniedError(openai.PermissionDeniedError): # type: ignore
|
|||
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
def __repr__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -357,17 +357,17 @@ class RateLimitError(openai.RateLimitError): # type: ignore
|
|||
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
def __repr__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -399,17 +399,17 @@ class ContextWindowExceededError(BadRequestError): # type: ignore
|
|||
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
def __repr__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -442,17 +442,17 @@ class RejectedRequestError(BadRequestError): # type: ignore
|
|||
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
def __repr__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -495,9 +495,9 @@ class ContentPolicyViolationError(BadRequestError): # type: ignore
|
|||
Transform the error to a string
|
||||
"""
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -537,17 +537,17 @@ class ServiceUnavailableError(openai.APIStatusError): # type: ignore
|
|||
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
def __repr__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -587,17 +587,17 @@ class BadGatewayError(openai.APIStatusError): # type: ignore
|
|||
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
def __repr__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -637,17 +637,17 @@ class InternalServerError(openai.InternalServerError): # type: ignore
|
|||
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
def __repr__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -678,17 +678,17 @@ class APIError(openai.APIError): # type: ignore
|
|||
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
def __repr__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -717,17 +717,17 @@ class APIConnectionError(openai.APIConnectionError): # type: ignore
|
|||
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
def __repr__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -755,17 +755,17 @@ class APIResponseValidationError(openai.APIResponseValidationError): # type: ig
|
|||
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
def __repr__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
return _message
|
||||
|
||||
|
|
@ -1005,9 +1005,9 @@ class MidStreamFallbackError(ServiceUnavailableError): # type: ignore
|
|||
|
||||
def __str__(self):
|
||||
_message = self.message
|
||||
if self.num_retries:
|
||||
if self.num_retries is not None:
|
||||
_message += f" LiteLLM Retried: {self.num_retries} times"
|
||||
if self.max_retries:
|
||||
if self.max_retries is not None:
|
||||
_message += f", LiteLLM Max Retries: {self.max_retries}"
|
||||
if self.original_exception:
|
||||
_message += f" Original exception: {type(self.original_exception).__name__}: {str(self.original_exception)}"
|
||||
|
|
|
|||
|
|
@ -5792,6 +5792,12 @@ class Router:
|
|||
if num_retries > 0:
|
||||
kwargs = self.log_retry(kwargs=kwargs, e=original_exception)
|
||||
else:
|
||||
# No retries will occur; stamp the exception with the actual counts
|
||||
# (0) before raising so error messages reflect reality instead of
|
||||
# carrying a stale value injected by the @client decorator.
|
||||
if type(original_exception) in litellm.LITELLM_EXCEPTION_TYPES:
|
||||
setattr(original_exception, "max_retries", 0)
|
||||
setattr(original_exception, "num_retries", 0)
|
||||
raise
|
||||
|
||||
verbose_router_logger.debug(
|
||||
|
|
|
|||
|
|
@ -2088,9 +2088,19 @@ def client(original_function): # noqa: PLR0915
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
setattr(
|
||||
e, "num_retries", num_retries
|
||||
) ## IMPORTANT: returns the deployment's num_retries to the router
|
||||
# Only set num_retries on the exception for non-router calls.
|
||||
# When the router is in use, it manages retry counts itself and stamps
|
||||
# the actual attempted count via setattr after the retry loop completes.
|
||||
# Injecting litellm.num_retries here for router calls causes misleading
|
||||
# "LiteLLM Retried: N times" in error messages even when zero retries
|
||||
# occurred (e.g. TimeoutErrorRetries=0 in retry_policy).
|
||||
_is_router_or_proxy_call = "model_group" in (
|
||||
kwargs.get("metadata") or {}
|
||||
)
|
||||
if not _is_router_or_proxy_call:
|
||||
setattr(
|
||||
e, "num_retries", num_retries
|
||||
) ## returns the deployment's num_retries for non-router retry logic
|
||||
|
||||
timeout = _get_wrapper_timeout(kwargs=kwargs, exception=e)
|
||||
setattr(e, "timeout", timeout)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue