Skip to content

fix(proxy/hooks): populate llm_provider on internal rate-limit errors - #27707

Merged
mateo-berri merged 12 commits into
litellm_internal_stagingfrom
litellm_provider-on-proxy-rate-limit-398f
Jun 5, 2026
Merged

fix(proxy/hooks): populate llm_provider on internal rate-limit errors#27707
mateo-berri merged 12 commits into
litellm_internal_stagingfrom
litellm_provider-on-proxy-rate-limit-398f

Conversation

@mateo-berri

Copy link
Copy Markdown
Contributor

Problem

From the Slack thread that motivated #27687:

The provider field is missing when a request fails due to a LiteLLM rate limit error. It looks like the exception is thrown in pre_call_hook before litellm.get_llm_provider is invoked. Is this by design, or a bug?

It's a bug. Every internal rate-limit hook (parallel_request_limiter, dynamic_rate_limiter, batch_rate_limiter, max_budget_limiter, max_iterations_limiter, max_budget_per_session_limiter, plus the v3 variants) raises its 429 from inside async_pre_call_hook before litellm.get_llm_provider runs anywhere in the request lifecycle. The Prometheus failure metric, the StandardLoggingPayload, and the raised exception itself all carry no provider info — making it impossible to slice litellm-side rate-limit metrics by provider.

This is a follow-up to #27687, which standardized the rate-limit error class but kept llm_provider hardcoded to "litellm_proxy" on the proxy-side raise sites.

Fix

Resolve the LLM provider from the request's model (data["model"]) at every limiter raise site via litellm.get_llm_provider(...). If the model is unknown / unparseable, fall back to "litellm_proxy" so a malformed model value never breaks the request path with a new exception masquerading as the original 429.

The resolved provider attaches to the raised exception's .llm_provider attribute. The Prometheus failure metric reads provider via _get_exception_class_name(exc)exception.llm_provider, so the label flips from the legacy "HTTPException" (or "Litellm_proxy.ProxyHTTPRateLimitError") to "Openai.ProxyHTTPRateLimitError" / "Bedrock.ProxyHTTPRateLimitError" etc. automatically — no Prometheus-side plumbing needed.

Limiters updated

File Raise sites
parallel_request_limiter.py 3 (RPM / TPM / max_parallel)
parallel_request_limiter_v3.py 2 (pre-check + TPM atomic-reservation)
dynamic_rate_limiter.py 2
dynamic_rate_limiter_v3.py 3 (saturation, priority, fail-closed)
batch_rate_limiter.py 1
max_budget_limiter.py 1
max_iterations_limiter.py 1
max_budget_per_session_limiter.py 1

A new litellm/proxy/hooks/rate_limiter_utils.py module hosts:

  • resolve_llm_provider_for_rate_limit(model) — defensive wrapper around get_llm_provider. Returns "litellm_proxy" on any failure (including missing/empty model).
  • ProxyHTTPRateLimitError — local 429 helper that carries model + llm_provider.

Naming note (deliberate)

The new class is ProxyHTTPRateLimitError, not ProxyRateLimitError, to avoid colliding with the same-named class introduced in #27687. The two PRs are kept independently mergeable. When both land, the natural follow-up is to consolidate them into a single class — the tests pin endswith("ProxyHTTPRateLimitError") rather than the full string so the rename is a low-friction follow-up.

Before / after

# Before (label snippet from trho's Prometheus output, edited)
litellm_proxy_failed_requests_metric_total{
  exception_class="HTTPException",
  exception_status="429",
  requested_model="gpt-4o-mini",
  ...
} 1.0

# After
litellm_proxy_failed_requests_metric_total{
  exception_class="Openai.ProxyHTTPRateLimitError",
  exception_status="429",
  requested_model="gpt-4o-mini",
  ...
} 1.0

Tests

tests/test_litellm/proxy/hooks/test_proxy_rate_limit_provider_field.py — 35 tests covering:

  • Each limiter, when tripped, raises a RateLimitError whose model and llm_provider match what litellm.get_llm_provider returns for the requested model.
  • Fallback path: when data["model"] is missing, None, or unparseable, llm_provider == "litellm_proxy" and no second exception leaks.
  • Resolver helper unit tests across openai / anthropic / bedrock / vertex / unknown / empty / malformed inputs.
$ uv run pytest tests/test_litellm/proxy/hooks/test_proxy_rate_limit_provider_field.py -x -vv
... 35 passed in 2.64s

$ uv run pytest tests/test_litellm/proxy/hooks/ -x -q
... 219 passed, 1 skipped in 7.18s

$ uv run pytest tests/test_litellm/proxy/ -k 'rate_limit or budget' -x -q
... 373 passed, 1 skipped in 17.35s

uv run black / uv run ruff check clean on touched files.

Out of scope

Slack Thread

Open in Web Open in Cursor 

cursoragent and others added 6 commits May 12, 2026 02:58
Introduces a small helper layer used by every proxy-side rate-limit
hook so that the 429 they raise carries a populated llm_provider /
model — instead of an empty exception.llm_provider that downstream
loggers (Prometheus failure metric, observability callbacks) read as
'no provider attribution'.

ProxyHTTPRateLimitError inherits from both fastapi.HTTPException
(so the proxy server still renders it as a 429) and
litellm.exceptions.RateLimitError (so isinstance checks and
PrometheusLogger._get_exception_class_name pick up llm_provider).
We deliberately don't call RateLimitError.__init__ — it constructs
an httpx.Response we don't need and would just add failure surface;
attribute parity is what downstream consumers care about.

resolve_llm_provider_for_rate_limit() wraps litellm.get_llm_provider
defensively. Internal limiter hooks fire from async_pre_call_hook —
well before get_llm_provider runs anywhere else in the request
lifecycle — so we have to call it ourselves at raise time. If the
model is missing or unparseable (alias, router-only model) we fall
back to llm_provider='litellm_proxy' rather than letting a second
exception leak out and break the request path.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
Both v1 and v3 parallel-request limiters fired bare HTTPException(429)
from inside async_pre_call_hook. The downstream Prometheus failure
metric reads exception.llm_provider via _get_exception_class_name —
the empty value showed up as exception_class='HTTPException' and
left model_id='None' on the time series.

Threads requested_model through every raise site in:

* parallel_request_limiter.py:
  - check_key_in_limits (the per-key/per-model/per-user/per-team/
    per-customer over-limit path)
  - raise_rate_limit_error (zero-limit + global_max_parallel_requests
    paths) — now takes an optional requested_model kwarg
* parallel_request_limiter_v3.py:
  - _handle_rate_limit_error (the OVER_LIMIT translator), called
    from both the should_rate_limit pre-check and the TPM
    reservation path

Resolved via resolve_llm_provider_for_rate_limit so unknown / missing
models silently fall back to llm_provider='litellm_proxy' instead of
breaking the request path with a second exception.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
Same plumbing change as the parallel limiters, applied to both
dynamic_rate_limiter (v1) and dynamic_rate_limiter_v3:

* v1: TPM-zero and RPM-zero paths in async_pre_call_hook now resolve
  data['model'] -> (model, llm_provider) once and pass it into both
  raises.
* v3: All three raise sites in _check_rate_limits — the
  model_saturation_check enforced raise, the priority_model
  enforced raise, and the fail-closed unknown-descriptor branch —
  now attribute the 429 to the actual provider.

Falls back to llm_provider='litellm_proxy' when the model can't be
resolved.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
batch_rate_limiter._raise_rate_limit_error now takes a
requested_model kwarg threaded from data['model'] in
_check_and_increment_batch_counters. The batch-creation 429 is what
gets raised when the input file's tokens/requests count would push
the per-key TPM/RPM window over its limit.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
Final batch of internal raise sites — the user/session-budget and
max-iterations hooks. Same pattern: resolve data['model'] once at
raise time, attach to ProxyHTTPRateLimitError so Prometheus and
observability callbacks can attribute the 429.

Hooks updated:
* max_budget_limiter (per-user max_budget exceeded)
* max_iterations_limiter (per-session agent iteration cap)
* max_budget_per_session_limiter (per-session dollar cap)

All three fall back to llm_provider='litellm_proxy' when data['model']
is missing or unparseable. Drops the now-unused HTTPException import
from each module.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
Regression coverage for the 'provider field missing' bug across every
proxy-side rate-limit hook + the helper layer:

* ProxyHTTPRateLimitError class shape (HTTPException + RateLimitError,
  dict-detail stringification, None-provider normalization).
* resolve_llm_provider_for_rate_limit happy paths
  (gpt-4o-mini, anthropic/..., bedrock/...) plus all three fallback
  branches (None, '', unknown name) plus a 'get_llm_provider raises'
  case that asserts we swallow the secondary exception.
* For each limiter (parallel v1/v3, dynamic v1/v3, batch,
  max_budget, max_iterations, max_budget_per_session): assert the
  raised exception is a RateLimitError carrying the resolved
  model + llm_provider, and a sibling test that asserts the
  fallback path returns 'litellm_proxy' without leaking a second
  exception.
* Two PrometheusLogger._get_exception_class_name pins so the
  Prometheus failure metric label flips from 'HTTPException' to
  'Openai.ProxyHTTPRateLimitError' (or 'Litellm_proxy.*' on
  fallback) — that's what dashboards consume.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
@codecov

codecov Bot commented May 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@CLAassistant

CLAassistant commented May 12, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ mateo-berri
❌ cursoragent
You have signed the CLA already but the status is still pending? Let us recheck it.

Comment thread litellm/proxy/hooks/dynamic_rate_limiter.py Outdated
Comment thread litellm/proxy/hooks/parallel_request_limiter.py
Comment thread litellm/proxy/hooks/dynamic_rate_limiter.py

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Missing attributes cause AttributeError when stringifying exception
    • Initialized self.num_retries and self.max_retries to None in ProxyHTTPRateLimitError.__init__ so that RateLimitError.__str__ (resolved via MRO) no longer raises AttributeError when the exception is stringified.
Preview (af811ec81e)
diff --git a/litellm/proxy/hooks/batch_rate_limiter.py b/litellm/proxy/hooks/batch_rate_limiter.py
--- a/litellm/proxy/hooks/batch_rate_limiter.py
+++ b/litellm/proxy/hooks/batch_rate_limiter.py
@@ -31,6 +31,10 @@
 )
 from litellm.integrations.custom_logger import CustomLogger
 from litellm.proxy._types import UserAPIKeyAuth
+from litellm.proxy.hooks.rate_limiter_utils import (
+    ProxyHTTPRateLimitError,
+    resolve_llm_provider_for_rate_limit,
+)
 
 if TYPE_CHECKING:
     from opentelemetry.trace import Span as _Span
@@ -104,6 +108,7 @@
         descriptors: List["RateLimitDescriptor"],
         batch_usage: BatchFileUsage,
         limit_type: str,
+        requested_model: Optional[str] = None,
     ) -> None:
         """Raise HTTPException for rate limit exceeded."""
         from datetime import datetime
@@ -148,7 +153,10 @@
                 f"Limit resets at: {reset_time_formatted}"
             )
 
-        raise HTTPException(
+        resolved_model, llm_provider = resolve_llm_provider_for_rate_limit(
+            requested_model
+        )
+        raise ProxyHTTPRateLimitError(
             status_code=429,
             detail=detail,
             headers={
@@ -156,6 +164,8 @@
                 "rate_limit_type": limit_type,
                 "reset_at": reset_time_formatted,
             },
+            model=resolved_model,
+            llm_provider=llm_provider,
         )
 
     async def _check_and_increment_batch_counters(
@@ -197,6 +207,7 @@
         )
 
         if rate_limit_response["overall_code"] == "OVER_LIMIT":
+            requested_model = data.get("model") if data else None
             for status in rate_limit_response["statuses"]:
                 if status["code"] == "OVER_LIMIT":
                     self._raise_rate_limit_error(
@@ -204,6 +215,7 @@
                         descriptors,
                         batch_usage,
                         status["rate_limit_type"],
+                        requested_model=requested_model,
                     )
 
     async def count_input_file_usage(

diff --git a/litellm/proxy/hooks/dynamic_rate_limiter.py b/litellm/proxy/hooks/dynamic_rate_limiter.py
--- a/litellm/proxy/hooks/dynamic_rate_limiter.py
+++ b/litellm/proxy/hooks/dynamic_rate_limiter.py
@@ -6,21 +6,22 @@
 import os
 from typing import List, Optional, Tuple, Union
 
-from fastapi import HTTPException
-
 import litellm
 from litellm import ModelResponse, Router
 from litellm._logging import verbose_proxy_logger
 from litellm.caching.caching import DualCache
 from litellm.integrations.custom_logger import CustomLogger
 from litellm.proxy._types import UserAPIKeyAuth
+from litellm.proxy.hooks.rate_limiter_utils import (
+    ProxyHTTPRateLimitError,
+    convert_priority_to_percent,
+    resolve_llm_provider_for_rate_limit,
+)
 from litellm.types.router import ModelGroupInfo
 from litellm.types.utils import CallTypesLiteral
 from litellm.utils import get_utc_datetime
 
-from .rate_limiter_utils import convert_priority_to_percent
 
-
 class DynamicRateLimiterCache:
     """
     Thin wrapper on DualCache for this file.
@@ -218,7 +219,10 @@
             )
             ### CHECK TPM ###
             if available_tpm is not None and available_tpm == 0:
-                raise HTTPException(
+                resolved_model, llm_provider = resolve_llm_provider_for_rate_limit(
+                    data.get("model")
+                )
+                raise ProxyHTTPRateLimitError(
                     status_code=429,
                     detail={
                         "error": "Key={} over available TPM={}. Model TPM={}, Active keys={}".format(
@@ -228,10 +232,15 @@
                             active_projects,
                         )
                     },
+                    model=resolved_model,
+                    llm_provider=llm_provider,
                 )
             ### CHECK RPM ###
             elif available_rpm is not None and available_rpm == 0:
-                raise HTTPException(
+                resolved_model, llm_provider = resolve_llm_provider_for_rate_limit(
+                    data.get("model")
+                )
+                raise ProxyHTTPRateLimitError(
                     status_code=429,
                     detail={
                         "error": "Key={} over available RPM={}. Model RPM={}, Active keys={}".format(
@@ -241,6 +250,8 @@
                             active_projects,
                         )
                     },
+                    model=resolved_model,
+                    llm_provider=llm_provider,
                 )
             elif available_rpm is not None or available_tpm is not None:
                 ## UPDATE CACHE WITH ACTIVE PROJECT

diff --git a/litellm/proxy/hooks/dynamic_rate_limiter_v3.py b/litellm/proxy/hooks/dynamic_rate_limiter_v3.py
--- a/litellm/proxy/hooks/dynamic_rate_limiter_v3.py
+++ b/litellm/proxy/hooks/dynamic_rate_limiter_v3.py
@@ -19,7 +19,11 @@
     RateLimitDescriptorRateLimitObject,
     _PROXY_MaxParallelRequestsHandler_v3,
 )
-from litellm.proxy.hooks.rate_limiter_utils import convert_priority_to_percent
+from litellm.proxy.hooks.rate_limiter_utils import (
+    ProxyHTTPRateLimitError,
+    convert_priority_to_percent,
+    resolve_llm_provider_for_rate_limit,
+)
 from litellm.proxy.utils import InternalUsageCache
 from litellm.types.router import ModelGroupInfo
 from litellm.types.utils import CallTypesLiteral
@@ -487,12 +491,13 @@
         )
 
         if atomic_response["overall_code"] == "OVER_LIMIT":
+            resolved_model, llm_provider = resolve_llm_provider_for_rate_limit(model)
             for status in atomic_response["statuses"]:
                 if status["code"] != "OVER_LIMIT":
                     continue
                 descriptor_key = status["descriptor_key"]
                 if descriptor_key == "model_saturation_check":
-                    raise HTTPException(
+                    raise ProxyHTTPRateLimitError(
                         status_code=429,
                         detail={
                             "error": f"Model capacity reached for {model}. "
@@ -507,13 +512,15 @@
                             "rate_limit_type": str(status["rate_limit_type"]),
                             "x-litellm-priority": priority or "default",
                         },
+                        model=resolved_model,
+                        llm_provider=llm_provider,
                     )
                 if descriptor_key == "priority_model":
                     verbose_proxy_logger.debug(
                         f"Enforcing priority limits for {model}, saturation: {saturation:.1%}, "
                         f"priority: {priority}"
                     )
-                    raise HTTPException(
+                    raise ProxyHTTPRateLimitError(
                         status_code=429,
                         detail={
                             "error": f"Priority-based rate limit exceeded. "
@@ -531,6 +538,8 @@
                             "x-litellm-priority": priority or "default",
                             "x-litellm-saturation": f"{saturation:.2%}",
                         },
+                        model=resolved_model,
+                        llm_provider=llm_provider,
                     )
 
             # Fail-closed guard: overall_code says OVER_LIMIT but no status
@@ -547,7 +556,7 @@
                 f"Dynamic rate limiter: OVER_LIMIT response with unknown "
                 f"descriptor_key(s) — refusing request. response={atomic_response}"
             )
-            raise HTTPException(
+            raise ProxyHTTPRateLimitError(
                 status_code=429,
                 detail={
                     "error": "Rate limit exceeded",
@@ -562,6 +571,8 @@
                     "retry-after": str(self.v3_limiter.window_size),
                     "x-litellm-priority": priority or "default",
                 },
+                model=resolved_model,
+                llm_provider=llm_provider,
             )
 
         # If priority is NOT enforced (saturation below threshold) but

diff --git a/litellm/proxy/hooks/max_budget_limiter.py b/litellm/proxy/hooks/max_budget_limiter.py
--- a/litellm/proxy/hooks/max_budget_limiter.py
+++ b/litellm/proxy/hooks/max_budget_limiter.py
@@ -5,6 +5,10 @@
 from litellm.caching.caching import DualCache
 from litellm.integrations.custom_logger import CustomLogger
 from litellm.proxy._types import UserAPIKeyAuth
+from litellm.proxy.hooks.rate_limiter_utils import (
+    ProxyHTTPRateLimitError,
+    resolve_llm_provider_for_rate_limit,
+)
 
 
 class _PROXY_MaxBudgetLimiter(CustomLogger):
@@ -63,7 +67,15 @@
 
             # CHECK IF REQUEST ALLOWED
             if curr_spend >= max_budget:
-                raise HTTPException(status_code=429, detail="Max budget limit reached.")
+                resolved_model, llm_provider = resolve_llm_provider_for_rate_limit(
+                    data.get("model") if data else None
+                )
+                raise ProxyHTTPRateLimitError(
+                    status_code=429,
+                    detail="Max budget limit reached.",
+                    model=resolved_model,
+                    llm_provider=llm_provider,
+                )
         except HTTPException as e:
             raise e
         except Exception as e:

diff --git a/litellm/proxy/hooks/max_budget_per_session_limiter.py b/litellm/proxy/hooks/max_budget_per_session_limiter.py
--- a/litellm/proxy/hooks/max_budget_per_session_limiter.py
+++ b/litellm/proxy/hooks/max_budget_per_session_limiter.py
@@ -17,12 +17,14 @@
 import os
 from typing import TYPE_CHECKING, Any, Optional, Union
 
-from fastapi import HTTPException
-
 from litellm import DualCache
 from litellm._logging import verbose_proxy_logger
 from litellm.integrations.custom_logger import CustomLogger
 from litellm.proxy._types import UserAPIKeyAuth
+from litellm.proxy.hooks.rate_limiter_utils import (
+    ProxyHTTPRateLimitError,
+    resolve_llm_provider_for_rate_limit,
+)
 
 if TYPE_CHECKING:
     from litellm.proxy.utils import InternalUsageCache as _InternalUsageCache
@@ -112,13 +114,18 @@
         )
 
         if current_spend >= max_budget:
-            raise HTTPException(
+            resolved_model, llm_provider = resolve_llm_provider_for_rate_limit(
+                data.get("model") if data else None
+            )
+            raise ProxyHTTPRateLimitError(
                 status_code=429,
                 detail=(
                     f"Session budget exceeded for session {session_id}. "
                     f"Current spend: ${current_spend:.4f}, "
                     f"max_budget_per_session: ${max_budget:.2f}."
                 ),
+                model=resolved_model,
+                llm_provider=llm_provider,
             )
 
         return None

diff --git a/litellm/proxy/hooks/max_iterations_limiter.py b/litellm/proxy/hooks/max_iterations_limiter.py
--- a/litellm/proxy/hooks/max_iterations_limiter.py
+++ b/litellm/proxy/hooks/max_iterations_limiter.py
@@ -13,12 +13,14 @@
 import os
 from typing import TYPE_CHECKING, Any, Optional, Union
 
-from fastapi import HTTPException
-
 from litellm import DualCache
 from litellm._logging import verbose_proxy_logger
 from litellm.integrations.custom_logger import CustomLogger
 from litellm.proxy._types import UserAPIKeyAuth
+from litellm.proxy.hooks.rate_limiter_utils import (
+    ProxyHTTPRateLimitError,
+    resolve_llm_provider_for_rate_limit,
+)
 
 if TYPE_CHECKING:
     from litellm.proxy.utils import InternalUsageCache as _InternalUsageCache
@@ -116,12 +118,17 @@
         current_count = await self._increment_and_get(cache_key)
 
         if current_count > max_iterations:
-            raise HTTPException(
+            resolved_model, llm_provider = resolve_llm_provider_for_rate_limit(
+                data.get("model") if data else None
+            )
+            raise ProxyHTTPRateLimitError(
                 status_code=429,
                 detail=(
                     f"Max iterations exceeded for session {session_id}. "
                     f"Current count: {current_count}, max_iterations: {max_iterations}."
                 ),
+                model=resolved_model,
+                llm_provider=llm_provider,
             )
 
         verbose_proxy_logger.debug(

diff --git a/litellm/proxy/hooks/parallel_request_limiter.py b/litellm/proxy/hooks/parallel_request_limiter.py
--- a/litellm/proxy/hooks/parallel_request_limiter.py
+++ b/litellm/proxy/hooks/parallel_request_limiter.py
@@ -17,6 +17,10 @@
     get_key_model_rpm_limit,
     get_key_model_tpm_limit,
 )
+from litellm.proxy.hooks.rate_limiter_utils import (
+    ProxyHTTPRateLimitError,
+    resolve_llm_provider_for_rate_limit,
+)
 
 if TYPE_CHECKING:
     from opentelemetry.trace import Span as _Span
@@ -73,7 +77,8 @@
             if max_parallel_requests == 0 or tpm_limit == 0 or rpm_limit == 0:
                 # base case
                 raise self.raise_rate_limit_error(
-                    additional_details=f"{CommonProxyErrors.max_parallel_request_limit_reached.value}. Hit limit for {rate_limit_type}. Current limits: max_parallel_requests: {max_parallel_requests}, tpm_limit: {tpm_limit}, rpm_limit: {rpm_limit}"
+                    additional_details=f"{CommonProxyErrors.max_parallel_request_limit_reached.value}. Hit limit for {rate_limit_type}. Current limits: max_parallel_requests: {max_parallel_requests}, tpm_limit: {tpm_limit}, rpm_limit: {rpm_limit}",
+                    requested_model=data.get("model") if data else None,
                 )
             new_val = {
                 "current_requests": 1,
@@ -95,10 +100,16 @@
             values_to_update_in_cache.append((request_count_api_key, new_val))
 
         else:
-            raise HTTPException(
+            requested_model = data.get("model") if data else None
+            resolved_model, llm_provider = resolve_llm_provider_for_rate_limit(
+                requested_model
+            )
+            raise ProxyHTTPRateLimitError(
                 status_code=429,
                 detail=f"LiteLLM Rate Limit Handler for rate limit type = {rate_limit_type}. {CommonProxyErrors.max_parallel_request_limit_reached.value}. current rpm: {current['current_rpm']}, rpm limit: {rpm_limit}, current tpm: {current['current_tpm']}, tpm limit: {tpm_limit}, current max_parallel_requests: {current['current_requests']}, max_parallel_requests: {max_parallel_requests}",
                 headers={"retry-after": str(self.time_to_next_minute())},
+                model=resolved_model,
+                llm_provider=llm_provider,
             )
 
         await self.internal_usage_cache.async_batch_set_cache(
@@ -122,18 +133,31 @@
         return seconds_to_next_minute
 
     def raise_rate_limit_error(
-        self, additional_details: Optional[str] = None
+        self,
+        additional_details: Optional[str] = None,
+        requested_model: Optional[str] = None,
     ) -> HTTPException:
         """
-        Raise an HTTPException with a 429 status code and a retry-after header
+        Raise an HTTPException with a 429 status code and a retry-after header.
+
+        ``requested_model`` is resolved via :func:`get_llm_provider` so the
+        raised exception carries ``llm_provider`` for downstream loggers
+        (Prometheus failure metric, observability callbacks). Falls back to
+        ``llm_provider="litellm_proxy"`` when the model is missing or
+        unparseable — see ``resolve_llm_provider_for_rate_limit``.
         """
         error_message = "Max parallel request limit reached"
         if additional_details is not None:
             error_message = error_message + " " + additional_details
-        raise HTTPException(
+        resolved_model, llm_provider = resolve_llm_provider_for_rate_limit(
+            requested_model
+        )
+        raise ProxyHTTPRateLimitError(
             status_code=429,
-            detail=f"Max parallel request limit reached {additional_details}",
+            detail=error_message,
             headers={"retry-after": str(self.time_to_next_minute())},
+            model=resolved_model,
+            llm_provider=llm_provider,
         )
 
     async def get_all_cache_objects(
@@ -225,7 +249,8 @@
             # if above -> raise error
             if current_global_requests >= global_max_parallel_requests:
                 return self.raise_rate_limit_error(
-                    additional_details=f"Hit Global Limit: Limit={global_max_parallel_requests}, current: {current_global_requests}"
+                    additional_details=f"Hit Global Limit: Limit={global_max_parallel_requests}, current: {current_global_requests}",
+                    requested_model=data.get("model") if data else None,
                 )
             # if below -> increment
             else:

diff --git a/litellm/proxy/hooks/parallel_request_limiter_v3.py b/litellm/proxy/hooks/parallel_request_limiter_v3.py
--- a/litellm/proxy/hooks/parallel_request_limiter_v3.py
+++ b/litellm/proxy/hooks/parallel_request_limiter_v3.py
@@ -23,8 +23,6 @@
     cast,
 )
 
-from fastapi import HTTPException
-
 from litellm import DualCache
 from litellm._logging import verbose_proxy_logger
 from litellm.constants import DYNAMIC_RATE_LIMIT_ERROR_THRESHOLD_PER_MINUTE
@@ -34,6 +32,10 @@
 )
 from litellm.proxy._types import UserAPIKeyAuth
 from litellm.proxy.auth.auth_utils import get_model_rate_limit_from_metadata
+from litellm.proxy.hooks.rate_limiter_utils import (
+    ProxyHTTPRateLimitError,
+    resolve_llm_provider_for_rate_limit,
+)
 from litellm.types.caching import RedisPipelineIncrementOperation
 from litellm.types.llms.openai import BaseLiteLLMOpenAIResponseObject
 from litellm.types.utils import ModelResponse, Usage
@@ -1837,6 +1839,7 @@
         self,
         response: RateLimitResponse,
         descriptors: List[RateLimitDescriptor],
+        requested_model: Optional[str] = None,
     ) -> None:
         """Handle rate limit exceeded error by raising HTTPException."""
         for status in response["statuses"]:
@@ -1869,7 +1872,10 @@
                     f"Limit resets at: {reset_time_formatted}"
                 )
 
-                raise HTTPException(
+                resolved_model, llm_provider = resolve_llm_provider_for_rate_limit(
+                    requested_model
+                )
+                raise ProxyHTTPRateLimitError(
                     status_code=429,
                     detail=detail,
                     headers={
@@ -1877,6 +1883,8 @@
                         "rate_limit_type": str(status["rate_limit_type"]),
                         "reset_at": reset_time_formatted,
                     },
+                    model=resolved_model,
+                    llm_provider=llm_provider,
                 )
 
     async def async_pre_call_hook(
@@ -1977,6 +1985,7 @@
                 self._handle_rate_limit_error(
                     response=response,
                     descriptors=descriptors,
+                    requested_model=requested_model,
                 )
             else:
                 # add descriptors to request headers
@@ -2022,6 +2031,7 @@
                     self._handle_rate_limit_error(
                         response=tpm_response,
                         descriptors=descriptors,
+                        requested_model=requested_model,
                     )
                 else:
                     data["_litellm_rate_limit_descriptors"] = descriptors

diff --git a/litellm/proxy/hooks/rate_limiter_utils.py b/litellm/proxy/hooks/rate_limiter_utils.py
--- a/litellm/proxy/hooks/rate_limiter_utils.py
+++ b/litellm/proxy/hooks/rate_limiter_utils.py
@@ -2,12 +2,106 @@
 Shared utility functions for rate limiter hooks.
 """
 
-from typing import Optional, Union
+from typing import Any, Optional, Tuple, Union
 
+from fastapi import HTTPException
+
+import litellm
+from litellm._logging import verbose_proxy_logger
+from litellm.exceptions import RateLimitError
 from litellm.types.router import ModelGroupInfo
 from litellm.types.utils import PriorityReservationDict
 
+PROXY_LLM_PROVIDER_FALLBACK = "litellm_proxy"
 
+
+def resolve_llm_provider_for_rate_limit(
+    model: Optional[str],
+) -> Tuple[str, str]:
+    """
+    Resolve ``(model, llm_provider)`` for a request being rejected by an
+    internal proxy-side rate-limit hook.
+
+    These hooks fire from ``async_pre_call_hook`` — well before
+    :func:`litellm.get_llm_provider` is invoked anywhere else in the request
+    lifecycle — so the raised 429 would otherwise have an empty
+    ``llm_provider`` field, making the resulting Prometheus
+    ``litellm_proxy_failed_requests_metric`` show up with
+    ``exception_class="RateLimitError"`` and no provider attribution.
+
+    Wrapped defensively: if ``model`` is missing, malformed, or
+    ``get_llm_provider`` raises (unknown alias, router-only model, etc.) we
+    fall back to ``("", "litellm_proxy")`` so we never break the request path
+    by piling a second exception on top of the rate-limit one we're trying to
+    raise.
+    """
+    if not model:
+        return "", PROXY_LLM_PROVIDER_FALLBACK
+    try:
+        resolved_model, custom_llm_provider, _, _ = litellm.get_llm_provider(
+            model=model,
+        )
+        return (
+            resolved_model or model,
+            custom_llm_provider or PROXY_LLM_PROVIDER_FALLBACK,
+        )
+    except Exception as e:
+        verbose_proxy_logger.debug(
+            "rate_limiter_utils.resolve_llm_provider_for_rate_limit: "
+            "could not resolve provider for model=%s, falling back to %s. err=%s",
+            model,
+            PROXY_LLM_PROVIDER_FALLBACK,
+            str(e),
+        )
+        return model, PROXY_LLM_PROVIDER_FALLBACK
+
+
+class ProxyHTTPRateLimitError(HTTPException, RateLimitError):
+    """
+    HTTPException raised by proxy-side rate-limit hooks that *also* exposes
+    ``model`` and ``llm_provider`` attributes.
+
+    Why both base classes:
+
+    - The proxy server's exception handler keys off ``HTTPException`` to render
+      a 429 response, so we must remain an ``HTTPException``.
+    - Downstream loggers (Prometheus ``async_post_call_failure_hook``,
+      structured logging, observability callbacks) read ``exception.llm_provider``
+      via :meth:`litellm.integrations.prometheus.PrometheusLogger._get_exception_class_name`
+      and ``isinstance(exc, RateLimitError)`` for category routing. Inheriting
+      from :class:`litellm.exceptions.RateLimitError` keeps that wiring intact.
+
+    We intentionally do not call ``RateLimitError.__init__`` (which constructs
+    an httpx.Response) — it isn't needed here and just adds failure surface.
+    Attribute parity is what downstream consumers rely on.
+    """
+
+    def __init__(
+        self,
+        status_code: int,
+        detail: Any = None,
+        headers: Optional[dict] = None,
+        *,
+        model: str = "",
+        llm_provider: str = PROXY_LLM_PROVIDER_FALLBACK,
+    ) -> None:
+        HTTPException.__init__(
+            self, status_code=status_code, detail=detail, headers=headers
+        )
+        self.status_code = status_code
+        self.model = model or ""
+        self.llm_provider = llm_provider or PROXY_LLM_PROVIDER_FALLBACK
+        # `message` is what RateLimitError.__str__ would print and what some
+        # observability callbacks log. Keep it human-readable.
+        self.message = detail if isinstance(detail, str) else str(detail)
+        # `RateLimitError.__str__` (resolved via MRO since Starlette's
+        # HTTPException doesn't define `__str__`) unconditionally reads
+        # these attributes. Set them so `str(exc)` doesn't raise
+        # AttributeError from logging/traceback paths.
+        self.num_retries: Optional[int] = None
+        self.max_retries: Optional[int] = None
+
+
 def convert_priority_to_percent(
     value: Union[float, PriorityReservationDict], model_info: Optional[ModelGroupInfo]
 ) -> float:

diff --git a/tests/test_litellm/proxy/hooks/test_proxy_rate_limit_provider_field.py b/tests/test_litellm/proxy/hooks/test_proxy_rate_limit_provider_field.py
new file mode 100644
--- /dev/null
+++ b/tests/test_litellm/proxy/hooks/test_proxy_rate_limit_provider_field.py
@@ -1,0 +1,968 @@
+"""
+Regression tests for the "provider field missing" bug on proxy-side
+rate-limit errors.
+
+Background
+----------
+The proxy's internal rate-limit hooks (parallel_request_limiter,
+parallel_request_limiter_v3, dynamic_rate_limiter, dynamic_rate_limiter_v3,
+batch_rate_limiter, max_budget_limiter, max_iterations_limiter,
+max_budget_per_session_limiter) all fire from ``async_pre_call_hook`` —
+*before* :func:`litellm.get_llm_provider` runs anywhere else in the request
+lifecycle.
+
+Until now, those hooks raised a bare ``HTTPException(429, ...)`` which carries
+no ``llm_provider`` / ``model`` attribute. Downstream:
+
+- The Prometheus ``litellm_proxy_failed_requests_metric`` reads
+  ``exception.llm_provider`` via ``_get_exception_class_name`` — it came back
+  empty, so dashboards showed ``exception_class="HTTPException"`` with no
+  provider attribution.
+- Observability callbacks that ``isinstance(e, RateLimitError)`` for
+  category routing missed these entirely.
+
+The fix wraps every internal raise site in
+:class:`ProxyHTTPRateLimitError` (an ``HTTPException`` *and* a
+``litellm.RateLimitError``), and resolves ``model`` / ``llm_provider`` from
+``data["model"]`` via :func:`get_llm_provider`. When the model is missing or
+unparseable we fall back to ``llm_provider="litellm_proxy"`` so we never break
+the request path with a second exception.
+
+These tests pin both the happy path (provider correctly resolved) and the
+fallback path (unknown model, missing model) for every limiter.
+"""
+
+import sys
+from unittest.mock import AsyncMock, MagicMock, patch
+
+import pytest
+from fastapi import HTTPException
+
+import litellm
+from litellm.caching.caching import DualCache
+from litellm.exceptions import RateLimitError
+from litellm.proxy._types import UserAPIKeyAuth
+from litellm.proxy.hooks.batch_rate_limiter import (
+    BatchFileUsage,
+    _PROXY_BatchRateLimiter,
+)
+from litellm.proxy.hooks.dynamic_rate_limiter import _PROXY_DynamicRateLimitHandler
+from litellm.proxy.hooks.dynamic_rate_limiter_v3 import (
+    _PROXY_DynamicRateLimitHandlerV3,
+)
+from litellm.proxy.hooks.max_budget_limiter import _PROXY_MaxBudgetLimiter
+from litellm.proxy.hooks.max_budget_per_session_limiter import (
+    _PROXY_MaxBudgetPerSessionHandler,
+)
+from litellm.proxy.hooks.max_iterations_limiter import _PROXY_MaxIterationsHandler
+from litellm.proxy.hooks.parallel_request_limiter import (
+    _PROXY_MaxParallelRequestsHandler,
+)
+from litellm.proxy.hooks.parallel_request_limiter_v3 import (
+    _PROXY_MaxParallelRequestsHandler_v3,
+)
+from litellm.proxy.hooks.rate_limiter_utils import (
+    PROXY_LLM_PROVIDER_FALLBACK,
+    ProxyHTTPRateLimitError,
+    resolve_llm_provider_for_rate_limit,
+)
+from litellm.proxy.utils import InternalUsageCache
+from litellm.types.agents import AgentResponse
+
+
+# ---------------------------------------------------------------------------
+# Helper class itself
+# ---------------------------------------------------------------------------
+
+
+class TestProxyHTTPRateLimitErrorClass:
+    """Pin the dual ``HTTPException`` + ``RateLimitError`` shape."""
+
+    def test_is_both_http_exception_and_rate_limit_error(self):
+        e = ProxyHTTPRateLimitError(
+            status_code=429,
+            detail="boom",
+            model="gpt-4o-mini",
+            llm_provider="openai",
+        )
+        # FastAPI handler keys off HTTPException to render the 429.
+        assert isinstance(e, HTTPException)
+        # Prometheus / observability key off RateLimitError + .llm_provider.
+        assert isinstance(e, RateLimitError)
+        assert e.status_code == 429
+        assert e.model == "gpt-4o-mini"
+        assert e.llm_provider == "openai"
+        assert e.message == "boom"
+        assert e.detail == "boom"
+
+    def test_dict_detail_is_stringified_for_message(self):
+        # Some hooks pass a dict detail (e.g. dynamic_rate_limiter v1) — the
+        # `message` attr (read by RateLimitError.__str__ and observability
+        # callbacks) must still be a string.
+        e = ProxyHTTPRateLimitError(
+            status_code=429,
+            detail={"error": "over rpm"},
+            model="claude-3-5-sonnet",
+            llm_provider="anthropic",
+        )
+        assert isinstance(e.message, str)
+        assert "over rpm" in e.message
+
+    def test_defaults_to_litellm_proxy_provider(self):
+        e = ProxyHTTPRateLimitError(status_code=429, detail="x")
+        assert e.llm_provider == PROXY_LLM_PROVIDER_FALLBACK
+        assert e.model == ""
+
+    def test_none_provider_normalized_to_fallback(self):
+        e = ProxyHTTPRateLimitError(
+            status_code=429,
+            detail="x",
+            model=None,  # type: ignore[arg-type]
+            llm_provider=None,  # type: ignore[arg-type]
+        )
+        assert e.llm_provider == PROXY_LLM_PROVIDER_FALLBACK
+        assert e.model == ""
+
+
+class TestResolveLLMProviderForRateLimit:
+    @pytest.mark.parametrize(
+        "model, expected_provider",
+        [
+            ("gpt-4o-mini", "openai"),
+            ("anthropic/claude-3-5-sonnet", "anthropic"),
+            ("bedrock/meta.llama3-1-70b-instruct-v1:0", "bedrock"),
+        ],
+    )
+    def test_known_models_resolve_provider(self, model, expected_provider):
+        resolved_model, provider = resolve_llm_provider_for_rate_limit(model)
+        assert provider == expected_provider
+        assert resolved_model  # non-empty
+
+    @pytest.mark.parametrize("model", [None, "", "totally-not-a-real-model-name"])
+    def test_missing_or_unknown_model_falls_back(self, model):
+        # Must never raise — the resolver wraps `get_llm_provider` defensively
+        # because raising here would mask the rate-limit error we're trying
+        # to surface to the user.
+        resolved_model, provider = resolve_llm_provider_for_rate_limit(model)
+        assert provider == PROXY_LLM_PROVIDER_FALLBACK
+        # Resolver returns the input model verbatim on the unknown branch so
+        # the `.model` attribute is never silently swapped to a different one.
+        if not model:
+            assert resolved_model == ""
+        else:
+            assert resolved_model == model
+
+    def test_get_llm_provider_raising_is_swallowed(self):
+        # If get_llm_provider itself blows up (unexpected error), we still
+        # fall back rather than letting the secondary exception escape.
+        with patch.object(
+            litellm,
+            "get_llm_provider",
+            side_effect=RuntimeError("boom"),
+        ):
+            resolved_model, provider = resolve_llm_provider_for_rate_limit("anything")
+        assert provider == PROXY_LLM_PROVIDER_FALLBACK
+        assert resolved_model == "anything"
+
+
+# ---------------------------------------------------------------------------
+# parallel_request_limiter v1
+# ---------------------------------------------------------------------------
+
+
+@pytest.mark.asyncio
+async def test_parallel_request_limiter_v1_populates_provider_when_at_rpm_limit():
+    """
+    Trip the per-key RPM cap and assert the raised exception carries
+    ``model`` / ``llm_provider`` resolved from ``data["model"]``.
+    """
+    handler = _PROXY_MaxParallelRequestsHandler(
+        internal_usage_cache=InternalUsageCache(DualCache())
+    )
+    user_api_key_dict = UserAPIKeyAuth(
+        api_key="sk-rl-test",
+        max_parallel_requests=10,
+        rpm_limit=1,
+        tpm_limit=10,
+    )
+    data = {"model": "gpt-4o-mini"}
+
+    # First request consumes the budget.
+    await handler.async_pre_call_hook(
+        user_api_key_dict=user_api_key_dict,
+        cache=DualCache(),
+        data=data,
+        call_type="completion",
+    )
+
+    with pytest.raises(HTTPException) as exc_info:
+        await handler.async_pre_call_hook(
+            user_api_key_dict=user_api_key_dict,
+            cache=DualCache(),
+            data=data,
+            call_type="completion",
+        )
+
+    exc = exc_info.value
+    assert exc.status_code == 429
... diff truncated: showing 800 of 1561 lines

You can send follow-ups to the cloud agent here.

Comment thread litellm/proxy/hooks/rate_limiter_utils.py
ProxyHTTPRateLimitError inherits from RateLimitError but did not call
RateLimitError.__init__, so num_retries/max_retries were never set.
When Starlette's HTTPException lacks __str__, MRO falls through to
RateLimitError.__str__, which unconditionally reads these attributes
and raises AttributeError during logging/traceback formatting.
Initialize them to None defensively.
@greptile-apps

greptile-apps Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a missing llm_provider field on proxy-side rate-limit 429 errors by resolving the provider from data["model"] at every internal limiter raise site before the exception is raised. A new ProxyHTTPRateLimitError class (dual-inheriting HTTPException + RateLimitError) is introduced in rate_limiter_utils.py to carry both fields, and 35 regression tests cover happy-path and fallback cases for all 8 limiters.

  • rate_limiter_utils.py gains resolve_llm_provider_for_rate_limit (a defensive wrapper around litellm.get_llm_provider that falls back to \"litellm_proxy\" on any failure) and ProxyHTTPRateLimitError.
  • All 8 limiter files replace their bare HTTPException(429, ...) raises with ProxyHTTPRateLimitError, threading model/llm_provider through each raise site.
  • A subtle but intentional behavior improvement: raise_rate_limit_error in parallel_request_limiter.py previously produced \"Max parallel request limit reached None\" when additional_details was absent; it now correctly omits the None suffix.

Confidence Score: 5/5

Safe to merge — all changes are additive improvements to error metadata; the defensive fallback ensures a get_llm_provider failure never replaces the 429 with a secondary exception.

Every raise site is wrapped defensively, the new ProxyHTTPRateLimitError correctly sets the attributes required by RateLimitError.str, and the 35 regression tests cover both happy-path provider resolution and the fallback path across all 8 limiters. No existing test assertions are weakened and no hot request path is affected.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/hooks/rate_limiter_utils.py New ProxyHTTPRateLimitError class and resolve_llm_provider_for_rate_limit helper; well-documented MRO trade-offs, defensive fallback, and required attributes (message, num_retries, max_retries) all set.
litellm/proxy/hooks/parallel_request_limiter.py Three raise sites updated to ProxyHTTPRateLimitError; raise_rate_limit_error gains requested_model param and fixes the pre-existing 'None' suffix bug in the detail message.
litellm/proxy/hooks/parallel_request_limiter_v3.py _handle_rate_limit_error gains optional requested_model; resolve_llm_provider_for_rate_limit is called inside the loop but always raises on the first matching status so only one resolution happens per error path.
litellm/proxy/hooks/dynamic_rate_limiter.py Two raise sites (TPM and RPM zero-check) updated; fastapi.HTTPException import removed and replaced by import from rate_limiter_utils.
litellm/proxy/hooks/dynamic_rate_limiter_v3.py Provider resolution moved before the status loop so all three raise sites (saturation, priority, fail-closed) share one resolver call — consistent and correct.
litellm/proxy/hooks/batch_rate_limiter.py requested_model captured from data before the status loop and threaded through _raise_rate_limit_error; single raise site updated correctly.
litellm/proxy/hooks/max_budget_limiter.py Single raise site replaced with ProxyHTTPRateLimitError; data.get('model') if data else None guard is consistent with other limiters.
litellm/proxy/hooks/max_budget_per_session_limiter.py Single raise site updated; fastapi.HTTPException import dropped, replaced by rate_limiter_utils import.
litellm/proxy/hooks/max_iterations_limiter.py Single raise site updated; same pattern as max_budget_per_session_limiter — clean and consistent.
tests/test_litellm/proxy/hooks/test_proxy_rate_limit_provider_field.py 35 new mock-only tests covering happy-path provider resolution, fallback on unknown/missing model, and Prometheus label format for all 8 limiters; no real network calls.

Reviews (2): Last reviewed commit: "fix(mypy): silence base-class status_cod..." | Re-trigger Greptile

Comment thread litellm/proxy/hooks/rate_limiter_utils.py Outdated
…mitError

HTTPException declares 'status_code: int' while openai.RateLimitError
(via APIStatusError) declares 'status_code: Literal[429] = 429'. Mypy
flags the multi-base override as [misc] in CI lint. The runtime semantics
are fine (we set self.status_code in __init__), so silence the
class-level annotation conflict with a targeted ignore.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@Sameerlite

Copy link
Copy Markdown
Contributor

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit d00ad1d. Configure here.

@Sameerlite Sameerlite left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks

@mateo-berri
mateo-berri changed the base branch from litellm_internal_staging to litellm_standardize_rate_limit_errors-5fb4 May 13, 2026 22:58
@mateo-berri
mateo-berri changed the base branch from litellm_standardize_rate_limit_errors-5fb4 to litellm_internal_staging May 13, 2026 22:58
cursor Bot pushed a commit that referenced this pull request May 14, 2026
…rs) into #27687

Fold the provider-attribution work from #27707 onto #27687's unified
ProxyRateLimitError so a single 429 carries:

  - category (LITELLM_RATE_LIMIT / LITELLM_BATCH_RATE_LIMIT / vendor)
  - rate_limit_type (REQUESTS / TOKENS / CONCURRENT_REQUESTS / BUDGET / MAX_ITERATIONS)
  - model (resolved via litellm.get_llm_provider so the prefix is stripped)
  - llm_provider (with 'litellm_proxy' fallback when model is missing/unparseable)

Mechanics
- Drop ProxyHTTPRateLimitError from rate_limiter_utils (it was a parallel
  HTTPException+RateLimitError class introduced in #27707). Keep
  resolve_llm_provider_for_rate_limit() as the single helper for resolving
  (model, llm_provider) at the limiter raise site.
- At every internal raise site (parallel_request_limiter v1/v3,
  dynamic_rate_limiter v1/v3, batch_rate_limiter, max_budget_limiter,
  max_budget_per_session_limiter, max_iterations_limiter) call the resolver
  and pass model/llm_provider into ProxyRateLimitError alongside the
  category/rate_limit_type fields.
- _PROXY_MaxParallelRequestsHandler.raise_rate_limit_error now takes both
  rate_limit_type and requested_model (NoReturn return type preserved).
- _PROXY_BatchRateLimiter._raise_rate_limit_error takes requested_model.
- ProxyRateLimitError.__init__ normalizes None model/llm_provider so callers
  can pass through resolver output without a defensive fallback at every site.

Tests
- Update test_proxy_rate_limit_provider_field.py (added by #27707) to import
  ProxyRateLimitError from common_utils, drop status_code=429 from
  constructor calls, and replace the prometheus exception_class_name
  assertions with the back-compat shim from #27687 (returns 'HTTPException'
  to preserve existing dashboards).
- All 34 #27707 tests + all 142 #27687 tests + all 218 proxy/hooks tests pass.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
…itellm_provider-on-proxy-rate-limit-398f

# Conflicts:
#	litellm/proxy/hooks/batch_rate_limiter.py
@mateo-berri
mateo-berri enabled auto-merge (squash) June 5, 2026 05:43
@mateo-berri
mateo-berri merged commit df704d9 into litellm_internal_staging Jun 5, 2026
67 of 91 checks passed
@mateo-berri
mateo-berri deleted the litellm_provider-on-proxy-rate-limit-398f branch June 5, 2026 05:46
Sameerlite pushed a commit that referenced this pull request Jun 5, 2026
…#27707)

* feat(proxy/hooks): add ProxyHTTPRateLimitError + provider resolver

Introduces a small helper layer used by every proxy-side rate-limit
hook so that the 429 they raise carries a populated llm_provider /
model — instead of an empty exception.llm_provider that downstream
loggers (Prometheus failure metric, observability callbacks) read as
'no provider attribution'.

ProxyHTTPRateLimitError inherits from both fastapi.HTTPException
(so the proxy server still renders it as a 429) and
litellm.exceptions.RateLimitError (so isinstance checks and
PrometheusLogger._get_exception_class_name pick up llm_provider).
We deliberately don't call RateLimitError.__init__ — it constructs
an httpx.Response we don't need and would just add failure surface;
attribute parity is what downstream consumers care about.

resolve_llm_provider_for_rate_limit() wraps litellm.get_llm_provider
defensively. Internal limiter hooks fire from async_pre_call_hook —
well before get_llm_provider runs anywhere else in the request
lifecycle — so we have to call it ourselves at raise time. If the
model is missing or unparseable (alias, router-only model) we fall
back to llm_provider='litellm_proxy' rather than letting a second
exception leak out and break the request path.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* fix(proxy/hooks): populate llm_provider on parallel-request 429s

Both v1 and v3 parallel-request limiters fired bare HTTPException(429)
from inside async_pre_call_hook. The downstream Prometheus failure
metric reads exception.llm_provider via _get_exception_class_name —
the empty value showed up as exception_class='HTTPException' and
left model_id='None' on the time series.

Threads requested_model through every raise site in:

* parallel_request_limiter.py:
  - check_key_in_limits (the per-key/per-model/per-user/per-team/
    per-customer over-limit path)
  - raise_rate_limit_error (zero-limit + global_max_parallel_requests
    paths) — now takes an optional requested_model kwarg
* parallel_request_limiter_v3.py:
  - _handle_rate_limit_error (the OVER_LIMIT translator), called
    from both the should_rate_limit pre-check and the TPM
    reservation path

Resolved via resolve_llm_provider_for_rate_limit so unknown / missing
models silently fall back to llm_provider='litellm_proxy' instead of
breaking the request path with a second exception.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* fix(proxy/hooks): populate llm_provider on dynamic-rate-limit 429s

Same plumbing change as the parallel limiters, applied to both
dynamic_rate_limiter (v1) and dynamic_rate_limiter_v3:

* v1: TPM-zero and RPM-zero paths in async_pre_call_hook now resolve
  data['model'] -> (model, llm_provider) once and pass it into both
  raises.
* v3: All three raise sites in _check_rate_limits — the
  model_saturation_check enforced raise, the priority_model
  enforced raise, and the fail-closed unknown-descriptor branch —
  now attribute the 429 to the actual provider.

Falls back to llm_provider='litellm_proxy' when the model can't be
resolved.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* fix(proxy/hooks): populate llm_provider on batch-rate-limit 429s

batch_rate_limiter._raise_rate_limit_error now takes a
requested_model kwarg threaded from data['model'] in
_check_and_increment_batch_counters. The batch-creation 429 is what
gets raised when the input file's tokens/requests count would push
the per-key TPM/RPM window over its limit.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* fix(proxy/hooks): populate llm_provider on budget/iterations 429s

Final batch of internal raise sites — the user/session-budget and
max-iterations hooks. Same pattern: resolve data['model'] once at
raise time, attach to ProxyHTTPRateLimitError so Prometheus and
observability callbacks can attribute the 429.

Hooks updated:
* max_budget_limiter (per-user max_budget exceeded)
* max_iterations_limiter (per-session agent iteration cap)
* max_budget_per_session_limiter (per-session dollar cap)

All three fall back to llm_provider='litellm_proxy' when data['model']
is missing or unparseable. Drops the now-unused HTTPException import
from each module.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* test(proxy/hooks): pin provider field on internal rate-limit 429s

Regression coverage for the 'provider field missing' bug across every
proxy-side rate-limit hook + the helper layer:

* ProxyHTTPRateLimitError class shape (HTTPException + RateLimitError,
  dict-detail stringification, None-provider normalization).
* resolve_llm_provider_for_rate_limit happy paths
  (gpt-4o-mini, anthropic/..., bedrock/...) plus all three fallback
  branches (None, '', unknown name) plus a 'get_llm_provider raises'
  case that asserts we swallow the secondary exception.
* For each limiter (parallel v1/v3, dynamic v1/v3, batch,
  max_budget, max_iterations, max_budget_per_session): assert the
  raised exception is a RateLimitError carrying the resolved
  model + llm_provider, and a sibling test that asserts the
  fallback path returns 'litellm_proxy' without leaking a second
  exception.
* Two PrometheusLogger._get_exception_class_name pins so the
  Prometheus failure metric label flips from 'HTTPException' to
  'Openai.ProxyHTTPRateLimitError' (or 'Litellm_proxy.*' on
  fallback) — that's what dashboards consume.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* perf(proxy/hooks): defer provider resolution to over-limit branches

* fix: use error_message in raise_rate_limit_error to avoid literal 'None' in detail

* Consolidate rate_limiter_utils imports in dynamic_rate_limiter

* fix(proxy): set num_retries/max_retries on ProxyHTTPRateLimitError

ProxyHTTPRateLimitError inherits from RateLimitError but did not call
RateLimitError.__init__, so num_retries/max_retries were never set.
When Starlette's HTTPException lacks __str__, MRO falls through to
RateLimitError.__str__, which unconditionally reads these attributes
and raises AttributeError during logging/traceback formatting.
Initialize them to None defensively.

* fix(mypy): silence base-class status_code conflict on ProxyHTTPRateLimitError

HTTPException declares 'status_code: int' while openai.RateLimitError
(via APIStatusError) declares 'status_code: Literal[429] = 429'. Mypy
flags the multi-base override as [misc] in CI lint. The runtime semantics
are fine (we set self.status_code in __init__), so silence the
class-level annotation conflict with a targeted ignore.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
hbjydev pushed a commit to hbjydev/phoebe that referenced this pull request Jun 14, 2026
…9.0) (#93)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | minor | `v1.88.1` → `v1.89.0` |

---

### Release Notes

<details>
<summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary>

### [`v1.89.0`](https://github.com/BerriAI/litellm/releases/tag/v1.89.0)

[Compare Source](https://github.com/BerriAI/litellm/compare/v1.89.0...v1.89.0)

##### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.89.0
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.89.0/cosign.pub \
  ghcr.io/berriai/litellm:v1.89.0
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

##### What's Changed

- test(responses): bump deprecated gemini-3-pro-preview to gemini-3.1-pro-preview by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29433](https://github.com/BerriAI/litellm/pull/29433)
- fix: map mistral/ministral-8b-latest in model price map by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29453](https://github.com/BerriAI/litellm/pull/29453)
- fix(datadog): split oversized batches on 413 instead of re-queueing forever by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29444](https://github.com/BerriAI/litellm/pull/29444)
- feat(otel): allowlist team\_metadata sub-keys promoted to baggage by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29442](https://github.com/BerriAI/litellm/pull/29442)
- fix: stop use\_chat\_completions\_api flag from leaking into provider request body by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29447](https://github.com/BerriAI/litellm/pull/29447)
- fix(anthropic, fireworks): inline legacy $ref defs in tool schemas by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28646](https://github.com/BerriAI/litellm/pull/28646)
- fix(proxy): omit OpenAI \[DONE] on google-genai streamGenerateContent by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29426](https://github.com/BerriAI/litellm/pull/29426)
- ci(release): create stable/X.Y.x line branch on X.Y.0 tags by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29457](https://github.com/BerriAI/litellm/pull/29457)
- fix(vector-stores): support engines URL for Vertex AI Search by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;27885](https://github.com/BerriAI/litellm/pull/27885)
- fix(ui): render caller-supplied filter options in caller order by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29462](https://github.com/BerriAI/litellm/pull/29462)
- fix(batches): skip unnecessary batch input file reads by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29114](https://github.com/BerriAI/litellm/pull/29114)
- docs(agents): clarify when to create new test files by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29472](https://github.com/BerriAI/litellm/pull/29472)
- Litellm OSS Staging by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29161](https://github.com/BerriAI/litellm/pull/29161)
- fix(mcp): clear allowed\_tools and tool overrides on MCP server edit by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29411](https://github.com/BerriAI/litellm/pull/29411)
- Litellm OSS Staging 010626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29422](https://github.com/BerriAI/litellm/pull/29422)
- fix(ci): make CircleCI rerun-failed-tests collect tests when 2+ test files fail by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29475](https://github.com/BerriAI/litellm/pull/29475)
- feat(a2a): watsonx Orchestrate agent provider by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29410](https://github.com/BerriAI/litellm/pull/29410)
- fix(azure\_ai): strip tool-level extra fields on 400 and retry by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29479](https://github.com/BerriAI/litellm/pull/29479)
- fix(docs): remove fixed dimensions from README hero image by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29496](https://github.com/BerriAI/litellm/pull/29496)
- Litellm oss staging by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29492](https://github.com/BerriAI/litellm/pull/29492)
- fix: small CLAUDE.md nits by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29504](https://github.com/BerriAI/litellm/pull/29504)
- Add MCP semantic conventions to otelv2 by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29468](https://github.com/BerriAI/litellm/pull/29468)
- fix(passthrough): emit otel guardrail span when a guardrail blocks by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29470](https://github.com/BerriAI/litellm/pull/29470)
- fix(proxy): strip NUL bytes from spend log payloads to prevent PostgreSQL 22P05 by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29515](https://github.com/BerriAI/litellm/pull/29515)
- \[internal copy of [#&#8203;28008](https://github.com/BerriAI/litellm/issues/28008)] Support MCP OAuth passthrough and issuer-scoped JWT auth by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28356](https://github.com/BerriAI/litellm/pull/28356)
- feat(vector-stores): forward per-request params to Vertex AI Search by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29459](https://github.com/BerriAI/litellm/pull/29459)
- feat(proxy): add per-MCP-server RPM rate limiting for keys and teams by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29482](https://github.com/BerriAI/litellm/pull/29482)
- fix(tests): drop module-level test calls that break local\_testing collection by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29520](https://github.com/BerriAI/litellm/pull/29520)
- feat(agents): add LangFlow agent provider with A2A session bridging by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28963](https://github.com/BerriAI/litellm/pull/28963)
- fix(ui/agents): make A2A skill tags enterable and validated by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29512](https://github.com/BerriAI/litellm/pull/29512)
- \[internal copy of [#&#8203;29232](https://github.com/BerriAI/litellm/issues/29232)] feat: route future Claude models to Anthropic provider via pattern matching by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29239](https://github.com/BerriAI/litellm/pull/29239)
- fix(tests): drop import-time completion call in test\_register\_model by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29521](https://github.com/BerriAI/litellm/pull/29521)
- test: stabilize batch VCR coverage and stop live upload/network leaks by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29477](https://github.com/BerriAI/litellm/pull/29477)
- \[internal copy of [#&#8203;29003](https://github.com/BerriAI/litellm/issues/29003)] fix(vertex\_ai): use user-supplied api\_base as is for Model Garden OpenAI-compat path by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29530](https://github.com/BerriAI/litellm/pull/29530)
- feat(proxy): native /health/drain preStop hook for graceful shutdown by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29439](https://github.com/BerriAI/litellm/pull/29439)
- fix(auth): preserve 401 status for expired JWTs in OTel traces by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29510](https://github.com/BerriAI/litellm/pull/29510)
- fix(otel): capture 401 error details in management endpoint spans by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29535](https://github.com/BerriAI/litellm/pull/29535)
- test(proxy/utils): pin bottom-of-file helper behavior by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29509](https://github.com/BerriAI/litellm/pull/29509)
- test(proxy/utils): pin PrismaClient and spend-update behavior by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29488](https://github.com/BerriAI/litellm/pull/29488)
- test(proxy/utils): pin ProxyLogging behavior by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29485](https://github.com/BerriAI/litellm/pull/29485)
- fix: missing span for guardrail passthrough by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29552](https://github.com/BerriAI/litellm/pull/29552)
- fix(auth): let internal users view search tools by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29542](https://github.com/BerriAI/litellm/pull/29542)
- fix: missing mcp otel attributes by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29554](https://github.com/BerriAI/litellm/pull/29554)
- fix(proxy): resolve managed video model ids for auth by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;29545](https://github.com/BerriAI/litellm/pull/29545)
- fix(key\_generate): allow team members to create keys on org-scoped teams by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29310](https://github.com/BerriAI/litellm/pull/29310)
- test(pass-through): move Gemini pass-through tests to gemini-3.1-flash-lite by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29595](https://github.com/BerriAI/litellm/pull/29595)
- Litellm oss staging 030626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29578](https://github.com/BerriAI/litellm/pull/29578)
- Fix : a2a bugs 030626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29566](https://github.com/BerriAI/litellm/pull/29566)
- \[internal copy of [#&#8203;29533](https://github.com/BerriAI/litellm/issues/29533)] fix(anthropic/adapter): emit thinking block for reasoning\_content-only streaming chunks by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29600](https://github.com/BerriAI/litellm/pull/29600)
- ci: reproduce default-Windows wheel install to guard MAX\_PATH by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29597](https://github.com/BerriAI/litellm/pull/29597)
- fix(vertex): strip output\_config.effort for Vertex Claude models that reject it (Haiku 4.5) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29585](https://github.com/BerriAI/litellm/pull/29585)
- Litellm websocket improvements by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29563](https://github.com/BerriAI/litellm/pull/29563)
- feat(arize/phoenix): OpenInference rendering parity — tool\_calls, cost, passthrough I/O, session/user, multimodal, cache tokens by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28800](https://github.com/BerriAI/litellm/pull/28800)
- \[internal copy of [#&#8203;29550](https://github.com/BerriAI/litellm/issues/29550)] fix: passthrough endpoints duplicate logs by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29598](https://github.com/BerriAI/litellm/pull/29598)
- fix(ci): keep coverage rename green when a parallel node runs no tests by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29608](https://github.com/BerriAI/litellm/pull/29608)
- test(vcr): close out the remaining VCR live-call leaks by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29603](https://github.com/BerriAI/litellm/pull/29603)
- fix(key\_generate): exempt UI/CLI session tokens from the budget ceiling for team keys by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29612](https://github.com/BerriAI/litellm/pull/29612)
- fix(realtime): allow null transcripts in stream logging payloads by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29625](https://github.com/BerriAI/litellm/pull/29625)
- build(ui): migrate eslint to flat config + bump eslint-config-next to 16 by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29626](https://github.com/BerriAI/litellm/pull/29626)
- fix(key\_generate): scope session-token team-key budget exemption to caller-supplied team\_id by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29641](https://github.com/BerriAI/litellm/pull/29641)
- fix(proxy): disable proxy buffering on streaming SSE responses by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29557](https://github.com/BerriAI/litellm/pull/29557)
- fix(mcp): gate /public/mcp\_hub strictly on litellm.public\_mcp\_servers by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;27764](https://github.com/BerriAI/litellm/pull/27764)
- ci(ui): frontend-lint job enforcing prettier + eslint on changed files by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29633](https://github.com/BerriAI/litellm/pull/29633)
- fix(gemini): googleSearch + server-side tools and googleMaps JSON schema by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29582](https://github.com/BerriAI/litellm/pull/29582)
- fix(proxy): passthrough 404 when SERVER\_ROOT\_PATH is set by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29658](https://github.com/BerriAI/litellm/pull/29658)
- fix(gemini-realtime): use GA event names for Pipecat 1.3.x compatibility by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29662](https://github.com/BerriAI/litellm/pull/29662)
- Litellm oss staging 040626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29671](https://github.com/BerriAI/litellm/pull/29671)
- style(ui): prettier formatting pass over the dashboard by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29622](https://github.com/BerriAI/litellm/pull/29622)
- chore: ignore prettier dashboard reformat in git blame by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29695](https://github.com/BerriAI/litellm/pull/29695)
- fix(helm): Enable Backend Deployment to mount Gateway config.yaml by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29605](https://github.com/BerriAI/litellm/pull/29605)
- \[internal copy of [#&#8203;29277](https://github.com/BerriAI/litellm/issues/29277)] fix(proxy): add default=None to LiteLLM\_TeamMembership.litellm\_budget\_table by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29684](https://github.com/BerriAI/litellm/pull/29684)
- test: make custom\_tokenizer proxy tests hermetic by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29643](https://github.com/BerriAI/litellm/pull/29643)
- test(proxy): stop running real-DB tests in GitHub Actions unit jobs by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29700](https://github.com/BerriAI/litellm/pull/29700)
- chore(ui): remove the bare-fetch lint rule by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29712](https://github.com/BerriAI/litellm/pull/29712)
- Litellm jwt mapping virtualkeys by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;28510](https://github.com/BerriAI/litellm/pull/28510)
- refactor(ui): shared HTTP client + location-pinned fetch() lint rule by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29723](https://github.com/BerriAI/litellm/pull/29723)
- fix(proxy): stop team BYOK model name corruption on model edit by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29731](https://github.com/BerriAI/litellm/pull/29731)
- \[internal copy of [#&#8203;29511](https://github.com/BerriAI/litellm/issues/29511)] feat(guardrails): add sensitive data routing to on-premise models by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29531](https://github.com/BerriAI/litellm/pull/29531)
- fix(proxy/hooks): populate llm\_provider on internal rate-limit errors by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;27707](https://github.com/BerriAI/litellm/pull/27707)
- fix(vertex/anthropic): handle namespace tools and strip client\_metadata for codex compatibility by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29489](https://github.com/BerriAI/litellm/pull/29489)
- Support OAuth M2M for Databricks Apps A2A agents by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29586](https://github.com/BerriAI/litellm/pull/29586)
- fix: small CLAUDE.md nit by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29749](https://github.com/BerriAI/litellm/pull/29749)
- fix(anthropic): route Claude Opus 4.8 through adaptive thinking by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29702](https://github.com/BerriAI/litellm/pull/29702)
- fix(proxy): persist oauth2\_flow on MCP server registration by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;29690](https://github.com/BerriAI/litellm/pull/29690)
- \[internal copy of [#&#8203;27491](https://github.com/BerriAI/litellm/issues/27491)] fix(realtime): Fix Realtime Audio Token Cost Tracking by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29722](https://github.com/BerriAI/litellm/pull/29722)
- fix(galileo): use ingest traces API and standard logging payload by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29651](https://github.com/BerriAI/litellm/pull/29651)
- fix(auth): expand all-team-models sentinel in can\_key\_call\_model for batch validation by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29746](https://github.com/BerriAI/litellm/pull/29746)
- test(vcr): stop refreshing cassette TTL on read so cassettes lapse after 24h by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29784](https://github.com/BerriAI/litellm/pull/29784)
- test(ci): record/replay OpenAI image gen so the spend E2E isn't outage-bound by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29787](https://github.com/BerriAI/litellm/pull/29787)
- fix(ui): route MCP playground auth by oauth2 mode instead of token\_url by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29714](https://github.com/BerriAI/litellm/pull/29714)
- refactor(ui): centralize proxy base URL resolution into tested resolver by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29793](https://github.com/BerriAI/litellm/pull/29793)
- Litellm oss staging 050626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29774](https://github.com/BerriAI/litellm/pull/29774)
- test(google): add google-genai SDK proxy integration tests by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29781](https://github.com/BerriAI/litellm/pull/29781)
- fix(jwt): use resolved DB user\_id for spend on legacy email match by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29217](https://github.com/BerriAI/litellm/pull/29217)
- feat(ui): generate dashboard API types from the proxy OpenAPI spec by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29816](https://github.com/BerriAI/litellm/pull/29816)
- fix(proxy): drop deleted team BYOK model name from team.models by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29820](https://github.com/BerriAI/litellm/pull/29820)
- feat(mcp): per-server env vars with global + per-user scopes by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28917](https://github.com/BerriAI/litellm/pull/28917)
- refactor(ui): route behavior-preserving networking calls through apiClient by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29806](https://github.com/BerriAI/litellm/pull/29806)
- fix(mcp): persist Tools-tab MCP OAuth token to DB by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29809](https://github.com/BerriAI/litellm/pull/29809)
- fix(ui): require new expiration when regenerating an expired key by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29838](https://github.com/BerriAI/litellm/pull/29838)
- refactor(ui): route query-building networking calls through apiClient by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29815](https://github.com/BerriAI/litellm/pull/29815)
- Make the image-gen record/replay proxy report cache mode and per-request HIT/MISS by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29802](https://github.com/BerriAI/litellm/pull/29802)
- feat(proxy): hot-reload .env in dev when running with --reload by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29783](https://github.com/BerriAI/litellm/pull/29783)
- fix(ui): stop MCP playground tool calls from sending twice by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29821](https://github.com/BerriAI/litellm/pull/29821)
- feat(fal\_ai): add Nano Banana / Gemini 2.5 Flash Image generation support by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29798](https://github.com/BerriAI/litellm/pull/29798)
- Title: Fix managed batch cancel credential resolution by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;29734](https://github.com/BerriAI/litellm/pull/29734)
- Title: fix(proxy): resolve vector store file list credentials from team deployments by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;29739](https://github.com/BerriAI/litellm/pull/29739)
- refactor: convert AWS and GCP Terraform stacks into reusable modules … by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;28103](https://github.com/BerriAI/litellm/pull/28103)
- chore(ui): build ui for release by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29853](https://github.com/BerriAI/litellm/pull/29853)
- fix(terraform/gcp): prompt for image\_registry in DeployStack one-click by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29852](https://github.com/BerriAI/litellm/pull/29852)
- fix(terraform/gcp): abandon SQL user on destroy by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29855](https://github.com/BerriAI/litellm/pull/29855)
- Extend the record/replay proxy to chat, embeddings, moderations, rerank, and Anthropic by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29847](https://github.com/BerriAI/litellm/pull/29847)
- chore(deps): bump deps by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29860](https://github.com/BerriAI/litellm/pull/29860)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29861](https://github.com/BerriAI/litellm/pull/29861)
- fix: 400 on Anthropic context overflow; seed identity on failed auth by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29848](https://github.com/BerriAI/litellm/pull/29848)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29862](https://github.com/BerriAI/litellm/pull/29862)
- chore(release): patch v1.89.0-rc.1 with [#&#8203;30064](https://github.com/BerriAI/litellm/issues/30064) (Claude Fable 5) for v1.89.0-rc.2 by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30143](https://github.com/BerriAI/litellm/pull/30143)

**Full Changelog**: <https://github.com/BerriAI/litellm/compare/v1.88.0...v1.89.0>

### [`v1.89.0`](https://github.com/BerriAI/litellm/releases/tag/v1.89.0)

[Compare Source](https://github.com/BerriAI/litellm/compare/v1.88.2...v1.89.0)

##### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.89.0
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.89.0/cosign.pub \
  ghcr.io/berriai/litellm:v1.89.0
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

##### What's Changed

- test(responses): bump deprecated gemini-3-pro-preview to gemini-3.1-pro-preview by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29433](https://github.com/BerriAI/litellm/pull/29433)
- fix: map mistral/ministral-8b-latest in model price map by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29453](https://github.com/BerriAI/litellm/pull/29453)
- fix(datadog): split oversized batches on 413 instead of re-queueing forever by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29444](https://github.com/BerriAI/litellm/pull/29444)
- feat(otel): allowlist team\_metadata sub-keys promoted to baggage by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29442](https://github.com/BerriAI/litellm/pull/29442)
- fix: stop use\_chat\_completions\_api flag from leaking into provider request body by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29447](https://github.com/BerriAI/litellm/pull/29447)
- fix(anthropic, fireworks): inline legacy $ref defs in tool schemas by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28646](https://github.com/BerriAI/litellm/pull/28646)
- fix(proxy): omit OpenAI \[DONE] on google-genai streamGenerateContent by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29426](https://github.com/BerriAI/litellm/pull/29426)
- ci(release): create stable/X.Y.x line branch on X.Y.0 tags by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29457](https://github.com/BerriAI/litellm/pull/29457)
- fix(vector-stores): support engines URL for Vertex AI Search by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;27885](https://github.com/BerriAI/litellm/pull/27885)
- fix(ui): render caller-supplied filter options in caller order by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29462](https://github.com/BerriAI/litellm/pull/29462)
- fix(batches): skip unnecessary batch input file reads by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29114](https://github.com/BerriAI/litellm/pull/29114)
- docs(agents): clarify when to create new test files by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29472](https://github.com/BerriAI/litellm/pull/29472)
- Litellm OSS Staging by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29161](https://github.com/BerriAI/litellm/pull/29161)
- fix(mcp): clear allowed\_tools and tool overrides on MCP server edit by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29411](https://github.com/BerriAI/litellm/pull/29411)
- Litellm OSS Staging 010626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29422](https://github.com/BerriAI/litellm/pull/29422)
- fix(ci): make CircleCI rerun-failed-tests collect tests when 2+ test files fail by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29475](https://github.com/BerriAI/litellm/pull/29475)
- feat(a2a): watsonx Orchestrate agent provider by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29410](https://github.com/BerriAI/litellm/pull/29410)
- fix(azure\_ai): strip tool-level extra fields on 400 and retry by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29479](https://github.com/BerriAI/litellm/pull/29479)
- fix(docs): remove fixed dimensions from README hero image by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29496](https://github.com/BerriAI/litellm/pull/29496)
- Litellm oss staging by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29492](https://github.com/BerriAI/litellm/pull/29492)
- fix: small CLAUDE.md nits by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29504](https://github.com/BerriAI/litellm/pull/29504)
- Add MCP semantic conventions to otelv2 by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29468](https://github.com/BerriAI/litellm/pull/29468)
- fix(passthrough): emit otel guardrail span when a guardrail blocks by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29470](https://github.com/BerriAI/litellm/pull/29470)
- fix(proxy): strip NUL bytes from spend log payloads to prevent PostgreSQL 22P05 by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29515](https://github.com/BerriAI/litellm/pull/29515)
- \[internal copy of [#&#8203;28008](https://github.com/BerriAI/litellm/issues/28008)] Support MCP OAuth passthrough and issuer-scoped JWT auth by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28356](https://github.com/BerriAI/litellm/pull/28356)
- feat(vector-stores): forward per-request params to Vertex AI Search by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29459](https://github.com/BerriAI/litellm/pull/29459)
- feat(proxy): add per-MCP-server RPM rate limiting for keys and teams by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29482](https://github.com/BerriAI/litellm/pull/29482)
- fix(tests): drop module-level test calls that break local\_testing collection by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29520](https://github.com/BerriAI/litellm/pull/29520)
- feat(agents): add LangFlow agent provider with A2A session bridging by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28963](https://github.com/BerriAI/litellm/pull/28963)
- fix(ui/agents): make A2A skill tags enterable and validated by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29512](https://github.com/BerriAI/litellm/pull/29512)
- \[internal copy of [#&#8203;29232](https://github.com/BerriAI/litellm/issues/29232)] feat: route future Claude models to Anthropic provider via pattern matching by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29239](https://github.com/BerriAI/litellm/pull/29239)
- fix(tests): drop import-time completion call in test\_register\_model by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29521](https://github.com/BerriAI/litellm/pull/29521)
- test: stabilize batch VCR coverage and stop live upload/network leaks by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29477](https://github.com/BerriAI/litellm/pull/29477)
- \[internal copy of [#&#8203;29003](https://github.com/BerriAI/litellm/issues/29003)] fix(vertex\_ai): use user-supplied api\_base as is for Model Garden OpenAI-compat path by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29530](https://github.com/BerriAI/litellm/pull/29530)
- feat(proxy): native /health/drain preStop hook for graceful shutdown by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29439](https://github.com/BerriAI/litellm/pull/29439)
- fix(auth): preserve 401 status for expired JWTs in OTel traces by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29510](https://github.com/BerriAI/litellm/pull/29510)
- fix(otel): capture 401 error details in management endpoint spans by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29535](https://github.com/BerriAI/litellm/pull/29535)
- test(proxy/utils): pin bottom-of-file helper behavior by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29509](https://github.com/BerriAI/litellm/pull/29509)
- test(proxy/utils): pin PrismaClient and spend-update behavior by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29488](https://github.com/BerriAI/litellm/pull/29488)
- test(proxy/utils): pin ProxyLogging behavior by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29485](https://github.com/BerriAI/litellm/pull/29485)
- fix: missing span for guardrail passthrough by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29552](https://github.com/BerriAI/litellm/pull/29552)
- fix(auth): let internal users view search tools by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29542](https://github.com/BerriAI/litellm/pull/29542)
- fix: missing mcp otel attributes by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29554](https://github.com/BerriAI/litellm/pull/29554)
- fix(proxy): resolve managed video model ids for auth by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;29545](https://github.com/BerriAI/litellm/pull/29545)
- fix(key\_generate): allow team members to create keys on org-scoped teams by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29310](https://github.com/BerriAI/litellm/pull/29310)
- test(pass-through): move Gemini pass-through tests to gemini-3.1-flash-lite by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29595](https://github.com/BerriAI/litellm/pull/29595)
- Litellm oss staging 030626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29578](https://github.com/BerriAI/litellm/pull/29578)
- Fix : a2a bugs 030626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29566](https://github.com/BerriAI/litellm/pull/29566)
- \[internal copy of [#&#8203;29533](https://github.com/BerriAI/litellm/issues/29533)] fix(anthropic/adapter): emit thinking block for reasoning\_content-only streaming chunks by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29600](https://github.com/BerriAI/litellm/pull/29600)
- ci: reproduce default-Windows wheel install to guard MAX\_PATH by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29597](https://github.com/BerriAI/litellm/pull/29597)
- fix(vertex): strip output\_config.effort for Vertex Claude models that reject it (Haiku 4.5) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29585](https://github.com/BerriAI/litellm/pull/29585)
- Litellm websocket improvements by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29563](https://github.com/BerriAI/litellm/pull/29563)
- feat(arize/phoenix): OpenInference rendering parity — tool\_calls, cost, passthrough I/O, session/user, multimodal, cache tokens by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28800](https://github.com/BerriAI/litellm/pull/28800)
- \[internal copy of [#&#8203;29550](https://github.com/BerriAI/litellm/issues/29550)] fix: passthrough endpoints duplicate logs by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29598](https://github.com/BerriAI/litellm/pull/29598)
- fix(ci): keep coverage rename green when a parallel node runs no tests by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29608](https://github.com/BerriAI/litellm/pull/29608)
- test(vcr): close out the remaining VCR live-call leaks by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29603](https://github.com/BerriAI/litellm/pull/29603)
- fix(key\_generate): exempt UI/CLI session tokens from the budget ceiling for team keys by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29612](https://github.com/BerriAI/litellm/pull/29612)
- fix(realtime): allow null transcripts in stream logging payloads by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29625](https://github.com/BerriAI/litellm/pull/29625)
- build(ui): migrate eslint to flat config + bump eslint-config-next to 16 by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29626](https://github.com/BerriAI/litellm/pull/29626)
- fix(key\_generate): scope session-token team-key budget exemption to caller-supplied team\_id by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29641](https://github.com/BerriAI/litellm/pull/29641)
- fix(proxy): disable proxy buffering on streaming SSE responses by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29557](https://github.com/BerriAI/litellm/pull/29557)
- fix(mcp): gate /public/mcp\_hub strictly on litellm.public\_mcp\_servers by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;27764](https://github.com/BerriAI/litellm/pull/27764)
- ci(ui): frontend-lint job enforcing prettier + eslint on changed files by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29633](https://github.com/BerriAI/litellm/pull/29633)
- fix(gemini): googleSearch + server-side tools and googleMaps JSON schema by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29582](https://github.com/BerriAI/litellm/pull/29582)
- fix(proxy): passthrough 404 when SERVER\_ROOT\_PATH is set by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29658](https://github.com/BerriAI/litellm/pull/29658)
- fix(gemini-realtime): use GA event names for Pipecat 1.3.x compatibility by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29662](https://github.com/BerriAI/litellm/pull/29662)
- Litellm oss staging 040626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29671](https://github.com/BerriAI/litellm/pull/29671)
- style(ui): prettier formatting pass over the dashboard by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29622](https://github.com/BerriAI/litellm/pull/29622)
- chore: ignore prettier dashboard reformat in git blame by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29695](https://github.com/BerriAI/litellm/pull/29695)
- fix(helm): Enable Backend Deployment to mount Gateway config.yaml by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29605](https://github.com/BerriAI/litellm/pull/29605)
- \[internal copy of [#&#8203;29277](https://github.com/BerriAI/litellm/issues/29277)] fix(proxy): add default=None to LiteLLM\_TeamMembership.litellm\_budget\_table by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29684](https://github.com/BerriAI/litellm/pull/29684)
- test: make custom\_tokenizer proxy tests hermetic by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29643](https://github.com/BerriAI/litellm/pull/29643)
- test(proxy): stop running real-DB tests in GitHub Actions unit jobs by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29700](https://github.com/BerriAI/litellm/pull/29700)
- chore(ui): remove the bare-fetch lint rule by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29712](https://github.com/BerriAI/litellm/pull/29712)
- Litellm jwt mapping virtualkeys by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;28510](https://github.com/BerriAI/litellm/pull/28510)
- refactor(ui): shared HTTP client + location-pinned fetch() lint rule by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29723](https://github.com/BerriAI/litellm/pull/29723)
- fix(proxy): stop team BYOK model name corruption on model edit by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29731](https://github.com/BerriAI/litellm/pull/29731)
- \[internal copy of [#&#8203;29511](https://github.com/BerriAI/litellm/issues/29511)] feat(guardrails): add sensitive data routing to on-premise models by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29531](https://github.com/BerriAI/litellm/pull/29531)
- fix(proxy/hooks): populate llm\_provider on internal rate-limit errors by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;27707](https://github.com/BerriAI/litellm/pull/27707)
- fix(vertex/anthropic): handle namespace tools and strip client\_metadata for codex compatibility by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29489](https://github.com/BerriAI/litellm/pull/29489)
- Support OAuth M2M for Databricks Apps A2A agents by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29586](https://github.com/BerriAI/litellm/pull/29586)
- fix: small CLAUDE.md nit by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29749](https://github.com/BerriAI/litellm/pull/29749)
- fix(anthropic): route Claude Opus 4.8 through adaptive thinking by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29702](https://github.com/BerriAI/litellm/pull/29702)
- fix(proxy): persist oauth2\_flow on MCP server registration by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;29690](https://github.com/BerriAI/litellm/pull/29690)
- \[internal copy of [#&#8203;27491](https://github.com/BerriAI/litellm/issues/27491)] fix(realtime): Fix Realtime Audio Token Cost Tracking by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29722](https://github.com/BerriAI/litellm/pull/29722)
- fix(galileo): use ingest traces API and standard logging payload by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29651](https://github.com/BerriAI/litellm/pull/29651)
- fix(auth): expand all-team-models sentinel in can\_key\_call\_model for batch validation by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29746](https://github.com/BerriAI/litellm/pull/29746)
- test(vcr): stop refreshing cassette TTL on read so cassettes lapse after 24h by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29784](https://github.com/BerriAI/litellm/pull/29784)
- test(ci): record/replay OpenAI image gen so the spend E2E isn't outage-bound by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29787](https://github.com/BerriAI/litellm/pull/29787)
- fix(ui): route MCP playground auth by oauth2 mode instead of token\_url by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29714](https://github.com/BerriAI/litellm/pull/29714)
- refactor(ui): centralize proxy base URL resolution into tested resolver by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29793](https://github.com/BerriAI/litellm/pull/29793)
- Litellm oss staging 050626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29774](https://github.com/BerriAI/litellm/pull/29774)
- test(google): add google-genai SDK proxy integration tests by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29781](https://github.com/BerriAI/litellm/pull/29781)
- fix(jwt): use resolved DB user\_id for spend on legacy email match by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29217](https://github.com/BerriAI/litellm/pull/29217)
- feat(ui): generate dashboard API types from the proxy OpenAPI spec by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29816](https://github.com/BerriAI/litellm/pull/29816)
- fix(proxy): drop deleted team BYOK model name from team.models by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29820](https://github.com/BerriAI/litellm/pull/29820)
- feat(mcp): per-server env vars with global + per-user scopes by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28917](https://github.com/BerriAI/litellm/pull/28917)
- refactor(ui): route behavior-preserving networking calls through apiClient by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29806](https://github.com/BerriAI/litellm/pull/29806)
- fix(mcp): persist Tools-tab MCP OAuth token to DB by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29809](https://github.com/BerriAI/litellm/pull/29809)
- fix(ui): require new expiration when regenerating an expired key by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29838](https://github.com/BerriAI/litellm/pull/29838)
- refactor(ui): route query-building networking calls through apiClient by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29815](https://github.com/BerriAI/litellm/pull/29815)
- Make the image-gen record/replay proxy report cache mode and per-request HIT/MISS by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29802](https://github.com/BerriAI/litellm/pull/29802)
- feat(proxy): hot-reload .env in dev when running with --reload by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29783](https://github.com/BerriAI/litellm/pull/29783)
- fix(ui): stop MCP playground tool calls from sending twice by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29821](https://github.com/BerriAI/litellm/pull/29821)
- feat(fal\_ai): add Nano Banana / Gemini 2.5 Flash Image generation support by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29798](https://github.com/BerriAI/litellm/pull/29798)
- Title: Fix managed batch cancel credential resolution by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;29734](https://github.com/BerriAI/litellm/pull/29734)
- Title: fix(proxy): resolve vector store file list credentials from team deployments by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;29739](https://github.com/BerriAI/litellm/pull/29739)
- refactor: convert AWS and GCP Terraform stacks into reusable modules … by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;28103](https://github.com/BerriAI/litellm/pull/28103)
- chore(ui): build ui for release by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29853](https://github.com/BerriAI/litellm/pull/29853)
- fix(terraform/gcp): prompt for image\_registry in DeployStack one-click by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29852](https://github.com/BerriAI/litellm/pull/29852)
- fix(terraform/gcp): abandon SQL user on destroy by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29855](https://github.com/BerriAI/litellm/pull/29855)
- Extend the record/replay proxy to chat, embeddings, moderations, rerank, and Anthropic by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29847](https://github.com/BerriAI/litellm/pull/29847)
- chore(deps): bump deps by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29860](https://github.com/BerriAI/litellm/pull/29860)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29861](https://github.com/BerriAI/litellm/pull/29861)
- fix: 400 on Anthropic context overflow; seed identity on failed auth by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29848](https://github.com/BerriAI/litellm/pull/29848)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29862](https://github.com/BerriAI/litellm/pull/29862)
- chore(release): patch v1.89.0-rc.1 with [#&#8203;30064](https://github.com/BerriAI/litellm/issues/30064) (Claude Fable 5) for v1.89.0-rc.2 by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30143](https://github.com/BerriAI/litellm/pull/30143)

**Full Changelog**: <https://github.com/BerriAI/litellm/compare/v1.88.0...v1.89.0>

### [`v1.88.2`](https://github.com/BerriAI/litellm/releases/tag/v1.88.2)

[Compare Source](https://github.com/BerriAI/litellm/compare/v1.88.2...v1.88.2)

##### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.88.2
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.88.2/cosign.pub \
  ghcr.io/berriai/litellm:v1.88.2
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

##### What's Changed

- chore(release): backport Fable 5, batch-file auth, CrowdStrike AIDR, Mantle Responses SigV4, and NetApp streaming-cost fix to stable/1.88.x and cut 1.88.2 by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30144](https://github.com/BerriAI/litellm/pull/30144)
- chore(release): backport DB-resilience, passthrough, model-info, budget, and deps fixes to stable/1.88.x by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30408](https://github.com/BerriAI/litellm/pull/30408)

**Full Changelog**: <https://github.com/BerriAI/litellm/compare/v1.88.1...v1.88.2>

### [`v1.88.2`](https://github.com/BerriAI/litellm/releases/tag/v1.88.2)

[Compare Source](https://github.com/BerriAI/litellm/compare/v1.88.1...v1.88.2)

##### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.88.2
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.88.2/cosign.pub \
  ghcr.io/berriai/litellm:v1.88.2
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

##### What's Changed

- chore(release): backport Fable 5, batch-file auth, CrowdStrike AIDR, Mantle Responses SigV4, and NetApp streaming-cost fix to stable/1.88.x and cut 1.88.2 by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30144](https://github.com/BerriAI/litellm/pull/30144)
- chore(release): backport DB-resilience, passthrough, model-info, budget, and deps fixes to stable/1.88.x by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30408](https://github.com/BerriAI/litellm/pull/30408)

**Full Changelog**: <https://github.com/BerriAI/litellm/compare/v1.88.1...v1.88.2>

</details>

---

### Configuration

📅 **Schedule**: (in timezone Europe/London)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMTkuMCIsInVwZGF0ZWRJblZlciI6IjQzLjIxOS4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19-->

Reviewed-on: https://forgejo.hayden.moe/hayden/phoebe/pulls/93
blake-hamm added a commit to blake-hamm/bhamm-lab that referenced this pull request Jun 16, 2026
…to v1.89.0 (#200)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [https://github.com/BerriAI/litellm.git](https://github.com/BerriAI/litellm) | minor | `v1.85.1` → `v1.89.0` |

---

> ⚠️ **Warning**
>
> Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/155) for more information.

---

### Release Notes

<details>
<summary>BerriAI/litellm (https://github.com/BerriAI/litellm.git)</summary>

### [`v1.89.0`](https://github.com/BerriAI/litellm/releases/tag/v1.89.0)

[Compare Source](https://github.com/BerriAI/litellm/compare/v1.88.2...v1.89.0)

#### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.89.0
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.89.0/cosign.pub \
  ghcr.io/berriai/litellm:v1.89.0
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

#### What's Changed

- test(responses): bump deprecated gemini-3-pro-preview to gemini-3.1-pro-preview by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29433](https://github.com/BerriAI/litellm/pull/29433)
- fix: map mistral/ministral-8b-latest in model price map by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29453](https://github.com/BerriAI/litellm/pull/29453)
- fix(datadog): split oversized batches on 413 instead of re-queueing forever by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29444](https://github.com/BerriAI/litellm/pull/29444)
- feat(otel): allowlist team\_metadata sub-keys promoted to baggage by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29442](https://github.com/BerriAI/litellm/pull/29442)
- fix: stop use\_chat\_completions\_api flag from leaking into provider request body by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29447](https://github.com/BerriAI/litellm/pull/29447)
- fix(anthropic, fireworks): inline legacy $ref defs in tool schemas by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28646](https://github.com/BerriAI/litellm/pull/28646)
- fix(proxy): omit OpenAI \[DONE] on google-genai streamGenerateContent by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29426](https://github.com/BerriAI/litellm/pull/29426)
- ci(release): create stable/X.Y.x line branch on X.Y.0 tags by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29457](https://github.com/BerriAI/litellm/pull/29457)
- fix(vector-stores): support engines URL for Vertex AI Search by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;27885](https://github.com/BerriAI/litellm/pull/27885)
- fix(ui): render caller-supplied filter options in caller order by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29462](https://github.com/BerriAI/litellm/pull/29462)
- fix(batches): skip unnecessary batch input file reads by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29114](https://github.com/BerriAI/litellm/pull/29114)
- docs(agents): clarify when to create new test files by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29472](https://github.com/BerriAI/litellm/pull/29472)
- Litellm OSS Staging by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29161](https://github.com/BerriAI/litellm/pull/29161)
- fix(mcp): clear allowed\_tools and tool overrides on MCP server edit by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29411](https://github.com/BerriAI/litellm/pull/29411)
- Litellm OSS Staging 010626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29422](https://github.com/BerriAI/litellm/pull/29422)
- fix(ci): make CircleCI rerun-failed-tests collect tests when 2+ test files fail by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29475](https://github.com/BerriAI/litellm/pull/29475)
- feat(a2a): watsonx Orchestrate agent provider by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29410](https://github.com/BerriAI/litellm/pull/29410)
- fix(azure\_ai): strip tool-level extra fields on 400 and retry by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29479](https://github.com/BerriAI/litellm/pull/29479)
- fix(docs): remove fixed dimensions from README hero image by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29496](https://github.com/BerriAI/litellm/pull/29496)
- Litellm oss staging by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29492](https://github.com/BerriAI/litellm/pull/29492)
- fix: small CLAUDE.md nits by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29504](https://github.com/BerriAI/litellm/pull/29504)
- Add MCP semantic conventions to otelv2 by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29468](https://github.com/BerriAI/litellm/pull/29468)
- fix(passthrough): emit otel guardrail span when a guardrail blocks by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29470](https://github.com/BerriAI/litellm/pull/29470)
- fix(proxy): strip NUL bytes from spend log payloads to prevent PostgreSQL 22P05 by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29515](https://github.com/BerriAI/litellm/pull/29515)
- \[internal copy of [#&#8203;28008](https://github.com/BerriAI/litellm/issues/28008)] Support MCP OAuth passthrough and issuer-scoped JWT auth by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28356](https://github.com/BerriAI/litellm/pull/28356)
- feat(vector-stores): forward per-request params to Vertex AI Search by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29459](https://github.com/BerriAI/litellm/pull/29459)
- feat(proxy): add per-MCP-server RPM rate limiting for keys and teams by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29482](https://github.com/BerriAI/litellm/pull/29482)
- fix(tests): drop module-level test calls that break local\_testing collection by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29520](https://github.com/BerriAI/litellm/pull/29520)
- feat(agents): add LangFlow agent provider with A2A session bridging by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28963](https://github.com/BerriAI/litellm/pull/28963)
- fix(ui/agents): make A2A skill tags enterable and validated by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29512](https://github.com/BerriAI/litellm/pull/29512)
- \[internal copy of [#&#8203;29232](https://github.com/BerriAI/litellm/issues/29232)] feat: route future Claude models to Anthropic provider via pattern matching by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29239](https://github.com/BerriAI/litellm/pull/29239)
- fix(tests): drop import-time completion call in test\_register\_model by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29521](https://github.com/BerriAI/litellm/pull/29521)
- test: stabilize batch VCR coverage and stop live upload/network leaks by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29477](https://github.com/BerriAI/litellm/pull/29477)
- \[internal copy of [#&#8203;29003](https://github.com/BerriAI/litellm/issues/29003)] fix(vertex\_ai): use user-supplied api\_base as is for Model Garden OpenAI-compat path by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29530](https://github.com/BerriAI/litellm/pull/29530)
- feat(proxy): native /health/drain preStop hook for graceful shutdown by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29439](https://github.com/BerriAI/litellm/pull/29439)
- fix(auth): preserve 401 status for expired JWTs in OTel traces by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29510](https://github.com/BerriAI/litellm/pull/29510)
- fix(otel): capture 401 error details in management endpoint spans by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29535](https://github.com/BerriAI/litellm/pull/29535)
- test(proxy/utils): pin bottom-of-file helper behavior by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29509](https://github.com/BerriAI/litellm/pull/29509)
- test(proxy/utils): pin PrismaClient and spend-update behavior by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29488](https://github.com/BerriAI/litellm/pull/29488)
- test(proxy/utils): pin ProxyLogging behavior by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29485](https://github.com/BerriAI/litellm/pull/29485)
- fix: missing span for guardrail passthrough by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29552](https://github.com/BerriAI/litellm/pull/29552)
- fix(auth): let internal users view search tools by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29542](https://github.com/BerriAI/litellm/pull/29542)
- fix: missing mcp otel attributes by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29554](https://github.com/BerriAI/litellm/pull/29554)
- fix(proxy): resolve managed video model ids for auth by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;29545](https://github.com/BerriAI/litellm/pull/29545)
- fix(key\_generate): allow team members to create keys on org-scoped teams by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29310](https://github.com/BerriAI/litellm/pull/29310)
- test(pass-through): move Gemini pass-through tests to gemini-3.1-flash-lite by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29595](https://github.com/BerriAI/litellm/pull/29595)
- Litellm oss staging 030626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29578](https://github.com/BerriAI/litellm/pull/29578)
- Fix : a2a bugs 030626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29566](https://github.com/BerriAI/litellm/pull/29566)
- \[internal copy of [#&#8203;29533](https://github.com/BerriAI/litellm/issues/29533)] fix(anthropic/adapter): emit thinking block for reasoning\_content-only streaming chunks by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29600](https://github.com/BerriAI/litellm/pull/29600)
- ci: reproduce default-Windows wheel install to guard MAX\_PATH by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29597](https://github.com/BerriAI/litellm/pull/29597)
- fix(vertex): strip output\_config.effort for Vertex Claude models that reject it (Haiku 4.5) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29585](https://github.com/BerriAI/litellm/pull/29585)
- Litellm websocket improvements by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29563](https://github.com/BerriAI/litellm/pull/29563)
- feat(arize/phoenix): OpenInference rendering parity — tool\_calls, cost, passthrough I/O, session/user, multimodal, cache tokens by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28800](https://github.com/BerriAI/litellm/pull/28800)
- \[internal copy of [#&#8203;29550](https://github.com/BerriAI/litellm/issues/29550)] fix: passthrough endpoints duplicate logs by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29598](https://github.com/BerriAI/litellm/pull/29598)
- fix(ci): keep coverage rename green when a parallel node runs no tests by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29608](https://github.com/BerriAI/litellm/pull/29608)
- test(vcr): close out the remaining VCR live-call leaks by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29603](https://github.com/BerriAI/litellm/pull/29603)
- fix(key\_generate): exempt UI/CLI session tokens from the budget ceiling for team keys by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29612](https://github.com/BerriAI/litellm/pull/29612)
- fix(realtime): allow null transcripts in stream logging payloads by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29625](https://github.com/BerriAI/litellm/pull/29625)
- build(ui): migrate eslint to flat config + bump eslint-config-next to 16 by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29626](https://github.com/BerriAI/litellm/pull/29626)
- fix(key\_generate): scope session-token team-key budget exemption to caller-supplied team\_id by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29641](https://github.com/BerriAI/litellm/pull/29641)
- fix(proxy): disable proxy buffering on streaming SSE responses by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29557](https://github.com/BerriAI/litellm/pull/29557)
- fix(mcp): gate /public/mcp\_hub strictly on litellm.public\_mcp\_servers by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;27764](https://github.com/BerriAI/litellm/pull/27764)
- ci(ui): frontend-lint job enforcing prettier + eslint on changed files by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29633](https://github.com/BerriAI/litellm/pull/29633)
- fix(gemini): googleSearch + server-side tools and googleMaps JSON schema by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29582](https://github.com/BerriAI/litellm/pull/29582)
- fix(proxy): passthrough 404 when SERVER\_ROOT\_PATH is set by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29658](https://github.com/BerriAI/litellm/pull/29658)
- fix(gemini-realtime): use GA event names for Pipecat 1.3.x compatibility by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29662](https://github.com/BerriAI/litellm/pull/29662)
- Litellm oss staging 040626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29671](https://github.com/BerriAI/litellm/pull/29671)
- style(ui): prettier formatting pass over the dashboard by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29622](https://github.com/BerriAI/litellm/pull/29622)
- chore: ignore prettier dashboard reformat in git blame by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29695](https://github.com/BerriAI/litellm/pull/29695)
- fix(helm): Enable Backend Deployment to mount Gateway config.yaml by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29605](https://github.com/BerriAI/litellm/pull/29605)
- \[internal copy of [#&#8203;29277](https://github.com/BerriAI/litellm/issues/29277)] fix(proxy): add default=None to LiteLLM\_TeamMembership.litellm\_budget\_table by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29684](https://github.com/BerriAI/litellm/pull/29684)
- test: make custom\_tokenizer proxy tests hermetic by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29643](https://github.com/BerriAI/litellm/pull/29643)
- test(proxy): stop running real-DB tests in GitHub Actions unit jobs by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29700](https://github.com/BerriAI/litellm/pull/29700)
- chore(ui): remove the bare-fetch lint rule by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29712](https://github.com/BerriAI/litellm/pull/29712)
- Litellm jwt mapping virtualkeys by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;28510](https://github.com/BerriAI/litellm/pull/28510)
- refactor(ui): shared HTTP client + location-pinned fetch() lint rule by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29723](https://github.com/BerriAI/litellm/pull/29723)
- fix(proxy): stop team BYOK model name corruption on model edit by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29731](https://github.com/BerriAI/litellm/pull/29731)
- \[internal copy of [#&#8203;29511](https://github.com/BerriAI/litellm/issues/29511)] feat(guardrails): add sensitive data routing to on-premise models by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29531](https://github.com/BerriAI/litellm/pull/29531)
- fix(proxy/hooks): populate llm\_provider on internal rate-limit errors by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;27707](https://github.com/BerriAI/litellm/pull/27707)
- fix(vertex/anthropic): handle namespace tools and strip client\_metadata for codex compatibility by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29489](https://github.com/BerriAI/litellm/pull/29489)
- Support OAuth M2M for Databricks Apps A2A agents by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29586](https://github.com/BerriAI/litellm/pull/29586)
- fix: small CLAUDE.md nit by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29749](https://github.com/BerriAI/litellm/pull/29749)
- fix(anthropic): route Claude Opus 4.8 through adaptive thinking by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29702](https://github.com/BerriAI/litellm/pull/29702)
- fix(proxy): persist oauth2\_flow on MCP server registration by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;29690](https://github.com/BerriAI/litellm/pull/29690)
- \[internal copy of [#&#8203;27491](https://github.com/BerriAI/litellm/issues/27491)] fix(realtime): Fix Realtime Audio Token Cost Tracking by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29722](https://github.com/BerriAI/litellm/pull/29722)
- fix(galileo): use ingest traces API and standard logging payload by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29651](https://github.com/BerriAI/litellm/pull/29651)
- fix(auth): expand all-team-models sentinel in can\_key\_call\_model for batch validation by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29746](https://github.com/BerriAI/litellm/pull/29746)
- test(vcr): stop refreshing cassette TTL on read so cassettes lapse after 24h by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29784](https://github.com/BerriAI/litellm/pull/29784)
- test(ci): record/replay OpenAI image gen so the spend E2E isn't outage-bound by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29787](https://github.com/BerriAI/litellm/pull/29787)
- fix(ui): route MCP playground auth by oauth2 mode instead of token\_url by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29714](https://github.com/BerriAI/litellm/pull/29714)
- refactor(ui): centralize proxy base URL resolution into tested resolver by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29793](https://github.com/BerriAI/litellm/pull/29793)
- Litellm oss staging 050626 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29774](https://github.com/BerriAI/litellm/pull/29774)
- test(google): add google-genai SDK proxy integration tests by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29781](https://github.com/BerriAI/litellm/pull/29781)
- fix(jwt): use resolved DB user\_id for spend on legacy email match by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29217](https://github.com/BerriAI/litellm/pull/29217)
- feat(ui): generate dashboard API types from the proxy OpenAPI spec by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29816](https://github.com/BerriAI/litellm/pull/29816)
- fix(proxy): drop deleted team BYOK model name from team.models by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29820](https://github.com/BerriAI/litellm/pull/29820)
- feat(mcp): per-server env vars with global + per-user scopes by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28917](https://github.com/BerriAI/litellm/pull/28917)
- refactor(ui): route behavior-preserving networking calls through apiClient by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29806](https://github.com/BerriAI/litellm/pull/29806)
- fix(mcp): persist Tools-tab MCP OAuth token to DB by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29809](https://github.com/BerriAI/litellm/pull/29809)
- fix(ui): require new expiration when regenerating an expired key by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;29838](https://github.com/BerriAI/litellm/pull/29838)
- refactor(ui): route query-building networking calls through apiClient by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29815](https://github.com/BerriAI/litellm/pull/29815)
- Make the image-gen record/replay proxy report cache mode and per-request HIT/MISS by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29802](https://github.com/BerriAI/litellm/pull/29802)
- feat(proxy): hot-reload .env in dev when running with --reload by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29783](https://github.com/BerriAI/litellm/pull/29783)
- fix(ui): stop MCP playground tool calls from sending twice by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;29821](https://github.com/BerriAI/litellm/pull/29821)
- feat(fal\_ai): add Nano Banana / Gemini 2.5 Flash Image generation support by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29798](https://github.com/BerriAI/litellm/pull/29798)
- Title: Fix managed batch cancel credential resolution by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;29734](https://github.com/BerriAI/litellm/pull/29734)
- Title: fix(proxy): resolve vector store file list credentials from team deployments by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;29739](https://github.com/BerriAI/litellm/pull/29739)
- refactor: convert AWS and GCP Terraform stacks into reusable modules … by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;28103](https://github.com/BerriAI/litellm/pull/28103)
- chore(ui): build ui for release by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29853](https://github.com/BerriAI/litellm/pull/29853)
- fix(terraform/gcp): prompt for image\_registry in DeployStack one-click by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29852](https://github.com/BerriAI/litellm/pull/29852)
- fix(terraform/gcp): abandon SQL user on destroy by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29855](https://github.com/BerriAI/litellm/pull/29855)
- Extend the record/replay proxy to chat, embeddings, moderations, rerank, and Anthropic by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29847](https://github.com/BerriAI/litellm/pull/29847)
- chore(deps): bump deps by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29860](https://github.com/BerriAI/litellm/pull/29860)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29861](https://github.com/BerriAI/litellm/pull/29861)
- fix: 400 on Anthropic context overflow; seed identity on failed auth by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29848](https://github.com/BerriAI/litellm/pull/29848)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29862](https://github.com/BerriAI/litellm/pull/29862)
- chore(release): patch v1.89.0-rc.1 with [#&#8203;30064](https://github.com/BerriAI/litellm/issues/30064) (Claude Fable 5) for v1.89.0-rc.2 by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30143](https://github.com/BerriAI/litellm/pull/30143)

**Full Changelog**: <https://github.com/BerriAI/litellm/compare/v1.88.0...v1.89.0>

### [`v1.88.2`](https://github.com/BerriAI/litellm/releases/tag/v1.88.2)

[Compare Source](https://github.com/BerriAI/litellm/compare/v1.88.1...v1.88.2)

#### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.88.2
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.88.2/cosign.pub \
  ghcr.io/berriai/litellm:v1.88.2
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

#### What's Changed

- chore(release): backport Fable 5, batch-file auth, CrowdStrike AIDR, Mantle Responses SigV4, and NetApp streaming-cost fix to stable/1.88.x and cut 1.88.2 by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;30144](https://github.com/BerriAI/litellm/pull/30144)
- chore(release): backport DB-resilience, passthrough, model-info, budget, and deps fixes to stable/1.88.x by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;30408](https://github.com/BerriAI/litellm/pull/30408)

**Full Changelog**: <https://github.com/BerriAI/litellm/compare/v1.88.1...v1.88.2>

### [`v1.88.1`](https://github.com/BerriAI/litellm/releases/tag/v1.88.1)

[Compare Source](https://github.com/BerriAI/litellm/compare/v1.88.0...v1.88.1)

#### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.88.1
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.88.1/cosign.pub \
  ghcr.io/berriai/litellm:v1.88.1
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

#### What's Changed

- build(deps): bump pyjwt to 2.13.0 and ws override to 8.20.1 (1.88.x) by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29987](https://github.com/BerriAI/litellm/pull/29987)
- chore(release): bump version to 1.88.1 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29989](https://github.com/BerriAI/litellm/pull/29989)

**Full Changelog**: <https://github.com/BerriAI/litellm/compare/v1.88.0...v1.88.1>

### [`v1.88.0`](https://github.com/BerriAI/litellm/releases/tag/v1.88.0)

[Compare Source](https://github.com/BerriAI/litellm/compare/v1.87.3...v1.88.0)

#### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.88.0
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.88.0/cosign.pub \
  ghcr.io/berriai/litellm:v1.88.0
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

#### What's Changed

- fix(proxy): gate team allowed\_passthrough\_routes to proxy admins by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28097](https://github.com/BerriAI/litellm/pull/28097)
- fix(tests): stabilize image-edit VCR cassettes to stop live gpt-image-1 spend by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28110](https://github.com/BerriAI/litellm/pull/28110)
- fix(bedrock/cohere): send embedding\_types as JSON array, not string by [@&#8203;ishaan-berri](https://github.com/ishaan-berri) in [#&#8203;28172](https://github.com/BerriAI/litellm/pull/28172)
- fix(tests): migrate realtime + rerank tests off shut-down upstream models by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28191](https://github.com/BerriAI/litellm/pull/28191)
- fix(caching): replay openai/responses bridge cache hits as chat streams by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28158](https://github.com/BerriAI/litellm/pull/28158)
- Litellm oss staging by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28161](https://github.com/BerriAI/litellm/pull/28161)
- feat(prometheus): add user\_email and user\_alias to user budget metrics by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28155](https://github.com/BerriAI/litellm/pull/28155)
- test(callbacks): harden flaky proxy callback-leak detector by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28195](https://github.com/BerriAI/litellm/pull/28195)
- fix(bedrock): sanitize batch metadata to prevent Pydantic ValidationError by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28202](https://github.com/BerriAI/litellm/pull/28202)
- fix(deepseek): use native /anthropic/v1/messages endpoint and sanitize tools by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28200](https://github.com/BerriAI/litellm/pull/28200)
- feat(ui): add Interactions API endpoint to playground with SSE streaming by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28156](https://github.com/BerriAI/litellm/pull/28156)
- fix(proxy): decode bytes and pass-through SSE for Google-native streamGenerateContent ([#&#8203;27444](https://github.com/BerriAI/litellm/issues/27444)) by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28213](https://github.com/BerriAI/litellm/pull/28213)
- refactor(bedrock/sagemaker): switch to lazy loading for response stre… by [@&#8203;harish-berri](https://github.com/harish-berri) in [#&#8203;28189](https://github.com/BerriAI/litellm/pull/28189)
- \[Refactor] UI - Spend Logs: consolidate filter state and extract components by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;25847](https://github.com/BerriAI/litellm/pull/25847)
- fix(tests): replace shut-down gpt-4o-audio-preview with gpt-audio-1.5 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28281](https://github.com/BerriAI/litellm/pull/28281)
- chore(ci): bump versions by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28287](https://github.com/BerriAI/litellm/pull/28287)
- feat: propagate team\_id and team\_alias to all child OTEL spans by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;28273](https://github.com/BerriAI/litellm/pull/28273)
- Day 0 support : Gemini 3.5 Flash by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28268](https://github.com/BerriAI/litellm/pull/28268)
- Gemini managed agents support by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28270](https://github.com/BerriAI/litellm/pull/28270)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28292](https://github.com/BerriAI/litellm/pull/28292)
- feat(gemini): add gemini-3.1-flash-lite model cost map by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28320](https://github.com/BerriAI/litellm/pull/28320)
- fix(spend\_counter): seed Redis counter via SET NX to prevent cross-pod double-seed by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;27854](https://github.com/BerriAI/litellm/pull/27854)
- fix(proxy): normalize batch file IDs before ManagedObjectTable write by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28339](https://github.com/BerriAI/litellm/pull/28339)
- fix(router): use forwarded model\_id for native Azure container IDs by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;27921](https://github.com/BerriAI/litellm/pull/27921)
- fix(ui): restore log filter loading indicator by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28282](https://github.com/BerriAI/litellm/pull/28282)
- test(e2e): migrate runner to uv, add All Proxy Models key test by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28313](https://github.com/BerriAI/litellm/pull/28313)
- feat(ui): team passthrough routes create parity + edit load fix by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28098](https://github.com/BerriAI/litellm/pull/28098)
- fix(mcp): JWT on tools/list and REST tools/call server resolution by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28227](https://github.com/BerriAI/litellm/pull/28227)
- feat(interactions): migrate to Google Interactions API steps schema (May 2026) by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28153](https://github.com/BerriAI/litellm/pull/28153)
- test(ui-e2e): admin key creation with a specific proxy model by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28365](https://github.com/BerriAI/litellm/pull/28365)
- fix(vertex\_ai): omit function\_call id on Vertex Gemini 3.5+ tool turns by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28324](https://github.com/BerriAI/litellm/pull/28324)
- feat(mcp): allow native MCP OAuth support for cursor by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28327](https://github.com/BerriAI/litellm/pull/28327)
- fix(interactions): never drop streamed text deltas; always emit terminal completion by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28394](https://github.com/BerriAI/litellm/pull/28394)
- fix(proxy): expose Prisma idle/connect timeout + extra DB URL params by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;28395](https://github.com/BerriAI/litellm/pull/28395)
- Litellm oss staging 1 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28337](https://github.com/BerriAI/litellm/pull/28337)
- fix: serialize guardrail\_response to JSON in OTEL traces by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;28362](https://github.com/BerriAI/litellm/pull/28362)
- chore(ci): merge dev branch by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28314](https://github.com/BerriAI/litellm/pull/28314)
- test(realtime): expect session.created as xAI realtime initial event by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28424](https://github.com/BerriAI/litellm/pull/28424)
- feat(tests): behavior-pinning harness + Key Tier-1 matrix by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28321](https://github.com/BerriAI/litellm/pull/28321)
- fix(proxy): hydrate wildcard discovery credentials ([#&#8203;28284](https://github.com/BerriAI/litellm/issues/28284)) - CCI Run by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28419](https://github.com/BerriAI/litellm/pull/28419)
- Litellm oss staging 04 21 2026 2 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;26569](https://github.com/BerriAI/litellm/pull/26569)
- chore(ci): merge dev branch by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28290](https://github.com/BerriAI/litellm/pull/28290)
- fix(vertex\_gemma): strip `context_management` from request body by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28438](https://github.com/BerriAI/litellm/pull/28438)
- fix(logging): recalculate cost after router retry failures by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28476](https://github.com/BerriAI/litellm/pull/28476)
- fix(otel): emit guardrail span on violation, surface status + categories by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;28364](https://github.com/BerriAI/litellm/pull/28364)
- test(proxy): behavior-pinning matrix for team management endpoints by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28441](https://github.com/BerriAI/litellm/pull/28441)
- test(vertex\_ai): tolerate transient 500 in google maps grounding test by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28503](https://github.com/BerriAI/litellm/pull/28503)
- fix(docker): restore npm to non\_root builder image by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28519](https://github.com/BerriAI/litellm/pull/28519)
- chore(ci): bump deps by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28524](https://github.com/BerriAI/litellm/pull/28524)
- build(deps-dev): bump black to 26.3.1 and apply formatting by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28525](https://github.com/BerriAI/litellm/pull/28525)
- chore(deps): bump deps by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28528](https://github.com/BerriAI/litellm/pull/28528)
- test(e2e): forward LITELLM\_LICENSE to UI e2e proxy by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28398](https://github.com/BerriAI/litellm/pull/28398)
- Add granian as a ASGI compliant web server. Provider better throughput stability, by [@&#8203;harish-berri](https://github.com/harish-berri) in [#&#8203;26027](https://github.com/BerriAI/litellm/pull/26027)
- Fix conflicts and UI by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28477](https://github.com/BerriAI/litellm/pull/28477)
- Add error\_description and hint for oauth flows by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28471](https://github.com/BerriAI/litellm/pull/28471)
- feat(mcp): Add tool call and tool list support via UI for Oauth mcps by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28454](https://github.com/BerriAI/litellm/pull/28454)
- feat(proxy): persist allowlisted OIDC claims in CLI SSO poll by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28463](https://github.com/BerriAI/litellm/pull/28463)
- fix(responses): use OpenAI SSEDecoder for Responses API streaming by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28566](https://github.com/BerriAI/litellm/pull/28566)
- Litellm oss staging 2 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28582](https://github.com/BerriAI/litellm/pull/28582)
- \[internal copy of [#&#8203;28269](https://github.com/BerriAI/litellm/issues/28269)] Codex cli jwt team alias by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28621](https://github.com/BerriAI/litellm/pull/28621)
- fix(check\_licenses): read PEP 639 license-expression metadata by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28529](https://github.com/BerriAI/litellm/pull/28529)
- test(proxy): behavior-pinning matrix for tier-2/3 key + team management endpoints by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28620](https://github.com/BerriAI/litellm/pull/28620)
- chore(test): remove dead old Playwright e2e suite by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28632](https://github.com/BerriAI/litellm/pull/28632)
- fix(sagemaker): send native Cohere embed payload to Cohere SageMaker endpoints by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28613](https://github.com/BerriAI/litellm/pull/28613)
- style: apply black formatting to fix lint CI (LIT-3274) ([#&#8203;28639](https://github.com/BerriAI/litellm/issues/28639)) by [@&#8203;krrish-berri-2](https://github.com/krrish-berri-2) in [#&#8203;28641](https://github.com/BerriAI/litellm/pull/28641)
- fix(bedrock): decouple STS region from Bedrock aws\_region\_name by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28245](https://github.com/BerriAI/litellm/pull/28245)
- test(streaming): tolerate Vertex 429 wrapped in MidStreamFallbackError by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28669](https://github.com/BerriAI/litellm/pull/28669)
- feat(guardrails): add Microsoft Purview DLP guardrail by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;24966](https://github.com/BerriAI/litellm/pull/24966)
- fix(mcp): forward upstream initialize instructions on cold gateway init by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;28231](https://github.com/BerriAI/litellm/pull/28231)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28680](https://github.com/BerriAI/litellm/pull/28680)
- CI: copy of [#&#8203;25177](https://github.com/BerriAI/litellm/issues/25177) (OCI GenAI: embeddings, streaming/reasoning fixes, model catalog) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28223](https://github.com/BerriAI/litellm/pull/28223)
- Encrypt callback\_vars in key/team metadata in DB by [@&#8203;Michael-RZ-Berri](https://github.com/Michael-RZ-Berri) in [#&#8203;27141](https://github.com/BerriAI/litellm/pull/27141)
- perf: reduce per-request and per-chunk overhead across Anthropic streaming hot paths by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;28289](https://github.com/BerriAI/litellm/pull/28289)
- feat(azure): add Speech STT config support by [@&#8203;ishaan-berri](https://github.com/ishaan-berri) in [#&#8203;27482](https://github.com/BerriAI/litellm/pull/27482)
- test(proxy): phase-4 payload behavior pinning for tier-2/3 key + team management endpoints by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28681](https://github.com/BerriAI/litellm/pull/28681)
- feat(prometheus): emit per-token-type detail metrics (LIT-3220) ([#&#8203;28372](https://github.com/BerriAI/litellm/issues/28372)) by [@&#8203;ishaan-berri](https://github.com/ishaan-berri) in [#&#8203;28378](https://github.com/BerriAI/litellm/pull/28378)
- fix(otel): stamp http.response.status\_code on all error responses by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28405](https://github.com/BerriAI/litellm/pull/28405)
- chore(ui): build ui by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28707](https://github.com/BerriAI/litellm/pull/28707)
- fix(helm): drop main- prefix from default image tag by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28710](https://github.com/BerriAI/litellm/pull/28710)
- test(model\_prices): allow audio\_transcription\_config in schema by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28708](https://github.com/BerriAI/litellm/pull/28708)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28709](https://github.com/BerriAI/litellm/pull/28709)
- fix(team): refresh team cache on team\_model\_add/delete (LIT-3244) by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28683](https://github.com/BerriAI/litellm/pull/28683)
- fix(ui/add-model): stop vertex\_ai-anthropic\_models from leaking into Anthropic dropdown by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28723](https://github.com/BerriAI/litellm/pull/28723)
- Fix spend logs v2 route permissions by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28705](https://github.com/BerriAI/litellm/pull/28705)
- fix(proxy): Bedrock Knowledge Base pass-through: preserve SigV4 headers and signed request body by [@&#8203;milan-berri](https://github.com/milan-berri) in [#&#8203;27526](https://github.com/BerriAI/litellm/pull/27526)
- chore(tests): migrate Bedrock CI to AWS account [`9412775`](https://github.com/BerriAI/litellm/commit/941277531214) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28728](https://github.com/BerriAI/litellm/pull/28728)
- fix(otel): export SERVER span on management-endpoint success without http\_request by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;28794](https://github.com/BerriAI/litellm/pull/28794)
- chore(ci): merge dev branch by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28801](https://github.com/BerriAI/litellm/pull/28801)
- chore(ci): merge dev branch by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28657](https://github.com/BerriAI/litellm/pull/28657)
- fix(ui): show 2-decimal precision for max\_budget on key overview by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28809](https://github.com/BerriAI/litellm/pull/28809)
- feat(proxy): allow `llm_api_routes` virtual keys to list MCP servers by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28442](https://github.com/BerriAI/litellm/pull/28442)
- chore(ci): merge dev branch by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28807](https://github.com/BerriAI/litellm/pull/28807)
- fix(team): keep team\_alias cache in sync on \_cache\_team\_object writes by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28737](https://github.com/BerriAI/litellm/pull/28737)
- chore(ci): merge dev branch by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28822](https://github.com/BerriAI/litellm/pull/28822)
- ci: daily oss-agent-shin canonical branch by [@&#8203;ishaan-berri](https://github.com/ishaan-berri) in [#&#8203;28829](https://github.com/BerriAI/litellm/pull/28829)
- test(proxy): add harness for proxy\_server.py behavior-pinning by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28827](https://github.com/BerriAI/litellm/pull/28827)
- feat(openai): apply regional-processing cost uplift for EU/US data residency by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28626](https://github.com/BerriAI/litellm/pull/28626)
- chore(admin-ui): regenerate static export with trailingSlash: true by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28112](https://github.com/BerriAI/litellm/pull/28112)
- fix(azure): preserve AD token refresh in v1 OpenAI client path by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28627](https://github.com/BerriAI/litellm/pull/28627)
- fix(ui): route API Reference back to query-param page by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28726](https://github.com/BerriAI/litellm/pull/28726)
- fix(model-edit): allow clearing custom pricing on wildcard models by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28719](https://github.com/BerriAI/litellm/pull/28719)
- fix(tests/vcr): make Redis cassette cache replay deterministically (zero VCR misses on consecutive runs) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28826](https://github.com/BerriAI/litellm/pull/28826)
- fix(proxy): strip LiteLLM policy tracking from OpenAI batch metadata by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;28425](https://github.com/BerriAI/litellm/pull/28425)
- Litellm OpenAI double prefix bug by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;28661](https://github.com/BerriAI/litellm/pull/28661)
- Litellm oss staging 250526 by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28770](https://github.com/BerriAI/litellm/pull/28770)
- fix(bedrock): align toolUse/toolSpec names and allow hyphens by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28874](https://github.com/BerriAI/litellm/pull/28874)
- fix(realtime): send TEXT frames and valid guardrail session.update by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28848](https://github.com/BerriAI/litellm/pull/28848)
- fix(mcp): extend key access-group union to MCP servers by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28890](https://github.com/BerriAI/litellm/pull/28890)
- fix(galileo): support hosted v2 spans API and string output extraction by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28771](https://github.com/BerriAI/litellm/pull/28771)
- fix(proxy): exclude proxy\_server\_request from its own body snapshot by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;28618](https://github.com/BerriAI/litellm/pull/28618)
- \[Feat] Add tool calling support for gemini and vertex ai live api by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;26590](https://github.com/BerriAI/litellm/pull/26590)
- refactor(ui): remove dead App Router scaffolding in (dashboard)/\* by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28891](https://github.com/BerriAI/litellm/pull/28891)
- fix(docker): use system Node in componentized builders + retry apk add by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;28888](https://github.com/BerriAI/litellm/pull/28888)
- docs(agents): require consent before writing new third-party names by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;28908](https://github.com/BerriAI/litellm/pull/28908)
- refactor(ui): extract auth state into AuthContext by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28910](https://github.com/BerriAI/litellm/pull/28910)
- fix(mcp): resolve team.access\_group\_ids → MCP servers by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28997](https://github.com/BerriAI/litellm/pull/28997)
- test(ui): e2e cover team model edit + admin identity in navbar by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28652](https://github.com/BerriAI/litellm/pull/28652)
- test(e2e): cover add-fallback flow in Router Settings by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29069](https://github.com/BerriAI/litellm/pull/29069)
- test(e2e): cover Team-BYOK add-model flow as proxy admin by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29068](https://github.com/BerriAI/litellm/pull/29068)
- fix(containers): record ownership for service-account keys + fix Prisma Json serialization by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28990](https://github.com/BerriAI/litellm/pull/28990)
- test(e2e): cover add-MCP-server flow via discovery → custom form by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29070](https://github.com/BerriAI/litellm/pull/29070)
- test(e2e): cover AI Hub make-public flow and public model\_hub\_table by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29071](https://github.com/BerriAI/litellm/pull/29071)
- \[internal copy of [#&#8203;28877](https://github.com/BerriAI/litellm/issues/28877)] feat: add support for claude code goal mode for bedrock opus output config by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;28898](https://github.com/BerriAI/litellm/pull/28898)
- feat(guardrails): wire apply\_guardrail into proxy logging callbacks by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28970](https://github.com/BerriAI/litellm/pull/28970)
- chore(ci): merge dev brach by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29192](https://github.com/BerriAI/litellm/pull/29192)
- perf(streaming): cut per-chunk overhead \~30% on Anthropic + Bedrock hot path by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;28720](https://github.com/BerriAI/litellm/pull/28720)
- fix(proxy): enforce tag budgets for key-level tags by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29108](https://github.com/BerriAI/litellm/pull/29108)
- fix(vertex-ai): use DB credentials in video handlers + implement Veo video edit by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29098](https://github.com/BerriAI/litellm/pull/29098)
- fix(datadog): drain cost-management queue + opt-in FinOps tag allowlist by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;28487](https://github.com/BerriAI/litellm/pull/28487)
- feat(helm): split per-component ServiceAccounts for gateway, backend, and UI by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;28712](https://github.com/BerriAI/litellm/pull/28712)
- chore(ci): bump deps ([#&#8203;29208](https://github.com/BerriAI/litellm/issues/29208)) by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29226](https://github.com/BerriAI/litellm/pull/29226)
- fix(tests/vcr): mint Google OAuth tokens live to prevent stale-token replay by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29229](https://github.com/BerriAI/litellm/pull/29229)
- chore(cookbook): bump Go directive to 1.26.3 in gollem example by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29234](https://github.com/BerriAI/litellm/pull/29234)
- chore(ci): bump version by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29242](https://github.com/BerriAI/litellm/pull/29242)
- feat(anthropic): add Claude Opus 4.8 and prune reasoning-effort flags by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29238](https://github.com/BerriAI/litellm/pull/29238)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29243](https://github.com/BerriAI/litellm/pull/29243)
- fix(ci): restore real Bedrock batch S3 bucket/role in oai\_misc\_config by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29245](https://github.com/BerriAI/litellm/pull/29245)
- fix(guardrails): persist disable\_global\_guardrails on keys by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29233](https://github.com/BerriAI/litellm/pull/29233)
- test(e2e): cover Team Admin view + member + key flows by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29072](https://github.com/BerriAI/litellm/pull/29072)
- docs: hand-written CLAUDE.md; remove AGENTS.md, point GEMINI.md at it by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29252](https://github.com/BerriAI/litellm/pull/29252)
- fix(teams): expose keys\_count on /v2/team/list and wire UI Resources badge by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;28502](https://github.com/BerriAI/litellm/pull/28502)
- fix(anthropic): stop injecting unsupported output\_config.effort=xhigh for Claude Code on Sonnet/Opus 4.6 by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29304](https://github.com/BerriAI/litellm/pull/29304)
- test(e2e): cover Internal Viewer nav, key, and team-info gating by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29075](https://github.com/BerriAI/litellm/pull/29075)
- test(e2e): cover Internal User key modal, team info, key page by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29074](https://github.com/BerriAI/litellm/pull/29074)
- test(e2e): cover navbar Logout flow as proxy admin by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29076](https://github.com/BerriAI/litellm/pull/29076)
- fix(mcp): resolve key.access\_group\_ids → MCP servers (ungated) by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29195](https://github.com/BerriAI/litellm/pull/29195)
- fix(router): enforce deployment budgets for dynamically added models by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29273](https://github.com/BerriAI/litellm/pull/29273)
- fix(proxy): map stripped batch body.model to proxy alias for auth by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29264](https://github.com/BerriAI/litellm/pull/29264)
- feat(mcp): support stateless and stateful clients via session-id routing by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;26857](https://github.com/BerriAI/litellm/pull/26857)
- fix(bedrock): support tool search results + chat annotations by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29120](https://github.com/BerriAI/litellm/pull/29120)
- fix(mcp): ignore stale ids on key save by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29128](https://github.com/BerriAI/litellm/pull/29128)
- feat(a2a): well-known agent-card discovery + LangGraph Platform mode by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28860](https://github.com/BerriAI/litellm/pull/28860)
- fix(proxy): link passthrough success spans to the SERVER root OTEL span by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29315](https://github.com/BerriAI/litellm/pull/29315)
- \[internal copy of [#&#8203;29089](https://github.com/BerriAI/litellm/issues/29089)] fix: duplicate claude code traces by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29311](https://github.com/BerriAI/litellm/pull/29311)
- feat(otel): typed semconv-aligned OpenTelemetry instrumentation by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;28909](https://github.com/BerriAI/litellm/pull/28909)
- tests(proxy\_server): surface current behavior in tests by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29309](https://github.com/BerriAI/litellm/pull/29309)
- test(e2e): cover Internal User create-key flow when in no teams by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29083](https://github.com/BerriAI/litellm/pull/29083)
- test(e2e): assert internal-user navbar identity is scoped to that user by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29077](https://github.com/BerriAI/litellm/pull/29077)
- feat(otel): add team\_metadata, http.route, and model names to inference spans by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;29319](https://github.com/BerriAI/litellm/pull/29319)
- feat(context\_management): compact\_20260112 polyfill for non-Anthropic providers by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;28868](https://github.com/BerriAI/litellm/pull/28868)
- feat(enterprise): add RESEND\_FROM\_EMAIL for self-hosted Resend sends by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;28830](https://github.com/BerriAI/litellm/pull/28830)
- Revert Bedrock CI back to the reactivated AWS account ([`8886022`](https://github.com/BerriAI/litellm/commit/888602223428)) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29326](https://github.com/BerriAI/litellm/pull/29326)
- fix(mcp): preserve source\_url in GET /v1/mcp/server list responses by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;29249](https://github.com/BerriAI/litellm/pull/29249)
- fix(mcp): preserve omitted fields on PUT /v1/mcp/server partial updates by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;29253](https://github.com/BerriAI/litellm/pull/29253)
- fix(ci): make litellm\_internal\_staging green (logging test + Bedrock Opus 4.7 self-heal) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29344](https://github.com/BerriAI/litellm/pull/29344)
- refactor(proxy/auth): normalize Bearer prefix in safe-hash helper by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29343](https://github.com/BerriAI/litellm/pull/29343)
- test(reasoning-effort-grid): cover Claude Opus 4.8 across provider routes by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29327](https://github.com/BerriAI/litellm/pull/29327)
- fix(guardrails): return HTTP 400 for litellm content filter blocks by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;28418](https://github.com/BerriAI/litellm/pull/28418)
- fix(proxy): restrict vector store index create/delete to proxy admins by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;29202](https://github.com/BerriAI/litellm/pull/29202)
- feat(pass\_through): extend passthrough\_managed\_object\_ids to Azure by [@&#8203;Sameerlite](https://github.com/Sameerlite) in [#&#8203;29160](https://github.com/BerriAI/litellm/pull/29160)
- fix(proxy): enforce allowed\_passthrough\_routes for auth=true pass-thr… by [@&#8203;shivamrawat1](https://github.com/shivamrawat1) in [#&#8203;29256](https://github.com/BerriAI/litellm/pull/29256)
- feat(mcp/auth): additive key access-group grants + opt-in member assignment by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29313](https://github.com/BerriAI/litellm/pull/29313)
- fix(reset\_budget): write only {spend, budget\_reset\_at} and stop pre-zeroing counter by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29358](https://github.com/BerriAI/litellm/pull/29358)
- test(e2e): cover PROXY\_LOGOUT\_URL redirect on Logout by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;29080](https://github.com/BerriAI/litellm/pull/29080)
- fix(ui): break logout redirect loop across dev and proxy origins by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29360](https://github.com/BerriAI/litellm/pull/29360)
- fix(openai-moderation): wire streaming flags through to unified dispatcher by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;27324](https://github.com/BerriAI/litellm/pull/27324)
- chore(ci): build ui by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29366](https://github.com/BerriAI/litellm/pull/29366)
- fix(v3 limiter): cap no-max\_tokens TPM floor at smallest configured limit by [@&#8203;michelligabriele](https://github.com/michelligabriele) in [#&#8203;28805](https://github.com/BerriAI/litellm/pull/28805)
- fix(e2e): tolerate trailing slash in SERVER\_ROOT\_PATH login redirect by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29369](https://github.com/BerriAI/litellm/pull/29369)
- chore(deps): bump deps by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29373](https://github.com/BerriAI/litellm/pull/29373)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;29372](https://github.com/BerriAI/litellm/pull/29372)
- chore(release): patch v1.88.0-rc.1 with four staged fixes by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29632](https://github.com/BerriAI/litellm/pull/29632)
- chore(release): patch v1.88.0-rc.1 with [#&#8203;29612](https://github.com/BerriAI/litellm/issues/29612) (session-token budget-ceiling exemption) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;29637](https://github.com/BerriAI/litellm/pull/29637)
- fix(key\_generate): harden GHSA-q775 …
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…BerriAI#27707)

* feat(proxy/hooks): add ProxyHTTPRateLimitError + provider resolver

Introduces a small helper layer used by every proxy-side rate-limit
hook so that the 429 they raise carries a populated llm_provider /
model — instead of an empty exception.llm_provider that downstream
loggers (Prometheus failure metric, observability callbacks) read as
'no provider attribution'.

ProxyHTTPRateLimitError inherits from both fastapi.HTTPException
(so the proxy server still renders it as a 429) and
litellm.exceptions.RateLimitError (so isinstance checks and
PrometheusLogger._get_exception_class_name pick up llm_provider).
We deliberately don't call RateLimitError.__init__ — it constructs
an httpx.Response we don't need and would just add failure surface;
attribute parity is what downstream consumers care about.

resolve_llm_provider_for_rate_limit() wraps litellm.get_llm_provider
defensively. Internal limiter hooks fire from async_pre_call_hook —
well before get_llm_provider runs anywhere else in the request
lifecycle — so we have to call it ourselves at raise time. If the
model is missing or unparseable (alias, router-only model) we fall
back to llm_provider='litellm_proxy' rather than letting a second
exception leak out and break the request path.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* fix(proxy/hooks): populate llm_provider on parallel-request 429s

Both v1 and v3 parallel-request limiters fired bare HTTPException(429)
from inside async_pre_call_hook. The downstream Prometheus failure
metric reads exception.llm_provider via _get_exception_class_name —
the empty value showed up as exception_class='HTTPException' and
left model_id='None' on the time series.

Threads requested_model through every raise site in:

* parallel_request_limiter.py:
  - check_key_in_limits (the per-key/per-model/per-user/per-team/
    per-customer over-limit path)
  - raise_rate_limit_error (zero-limit + global_max_parallel_requests
    paths) — now takes an optional requested_model kwarg
* parallel_request_limiter_v3.py:
  - _handle_rate_limit_error (the OVER_LIMIT translator), called
    from both the should_rate_limit pre-check and the TPM
    reservation path

Resolved via resolve_llm_provider_for_rate_limit so unknown / missing
models silently fall back to llm_provider='litellm_proxy' instead of
breaking the request path with a second exception.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* fix(proxy/hooks): populate llm_provider on dynamic-rate-limit 429s

Same plumbing change as the parallel limiters, applied to both
dynamic_rate_limiter (v1) and dynamic_rate_limiter_v3:

* v1: TPM-zero and RPM-zero paths in async_pre_call_hook now resolve
  data['model'] -> (model, llm_provider) once and pass it into both
  raises.
* v3: All three raise sites in _check_rate_limits — the
  model_saturation_check enforced raise, the priority_model
  enforced raise, and the fail-closed unknown-descriptor branch —
  now attribute the 429 to the actual provider.

Falls back to llm_provider='litellm_proxy' when the model can't be
resolved.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* fix(proxy/hooks): populate llm_provider on batch-rate-limit 429s

batch_rate_limiter._raise_rate_limit_error now takes a
requested_model kwarg threaded from data['model'] in
_check_and_increment_batch_counters. The batch-creation 429 is what
gets raised when the input file's tokens/requests count would push
the per-key TPM/RPM window over its limit.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* fix(proxy/hooks): populate llm_provider on budget/iterations 429s

Final batch of internal raise sites — the user/session-budget and
max-iterations hooks. Same pattern: resolve data['model'] once at
raise time, attach to ProxyHTTPRateLimitError so Prometheus and
observability callbacks can attribute the 429.

Hooks updated:
* max_budget_limiter (per-user max_budget exceeded)
* max_iterations_limiter (per-session agent iteration cap)
* max_budget_per_session_limiter (per-session dollar cap)

All three fall back to llm_provider='litellm_proxy' when data['model']
is missing or unparseable. Drops the now-unused HTTPException import
from each module.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* test(proxy/hooks): pin provider field on internal rate-limit 429s

Regression coverage for the 'provider field missing' bug across every
proxy-side rate-limit hook + the helper layer:

* ProxyHTTPRateLimitError class shape (HTTPException + RateLimitError,
  dict-detail stringification, None-provider normalization).
* resolve_llm_provider_for_rate_limit happy paths
  (gpt-4o-mini, anthropic/..., bedrock/...) plus all three fallback
  branches (None, '', unknown name) plus a 'get_llm_provider raises'
  case that asserts we swallow the secondary exception.
* For each limiter (parallel v1/v3, dynamic v1/v3, batch,
  max_budget, max_iterations, max_budget_per_session): assert the
  raised exception is a RateLimitError carrying the resolved
  model + llm_provider, and a sibling test that asserts the
  fallback path returns 'litellm_proxy' without leaking a second
  exception.
* Two PrometheusLogger._get_exception_class_name pins so the
  Prometheus failure metric label flips from 'HTTPException' to
  'Openai.ProxyHTTPRateLimitError' (or 'Litellm_proxy.*' on
  fallback) — that's what dashboards consume.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

* perf(proxy/hooks): defer provider resolution to over-limit branches

* fix: use error_message in raise_rate_limit_error to avoid literal 'None' in detail

* Consolidate rate_limiter_utils imports in dynamic_rate_limiter

* fix(proxy): set num_retries/max_retries on ProxyHTTPRateLimitError

ProxyHTTPRateLimitError inherits from RateLimitError but did not call
RateLimitError.__init__, so num_retries/max_retries were never set.
When Starlette's HTTPException lacks __str__, MRO falls through to
RateLimitError.__str__, which unconditionally reads these attributes
and raises AttributeError during logging/traceback formatting.
Initialize them to None defensively.

* fix(mypy): silence base-class status_code conflict on ProxyHTTPRateLimitError

HTTPException declares 'status_code: int' while openai.RateLimitError
(via APIStatusError) declares 'status_code: Literal[429] = 429'. Mypy
flags the multi-base override as [misc] in CI lint. The runtime semantics
are fine (we set self.status_code in __init__), so silence the
class-level annotation conflict with a targeted ignore.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants