diff --git a/litellm/types/integrations/prometheus.py b/litellm/types/integrations/prometheus.py index f9e50ff0f4..6fe859370c 100644 --- a/litellm/types/integrations/prometheus.py +++ b/litellm/types/integrations/prometheus.py @@ -1,5 +1,5 @@ import re -from dataclasses import dataclass, field, fields +from dataclasses import MISSING, dataclass, field, fields from enum import Enum from types import MappingProxyType from typing import Any, ClassVar, Dict, List, Literal, Mapping, Optional, Tuple, Union @@ -743,7 +743,13 @@ class PrometheusMetricLabels: return default_labels + custom_labels -@dataclass(frozen=True) +_USER_API_KEY_LABEL_VALUE_INIT_ALIASES: Dict[str, str] = { + # Some tests / call sites use ``api_key_hash``; Prometheus field is ``hashed_api_key``. + "api_key_hash": "hashed_api_key", +} + + +@dataclass(frozen=True, init=False) class UserAPIKeyLabelValues: """ Prometheus metric label inputs (Python field names match historical Pydantic ``model_dump`` keys). @@ -780,6 +786,31 @@ class UserAPIKeyLabelValues: org_id: Optional[str] = None org_alias: Optional[str] = None + def __init__(self, **kwargs: Any) -> None: + """ + Match former Pydantic behavior: unknown keys are ignored; ``api_key_hash`` maps to + ``hashed_api_key``. This supports ``**standard_logging_payload`` in tests. + """ + field_names = {f.name for f in fields(self)} + merged: Dict[str, Any] = {} + for f in fields(self): + if f.default_factory is not MISSING: + merged[f.name] = f.default_factory() + else: + merged[f.name] = f.default + + for k, v in kwargs.items(): + if k in field_names: + merged[k] = v + continue + canon = _USER_API_KEY_LABEL_VALUE_INIT_ALIASES.get(k) + if canon is not None and canon in field_names: + merged[canon] = v + + for f in fields(self): + object.__setattr__(self, f.name, merged[f.name]) + self.__post_init__() + def __post_init__(self) -> None: object.__setattr__(self, "tags", tuple(self.tags)) if self.stream is not None: diff --git a/tests/enterprise/litellm_enterprise/enterprise_callbacks/test_prometheus_logging_callbacks.py b/tests/enterprise/litellm_enterprise/enterprise_callbacks/test_prometheus_logging_callbacks.py index 8fa56029f4..d3f7d882da 100644 --- a/tests/enterprise/litellm_enterprise/enterprise_callbacks/test_prometheus_logging_callbacks.py +++ b/tests/enterprise/litellm_enterprise/enterprise_callbacks/test_prometheus_logging_callbacks.py @@ -660,7 +660,7 @@ async def test_async_log_failure_event(prometheus_logger): ) # litellm_llm_api_failed_requests_metric incremented - # Labels: end_user, api_key_hash, api_key_alias, model, team, team_alias, user, model_id + # Labels: end_user, hashed_api_key, api_key_alias, model, team, team_alias, user, model_id prometheus_logger.litellm_llm_api_failed_requests_metric.labels.assert_called_once_with( None, # end_user_id "test_hash", @@ -1150,10 +1150,10 @@ def test_prometheus_factory(monkeypatch, enable_end_user_cost_tracking_prometheu enum_values = UserAPIKeyLabelValues( end_user="test_end_user", - api_key_hash="test_hash", + hashed_api_key="test_hash", api_key_alias="test_alias", ) - supported_labels = ["end_user", "api_key_hash", "api_key_alias"] + supported_labels = ["end_user", "hashed_api_key", "api_key_alias"] returned_dict = prometheus_label_factory( supported_enum_labels=supported_labels, enum_values=enum_values ) @@ -1162,6 +1162,8 @@ def test_prometheus_factory(monkeypatch, enable_end_user_cost_tracking_prometheu assert returned_dict["end_user"] == "test_end_user" else: assert returned_dict["end_user"] == None + assert returned_dict["hashed_api_key"] == "test_hash" + assert returned_dict["api_key_alias"] == "test_alias" def test_get_custom_labels_from_metadata(monkeypatch):