From 32c15b5478691adbdf3075dd88ae5ea55e8ca918 Mon Sep 17 00:00:00 2001 From: Denel John Date: Fri, 16 Oct 2026 16:22:24 -0400 Subject: [PATCH] fix(prometheus): bound non-string model values on failed requests A numeric, list or dict model with no deployment behind the call skipped the bounding and became its own label value. Send every such value through the same helper so they collapse into the shared other series. Signed-off-by: Denel John --- litellm/integrations/prometheus.py | 6 +++--- .../test_prometheus_requested_model_cardinality.py | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/litellm/integrations/prometheus.py b/litellm/integrations/prometheus.py index 2af5faf0757..e513d2c655e 100644 --- a/litellm/integrations/prometheus.py +++ b/litellm/integrations/prometheus.py @@ -2617,9 +2617,9 @@ class PrometheusLogger(CustomLogger): # With no deployment behind the call, the name is whatever the caller sent (for example a model the # key may not use). Bound it like ``requested_model`` so unknown names share one series. model: Final = ( - _bounded_requested_model_label(requested_name, router_originated=True) - if not deployment_model_id and isinstance(requested_name, str) - else requested_name + requested_name + if deployment_model_id + else _bounded_requested_model_label(requested_name, router_originated=True) # pyright: ignore[reportUnknownArgumentType] # caller-supplied value, the helper type-checks it ) litellm_params: Final = kwargs.get("litellm_params", {}) or {} diff --git a/tests/unit/integrations/test_prometheus_requested_model_cardinality.py b/tests/unit/integrations/test_prometheus_requested_model_cardinality.py index de08ff556ec..f4f0d3607e4 100644 --- a/tests/unit/integrations/test_prometheus_requested_model_cardinality.py +++ b/tests/unit/integrations/test_prometheus_requested_model_cardinality.py @@ -360,3 +360,14 @@ async def test_sdk_failures_without_proxy_router_keep_their_model_label_on_llm_a await _fire_llm_api_failure(logger, "gpt-4o-mini") assert _model_label_values(logger.litellm_llm_api_failed_requests_metric) == {"gpt-4o-mini"} + + +@pytest.mark.asyncio +@pytest.mark.parametrize("model", [123, 4.5, ["gpt-4o-mini"], {"name": "gpt-4o-mini"}]) +async def test_non_string_denied_models_collapse_to_other_on_llm_api_failed_requests_metric(router, model: object): + logger = PrometheusLogger() + + with patch("litellm.proxy.proxy_server.llm_router", router, create=True): # test-quality-ok: production reads proxy_server.llm_router lazily, no injection seam + await _fire_llm_api_failure(logger, model) # type: ignore[arg-type] + + assert _model_label_values(logger.litellm_llm_api_failed_requests_metric) == {UNRECOGNIZED_REQUESTED_MODEL_LABEL}