diff --git a/litellm/exceptions.py b/litellm/exceptions.py index 51810c5643f..c1bd70cfe25 100644 --- a/litellm/exceptions.py +++ b/litellm/exceptions.py @@ -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)}" diff --git a/litellm/router.py b/litellm/router.py index b275c264ebc..27d3e2152aa 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -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( diff --git a/litellm/utils.py b/litellm/utils.py index e1ad1db63ef..f62d5ef030b 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -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)