mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
feat(exceptions): add RateLimitErrorCategory + headers/detail fields on RateLimitError
LiteLLM previously surfaced rate-limit conditions through several unrelated
error classes (RateLimitError, FastAPI HTTPException(429), BaseLLMException).
This commit adds the data model needed to consolidate them under a single
class:
* RateLimitErrorCategory enum exposing four categorical values
(vendor_rate_limit, vendor_batch_rate_limit, litellm_rate_limit,
litellm_batch_rate_limit) so callers can switch on the rate-limit source.
* New optional fields on RateLimitError:
- category (defaults to vendor_rate_limit, preserving today's behavior for
every existing call site in exception_mapping_utils);
- headers (preserves retry-after / rate_limit_type / reset_at across the
proxy boundary instead of dropping them on the floor);
- detail (mirrors FastAPI HTTPException.detail so the same instance can be
serialized through both paths).
litellm.RateLimitErrorCategory is re-exported at the package root to match
the existing exception-export pattern.
LIT-2968
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
This commit is contained in:
parent
6de00e24b4
commit
150616c1a1
2 changed files with 63 additions and 1 deletions
|
|
@ -1255,6 +1255,7 @@ from .exceptions import (
|
|||
NotFoundError,
|
||||
PermissionDeniedError,
|
||||
RateLimitError,
|
||||
RateLimitErrorCategory,
|
||||
ServiceUnavailableError,
|
||||
BadGatewayError,
|
||||
OpenAIError,
|
||||
|
|
|
|||
|
|
@ -9,13 +9,45 @@
|
|||
|
||||
## LiteLLM versions of the OpenAI Exception Types
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
import enum
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
import httpx
|
||||
import openai
|
||||
|
||||
from litellm.types.utils import LiteLLMCommonStrings
|
||||
|
||||
|
||||
class RateLimitErrorCategory(str, enum.Enum):
|
||||
"""
|
||||
Category of a rate limit error, allowing callers to distinguish where the rate
|
||||
limit originated. Exposed on every :class:`RateLimitError` instance via the
|
||||
``category`` attribute.
|
||||
|
||||
Use these values to switch on the rate limit source, e.g.::
|
||||
|
||||
try:
|
||||
...
|
||||
except litellm.RateLimitError as e:
|
||||
if e.category == RateLimitErrorCategory.LITELLM_RATE_LIMIT:
|
||||
... # litellm's own limiter (key/team/user/model RPM/TPM/budget)
|
||||
elif e.category == RateLimitErrorCategory.VENDOR_RATE_LIMIT:
|
||||
... # the upstream LLM provider returned 429
|
||||
"""
|
||||
|
||||
VENDOR_RATE_LIMIT = "vendor_rate_limit"
|
||||
"""The upstream LLM provider returned a rate-limit response (e.g. OpenAI 429)."""
|
||||
|
||||
VENDOR_BATCH_RATE_LIMIT = "vendor_batch_rate_limit"
|
||||
"""The upstream LLM provider returned a rate-limit response on a batch endpoint."""
|
||||
|
||||
LITELLM_RATE_LIMIT = "litellm_rate_limit"
|
||||
"""LiteLLM's own rate limiter (key/team/user/model RPM/TPM, budget, parallel-requests, etc.) blocked the request."""
|
||||
|
||||
LITELLM_BATCH_RATE_LIMIT = "litellm_batch_rate_limit"
|
||||
"""LiteLLM's own batch rate limiter (token/request budget across a batch input file) blocked the request."""
|
||||
|
||||
|
||||
_MINIMAL_ERROR_RESPONSE: Optional[httpx.Response] = None
|
||||
|
||||
|
||||
|
|
@ -321,6 +353,18 @@ class PermissionDeniedError(openai.PermissionDeniedError): # type: ignore
|
|||
|
||||
|
||||
class RateLimitError(openai.RateLimitError): # type: ignore
|
||||
"""
|
||||
Unified rate-limit error.
|
||||
|
||||
Every rate-limit condition surfaced by litellm — whether it originated from
|
||||
an upstream LLM provider, a vendor batch endpoint, or one of litellm's own
|
||||
proxy-side limiters (parallel-requests, dynamic-rate, batch-rate, budget,
|
||||
max-iterations, etc.) — is raised as an instance of this class.
|
||||
|
||||
The :attr:`category` attribute lets callers distinguish the source. See
|
||||
:class:`RateLimitErrorCategory` for the available values.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message,
|
||||
|
|
@ -330,6 +374,11 @@ class RateLimitError(openai.RateLimitError): # type: ignore
|
|||
litellm_debug_info: Optional[str] = None,
|
||||
max_retries: Optional[int] = None,
|
||||
num_retries: Optional[int] = None,
|
||||
category: Union[str, RateLimitErrorCategory] = (
|
||||
RateLimitErrorCategory.VENDOR_RATE_LIMIT
|
||||
),
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
detail: Any = None,
|
||||
):
|
||||
self.status_code = 429
|
||||
self.message = "litellm.RateLimitError: {}".format(message)
|
||||
|
|
@ -338,9 +387,21 @@ class RateLimitError(openai.RateLimitError): # type: ignore
|
|||
self.litellm_debug_info = litellm_debug_info
|
||||
self.max_retries = max_retries
|
||||
self.num_retries = num_retries
|
||||
self.category = (
|
||||
category.value if isinstance(category, RateLimitErrorCategory) else category
|
||||
)
|
||||
# Headers carried with the error (e.g. retry-after, rate_limit_type,
|
||||
# reset_at). Preserved across the proxy boundary so clients can react
|
||||
# appropriately.
|
||||
_response_headers = (
|
||||
getattr(response, "headers", None) if response is not None else None
|
||||
)
|
||||
self.headers: Optional[Dict[str, str]] = (
|
||||
{k: str(v) for k, v in headers.items()} if headers else None
|
||||
) or (dict(_response_headers) if _response_headers else None)
|
||||
# Mirrors FastAPI HTTPException.detail so the same instance can be
|
||||
# serialized through both the ProxyException and HTTPException paths.
|
||||
self.detail = detail if detail is not None else self.message
|
||||
self.response = httpx.Response(
|
||||
status_code=429,
|
||||
headers=_response_headers,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue