From 52a8895be5a5aa4846a509d7cc3904209c344dd9 Mon Sep 17 00:00:00 2001 From: Johnx-w <231812476+Johnx-w@users.noreply.github.com> Date: Thu, 5 Nov 2026 13:32:53 +0800 Subject: [PATCH] fix(prometheus): label proxy failures by requested service tier --- litellm/integrations/prometheus.py | 5 +++ litellm/types/integrations/prometheus.py | 1 + .../test_prometheus_service_tier_label.py | 34 ++++++++++++++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/litellm/integrations/prometheus.py b/litellm/integrations/prometheus.py index fc1a601f9da..f1e50c7e832 100644 --- a/litellm/integrations/prometheus.py +++ b/litellm/integrations/prometheus.py @@ -52,6 +52,7 @@ from litellm.litellm_core_utils.core_helpers import ( ) from litellm.litellm_core_utils.get_llm_provider_logic import is_registered_custom_provider from litellm.litellm_core_utils.service_tier_utils import ( + KNOWN_REQUEST_SERVICE_TIERS, get_service_tier_from_standard_logging_payload, ) from litellm.llms.azure.common_utils import caused_by_entra_rejection @@ -3072,6 +3073,7 @@ class PrometheusLogger(CustomLogger): api_provider: Final = self._extract_api_provider_from_request_data( request_data ) or self._extract_api_provider_from_exception(original_exception) + requested_tier: Final = request_data.get("service_tier") enum_values: Final = UserAPIKeyLabelValues( end_user=user_api_key_dict.end_user_id, user=user_api_key_dict.user_id, @@ -3097,6 +3099,9 @@ class PrometheusLogger(CustomLogger): model_id=model_id, api_provider=api_provider, stream=(str(request_data.get("stream")) if litellm.prometheus_emit_stream_label else None), + service_tier=requested_tier + if isinstance(requested_tier, str) and requested_tier in KNOWN_REQUEST_SERVICE_TIERS + else None, ) _label_ctx: Final = PrometheusLabelFactoryContext(enum_values) PrometheusLogger._inc_labeled_counter( diff --git a/litellm/types/integrations/prometheus.py b/litellm/types/integrations/prometheus.py index ca084bb358c..8fb660ac4d1 100644 --- a/litellm/types/integrations/prometheus.py +++ b/litellm/types/integrations/prometheus.py @@ -515,6 +515,7 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.USER_EMAIL.value, UserAPIKeyLabelNames.EXCEPTION_STATUS.value, UserAPIKeyLabelNames.EXCEPTION_CLASS.value, + UserAPIKeyLabelNames.SERVICE_TIER.value, # ``rate_limit_category`` / ``rate_limit_type`` are appended in # ``get_labels()`` when ``litellm.prometheus_emit_rate_limit_labels`` # is True. Kept opt-in so existing dashboards keyed on this metric's diff --git a/tests/unit/integrations/test_prometheus_service_tier_label.py b/tests/unit/integrations/test_prometheus_service_tier_label.py index 8b8131b5af2..7f860a0efe3 100644 --- a/tests/unit/integrations/test_prometheus_service_tier_label.py +++ b/tests/unit/integrations/test_prometheus_service_tier_label.py @@ -1,5 +1,5 @@ """ -Unit tests for the service_tier Prometheus label on latency and spend metrics. +Unit tests for the service_tier Prometheus label on latency, spend, and failure metrics. Covers the label being declared on the metrics that carry it, the precedence between the tier a provider served and the tier a caller requested, and the @@ -10,9 +10,12 @@ Run with: """ import datetime +from typing import Final import pytest +from prometheus_client import generate_latest +from litellm.exceptions import RateLimitError from litellm.integrations.prometheus import PrometheusLogger from litellm.litellm_core_utils.service_tier_utils import ( KNOWN_REQUEST_SERVICE_TIERS, @@ -23,6 +26,7 @@ from litellm.types.integrations.prometheus import ( UserAPIKeyLabelNames, UserAPIKeyLabelValues, ) +from litellm.types.proxy.auth.user_api_key_auth import UserAPIKeyAuth SERVICE_TIER_METRICS = [ "litellm_llm_api_latency_metric", @@ -239,3 +243,31 @@ def test_allowlist_covers_every_modeled_service_tier(): missing = {tier.value for tier in ServiceTier} - KNOWN_REQUEST_SERVICE_TIERS assert not missing, f"ServiceTier values missing from the allowlist: {sorted(missing)}" + + +@pytest.mark.parametrize("tier", (*sorted(KNOWN_REQUEST_SERVICE_TIERS), None, "", "unknown-tier", 42, [])) +@pytest.mark.asyncio +async def test_failed_request_emits_bounded_service_tier(tier: object) -> None: + _clear_prometheus_registry() + try: + logger: Final = PrometheusLogger() + await logger.async_post_call_failure_hook( + request_data={"model": "test-model", "service_tier": tier}, + original_exception=RateLimitError( + message="capacity unavailable", llm_provider="openai", model="test-model" + ), + user_api_key_dict=UserAPIKeyAuth(), + ) + series: Final = tuple( + line + for line in generate_latest().decode().splitlines() + if line.startswith("litellm_proxy_failed_requests_metric_total{") + ) + assert len(series) == 1 + expected_tier: Final = tier if isinstance(tier, str) and tier in KNOWN_REQUEST_SERVICE_TIERS else "None" + assert f'service_tier="{expected_tier}"' in series[0] + assert 'exception_status="429"' in series[0] + assert 'exception_class="Openai.RateLimitError"' in series[0] + assert series[0].endswith(" 1.0") + finally: + _clear_prometheus_registry()