From 5f9ab5995753330955e137d6e65755d9ace2c927 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 11 May 2026 22:48:36 +0000 Subject: [PATCH] feat(logging): expose rate-limit category via StandardLoggingPayload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an optional 'error_rate_limit_category' field to StandardLoggingPayloadErrorInformation, populated from the unified RateLimitError.category attribute (introduced in the previous commits on this branch). Why: the .category attribute is reachable off the raw exception today via getattr(e, 'category', None), but the structured contract that downstream custom callbacks / loggers / spend log writers consume is the StandardLoggingPayload. Without this field, a user building custom rate-limit metrics on top of callback data has to special-case the raw exception object — which defeats the purpose of the StandardLoggingPayload abstraction. The field is None for non-rate-limit exceptions (so consumers can read it unconditionally without isinstance checks) and is one of the RateLimitErrorCategory string values otherwise. LIT-2968 Co-authored-by: Mateo Wang --- litellm/litellm_core_utils/litellm_logging.py | 11 +++++++++++ litellm/types/utils.py | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index c73d914e6cc..7c1542a204b 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -5159,12 +5159,23 @@ class StandardLoggingPayloadSetup: # Get additional error details error_message = str(original_exception) + # For rate-limit errors (litellm.RateLimitError + the proxy-side + # ProxyRateLimitError subclass), surface the unified `category` field + # so callbacks can distinguish vendor vs. litellm rate limits without + # reaching for the raw exception object. + rate_limit_category: Optional[str] = ( + getattr(original_exception, "category", None) + if original_exception is not None + else None + ) + return StandardLoggingPayloadErrorInformation( error_code=error_status, error_class=error_class, llm_provider=_llm_provider_in_exception, traceback=traceback_info, error_message=error_message if original_exception else "", + error_rate_limit_category=rate_limit_category, ) @staticmethod diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 400edcac889..832ed12236c 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -2697,6 +2697,16 @@ class StandardLoggingPayloadErrorInformation(TypedDict, total=False): llm_provider: Optional[str] traceback: Optional[str] error_message: Optional[str] + error_rate_limit_category: Optional[str] + """ + For 429 / rate-limit errors, the source of the rate limit. One of the + string values defined by :class:`litellm.exceptions.RateLimitErrorCategory` + (``vendor_rate_limit``, ``vendor_batch_rate_limit``, ``litellm_rate_limit``, + ``litellm_batch_rate_limit``). ``None`` for non-rate-limit exceptions. + + Surfaced here so custom callbacks / metrics consumers can switch on the + rate-limit source without reaching for the raw exception. + """ class GuardrailMode(TypedDict, total=False):