fix: stabilize prometheus label sets and drop redundant model normalization

- Cache PrometheusLogger.get_labels_for_metric per metric_name so that
  the label set used to construct counters at __init__ time stays in
  sync with the label set used at increment time, even if module-level
  toggles like prometheus_emit_rate_limit_labels or
  prometheus_emit_stream_label are flipped at runtime. Without this,
  toggling these flags after the logger was created would cause
  ValueError from prometheus_client because the runtime labels would
  not match the counter's declared labelnames.
- Drop redundant 'model or ""' guard in ProxyRateLimitError.__init__
  where model is already normalized one step earlier.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
This commit is contained in:
Cursor Agent 2026-05-14 22:25:57 +00:00
parent 57682ee792
commit 984f75de63
No known key found for this signature in database
2 changed files with 31 additions and 2 deletions

View file

@ -82,6 +82,20 @@ class PrometheusLogger(CustomLogger):
# Always initialize label_filters, even for non-premium users
self.label_filters = self._parse_prometheus_config()
# Cache resolved label sets per metric. Several entries in
# ``PrometheusMetricLabels.get_labels`` read module-level toggles
# (e.g. ``litellm.prometheus_emit_stream_label``,
# ``litellm.prometheus_emit_rate_limit_labels``) that can be
# changed at runtime. Prometheus counters/gauges/histograms are
# created with a *fixed* ``labelnames`` set; if a runtime call
# to ``get_labels_for_metric`` returned a different set, the
# subsequent ``counter.labels(**_labels)`` would raise a
# ``ValueError`` from the prometheus client. Snapshotting at
# logger init time pins the label set for the lifetime of the
# logger so toggling these flags only takes effect after a
# restart, keeping init-time and runtime label sets in sync.
self._cached_metric_labels: Dict[str, List[str]] = {}
_custom_buckets = litellm.prometheus_latency_buckets
self.latency_buckets = (
tuple(_custom_buckets)
@ -973,13 +987,27 @@ class PrometheusLogger(CustomLogger):
self, metric_name: DEFINED_PROMETHEUS_METRICS
) -> List[str]:
"""
Get the labels for a metric, filtered if configured
Get the labels for a metric, filtered if configured.
The result is cached on the instance so the label set used to
construct each Prometheus metric at ``__init__`` time stays in lock
step with the label set passed to ``counter.labels(...)`` at
runtime, even if the underlying module-level toggles consulted by
:meth:`PrometheusMetricLabels.get_labels` (e.g.
``litellm.prometheus_emit_rate_limit_labels``,
``litellm.prometheus_emit_stream_label``) are flipped after the
logger has been created.
"""
cached = self._cached_metric_labels.get(metric_name)
if cached is not None:
return cached
# Get default labels for this metric from PrometheusMetricLabels
default_labels = PrometheusMetricLabels.get_labels(metric_name)
# If no label filtering is configured for this metric, use default labels
if metric_name not in self.label_filters:
self._cached_metric_labels[metric_name] = default_labels
return default_labels
# Get configured labels for this metric
@ -990,6 +1018,7 @@ class PrometheusLogger(CustomLogger):
label for label in default_labels if label in configured_labels
]
self._cached_metric_labels[metric_name] = filtered_labels
return filtered_labels
def _track_end_user_metric_series(

View file

@ -174,7 +174,7 @@ class ProxyRateLimitError(HTTPException, RateLimitError): # type: ignore[misc]
self,
message=message,
llm_provider=llm_provider,
model=model or "",
model=model,
category=category,
rate_limit_type=rate_limit_type,
headers=stringified_headers,