fix(proxy): honor key-level disable_fallbacks after first pre-call pass
Some checks failed
ai-gateway image / ai-gateway release image (push) Has been cancelled
LiteLLM Rust / rust-lint (push) Has been cancelled
LiteLLM Rust / rust-test (push) Has been cancelled
Terraform Modules / fmt, validate, test (aws) (push) Has been cancelled
Terraform Modules / fmt, validate, test (gcp) (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled

Key metadata disable_fallbacks only lands on data during add_key_level_controls,
so the local rate-limit fallback retry now rechecks it post pre-call. Also use a
real UserAPIKeyAuth in the skip pre-call test since the path reads router_settings

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng 2026-09-16 08:17:58 +00:00
parent 391da46e2c
commit 9974cf4bf8
3 changed files with 35 additions and 4 deletions

View file

@ -2093,7 +2093,12 @@ class ProxyBaseLLMRequestProcessing:
except ProxyRateLimitError as original_exc:
rate_limited_data: Final = self.data
original_model: Final = rate_limited_data.get("model")
if pristine is None or not configured_fallbacks or not isinstance(original_model, str):
if (
pristine is None
or not configured_fallbacks
or rate_limited_data.get("disable_fallbacks")
or not isinstance(original_model, str)
):
raise
fallback_models: Final = self._resolve_fallback_models(

View file

@ -87,7 +87,7 @@ class TestSkipPreCallLogic:
await processor.base_process_llm_request(
request=MagicMock(spec=Request),
fastapi_response=MagicMock(spec=Response),
user_api_key_dict=MagicMock(spec=UserAPIKeyAuth),
user_api_key_dict=UserAPIKeyAuth(),
route_type="aresponses",
proxy_logging_obj=mock_proxy_logging,
llm_router=MagicMock(),

View file

@ -6410,7 +6410,9 @@ class TestPreCallWithFallbacksOnLocalRateLimit:
@staticmethod
def _otel_key(
rpm_limit: int | None = None, model_rpm_limit: dict[str, int] | None = None
rpm_limit: int | None = None,
model_rpm_limit: dict[str, int] | None = None,
disable_fallbacks: bool = False,
) -> ProxyUserAPIKeyAuth:
from opentelemetry.sdk.trace import TracerProvider
@ -6419,7 +6421,10 @@ class TestPreCallWithFallbacksOnLocalRateLimit:
api_key="hashed-key",
parent_otel_span=span,
rpm_limit=rpm_limit,
metadata={"model_rpm_limit": model_rpm_limit} if model_rpm_limit else {},
metadata={
**({"model_rpm_limit": model_rpm_limit} if model_rpm_limit else {}),
**({"disable_fallbacks": True} if disable_fallbacks else {}),
},
)
@staticmethod
@ -6540,6 +6545,27 @@ class TestPreCallWithFallbacksOnLocalRateLimit:
assert data["model"] == fallback_model
assert rig[3] == [primary_model, primary_model, fallback_model]
@pytest.mark.asyncio
async def test_key_metadata_disable_fallbacks_returns_429_instead_of_retrying(
self, monkeypatch: pytest.MonkeyPatch
):
"""``disable_fallbacks`` set in key metadata only lands on ``data`` during the first
pre-call pass (``add_key_level_controls``), so it must be honored after that pass."""
from litellm.proxy.common_utils.proxy_rate_limit_error import ProxyRateLimitError
primary_model = "gpt-4.1"
fallback_model = "gpt-4.1-mini"
key = self._otel_key(model_rpm_limit={primary_model: 1}, disable_fallbacks=True)
rig = self._v3_limiter_rig(monkeypatch, key, [{primary_model: [fallback_model]}])
request = {"model": primary_model, "messages": [{"role": "user", "content": "hi"}]}
await self._pre_call(dict(request), key, rig)
with pytest.raises(ProxyRateLimitError) as exc_info:
await self._pre_call(dict(request), key, rig)
assert exc_info.value.status_code == 429
assert rig[3] == [primary_model, primary_model]
class _RecordingSuccessLogger(CustomLogger):
def __init__(self):