From 3bd18ef77312a69629bda244b85e45896013a75b Mon Sep 17 00:00:00 2001 From: Bungic <71993947+Bungic@users.noreply.github.com> Date: Thu, 28 May 2026 18:40:11 +0300 Subject: [PATCH 1/2] fix(prometheus): rate-limit gauges drop the emit when value is 0 The remaining_requests and remaining_tokens gauges are guarded by a truthy check: if remaining_requests: ...emit metric When the value is 0 (the upstream has actually exhausted the user's quota), Python evaluates 0 as falsy and the metric is never emitted. This is exactly the moment the metric is most useful for alerting. The rest of this file already uses the correct `is not None` pattern for similar nullable values (lines 1688, 1710, 1856, 1868, 1901, etc.), and even for these exact variables in the *non*-init path at lines 2419 and 2431. The two checks at 2519 and 2537 are the holdouts. Changed truthy to `is not None`. No behavior change when the upstream header is genuinely absent (still skipped); only difference is that 0 now flows through to the gauge. --- litellm/integrations/prometheus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/integrations/prometheus.py b/litellm/integrations/prometheus.py index 64d4dd578b2..ad1f52c2259 100644 --- a/litellm/integrations/prometheus.py +++ b/litellm/integrations/prometheus.py @@ -2691,7 +2691,7 @@ class PrometheusLogger(CustomLogger): label_context=label_context, ) - if remaining_requests: + if remaining_requests is not None: """ "model_group", "api_provider", @@ -2705,7 +2705,7 @@ class PrometheusLogger(CustomLogger): ) self.litellm_remaining_requests_metric.labels(**_labels).set(remaining_requests) - if remaining_tokens: + if remaining_tokens is not None: _labels = prometheus_label_factory( supported_enum_labels=self.get_labels_for_metric(metric_name="litellm_remaining_tokens_metric"), enum_values=enum_values, From 3418ebb1d2fb8b197a541c84a078a11334101295 Mon Sep 17 00:00:00 2001 From: Bungic <71993947+Bungic@users.noreply.github.com> Date: Thu, 28 May 2026 19:11:02 +0300 Subject: [PATCH 2/2] test(prometheus): regression test for zero-value rate-limit gauges Asserts that set_llm_deployment_success_metrics still emits the remaining_requests / remaining_tokens gauges with value 0 when the upstream provider reports zero remaining quota. Pre-fix, the truthy guard dropped the emit; this test fails on that revision and passes on the fix. --- ...etheus_remaining_tokens_router_fallback.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/test_litellm/integrations/test_prometheus_remaining_tokens_router_fallback.py b/tests/test_litellm/integrations/test_prometheus_remaining_tokens_router_fallback.py index d754de86569..10eaf8155e0 100644 --- a/tests/test_litellm/integrations/test_prometheus_remaining_tokens_router_fallback.py +++ b/tests/test_litellm/integrations/test_prometheus_remaining_tokens_router_fallback.py @@ -296,3 +296,46 @@ class TestRouterFallbackDefensivePaths: ) prometheus_logger.litellm_remaining_tokens_metric.labels.assert_not_called() + + +class TestProviderHeaderPathEmitsZero: + """The sibling path to the router fallback above. + + When a provider does return `x-ratelimit-remaining-*`, the fallback + short-circuits and `set_llm_deployment_success_metrics` owns the emit. That + branch used to be guarded by `if remaining_requests:`, so a reported `0` + was dropped and the gauge kept serving its previous value: a dashboard + reading headroom while the quota was gone. + """ + + def test_should_emit_both_gauges_when_provider_reports_zero(self, prometheus_logger): + payload = _build_payload( + custom_llm_provider="openai", + additional_headers={ + "x_ratelimit_remaining_requests": 0, + "x_ratelimit_remaining_tokens": 0, + }, + ) + + prometheus_logger.litellm_remaining_requests_metric = MagicMock() + prometheus_logger.litellm_remaining_tokens_metric = MagicMock() + prometheus_logger.litellm_deployment_success_responses = MagicMock() + prometheus_logger.litellm_deployment_total_requests = MagicMock() + prometheus_logger.litellm_deployment_latency_per_output_token = MagicMock() + prometheus_logger.litellm_overhead_latency_metric = MagicMock() + prometheus_logger.set_deployment_healthy = MagicMock() + + prometheus_logger.set_llm_deployment_success_metrics( + request_kwargs={ + "model": "gpt-5-nano", + "litellm_params": {"custom_llm_provider": "openai"}, + "standard_logging_object": payload, + }, + start_time=None, + end_time=None, + enum_values=_enum_values(), + output_tokens=10, + ) + + prometheus_logger.litellm_remaining_requests_metric.labels().set.assert_called_once_with(0) + prometheus_logger.litellm_remaining_tokens_metric.labels().set.assert_called_once_with(0)