fix(logging): keep proxy-side rate limits as expected client errors

ProxyRateLimitError derives from HTTPException but carries an llm_provider,
so it read as provider-originated and regained its traceback. Any
HTTPException is a proxy rejection regardless of llm_provider.
This commit is contained in:
mateo-berri 2026-08-25 20:30:44 -07:00
parent ac6deec529
commit a6e1708e6c
2 changed files with 19 additions and 0 deletions

View file

@ -58,7 +58,17 @@ def safe_divide(
return numerator / denominator
def _is_proxy_rejection(exception: BaseException) -> bool:
try:
from starlette.exceptions import HTTPException
except ImportError:
return False
return isinstance(exception, HTTPException)
def _is_provider_originated(exception: BaseException) -> bool:
if _is_proxy_rejection(exception):
return False
if getattr(exception, "llm_provider", None):
return True
from litellm.llms.base_llm.chat.transformation import BaseLLMException
@ -74,6 +84,8 @@ def is_expected_client_error(exception: BaseException | None) -> bool:
client error and keeps its traceback: a mapped litellm exception carries
``llm_provider``, and the raw ``BaseLLMException`` that provider handlers
raise before mapping (the /v1/messages route surfaces it as-is) is one too.
The proxy's own limiters raise ``HTTPException`` subclasses that also carry
an ``llm_provider``, so any ``HTTPException`` stays a proxy rejection.
ProxyException stores the status on .code (as a str), HTTPException and
litellm exceptions on .status_code.

View file

@ -286,6 +286,7 @@ class TestIsExpectedClientError:
from litellm.exceptions import AuthenticationError, RateLimitError
from litellm.litellm_core_utils.core_helpers import is_expected_client_error
from litellm.llms.anthropic.common_utils import AnthropicError
from litellm.proxy.common_utils.proxy_rate_limit_error import ProxyRateLimitError
provider_auth_failure = AuthenticationError(
message="AnthropicException - API key is invalid.", llm_provider="anthropic", model="claude-haiku-4-5"
@ -298,6 +299,12 @@ class TestIsExpectedClientError:
unmapped_provider_failure = AnthropicError(status_code=401, message='{"type":"authentication_error"}')
assert is_expected_client_error(unmapped_provider_failure) is False
proxy_rate_limit = ProxyRateLimitError(
detail={"error": "Max parallel requests reached"}, model="claude-haiku-4-5", llm_provider="anthropic"
)
assert proxy_rate_limit.llm_provider == "anthropic"
assert is_expected_client_error(proxy_rate_limit) is True
class RouterRejection(Exception):
def __init__(self):
self.status_code = 429