litellm/tests
Deepanshu Lulla 0580465384
feat(router): add per-deployment allowed_fails_policy and cooldown_time override support (#34416)
* feat(router): add per-deployment allowed_fails_policy and cooldown_time override support

Three bugs fixed in the router cooldown system: (1) deployment-level allowed_fails and
allowed_fails_policy in model_info now take precedence over router-level settings in
_should_cooldown_deployment; (2) failed fallback deployments now get evaluated for
cooldown via _trigger_cooldown_for_failed_deployment, bypassing the Logging dedup gate;
(3) DualCache promotes Redis cooldown entries using default 600s TTL instead of true
remaining cooldown time -- _corrected_active_cooldown now evicts expired entries and
corrects stale in-memory TTLs on backfill. Adds ServiceUnavailableError, BadGatewayError,
and NotFoundError fields to AllowedFailsPolicy and cooldown_time to LiteLLMParamsTypedDict.

* fix(router): gate fallback cooldown trigger on has_logged_async_failure; use only litellm_metadata for deployment ID

* fix(router): use X | Y union syntax to fix UP007 strict lint gate

* test(router_utils): add coverage for _trigger_cooldown_for_failed_deployment and has_logged_async_failure gate

* test(router_utils): cover deployment cooldown override and exception swallow paths

* fix(router): add InternalServerError/ServiceUnavailableError/BadGatewayError/NotFoundError to router-level get_allowed_fails_from_policy

* fix(router): format router.py and add router-level policy tests

* test(router): add CI-visible coverage for per-deployment cooldown policy

Tests for `_get_deployment_cooldown_policy`, `_resolve_allowed_fails_from_policy`,
and `_should_cooldown_based_on_deployment_policy` (cooldown_handlers.py), the
`_corrected_active_cooldown` branches in CooldownCache, and the four new
exception-type branches in `Router.get_allowed_fails_from_policy` (router.py) --
all in `tests/test_litellm/` which the enterprise-routing CI job runs.

* fix(router): use is not None guard for cooldown_time_override in should_cooldown_based_on_allowed_fails_policy

A cooldown_time_override of 0 was previously treated as falsy and silently
fell through to the router-level cooldown_time value. Switched to an explicit
is not None check so that zero is honored as a valid override.

Added a regression test covering the zero case.

* fix(router): honor has_logged_async_failure and metadata for fallback cooldown; support both model_info and litellm_params locations

Manual verification against a live proxy surfaced that the fallback-cooldown-gap
trigger never actually fired: the has_logged_async_failure check read a plain
attribute that Logging never sets (the real flag lives in model_call_details),
and the deployment_id lookup only trusted litellm_metadata, which regular chat
completions never populate (only batch/thread/file endpoints do). Router
overwrites model_info on whichever key is present before every attempt, so
metadata is equally authoritative there, not caller-controlled as previously
assumed. Also let allowed_fails/allowed_fails_policy/cooldown_time be set under
either model_info or litellm_params, each preferring its own canonical location.

* fix(router): fix ContentPolicyViolationError policy shadowing and partial-policy zero-threshold

Two bugs from Greptile review on PR #34416:

- ContentPolicyViolationError subclasses BadRequestError, so listing
  BadRequestError first in _EXCEPTION_POLICY_FIELDS made the isinstance
  check always match BadRequestError for content-policy errors, using the
  wrong allowed_fails threshold. Reordered so the subclass is checked first.

- A deployment with a partial allowed_fails_policy and no deployment-wide
  allowed_fails forced allowed_fails_override=0 for any exception type its
  policy didn't cover, cooling the deployment down on the first unrelated
  failure. Now defers to router-level behavior for uncovered exception
  types instead of forcing an immediate cooldown.

* fix(router): only trust a metadata/litellm_metadata bucket the router itself wrote deployment info into

veria-ai flagged that preferring litellm_metadata whenever present could pick up a
caller-supplied litellm_metadata.model_info.id (preserved via allow_client_pricing_override)
instead of the metadata bucket the router actually populated for a regular completion's
fallback attempt, naming an arbitrary "victim" deployment for cooldown.

Router._update_kwargs_with_deployment() always writes model_info and
deployment_model_name into the same bucket together. Only trust a bucket that
carries deployment_model_name alongside model_info, since that marker is only
ever set by the router itself, not by request-body metadata.

* test(router): add regression coverage for ContentPolicyViolationError policy shadowing

The subclass-ordering fix in commit 38fe4e4490 had no regression test.
Verified the new test fails on the pre-fix ordering (asserts 2, got 10)
before restoring the fix, and confirmed the same behavior through the full
_should_cooldown_deployment call path against a real Router instance.

* fix(router): let explicit allowed_fails_policy entries override the generic 4XX cooldown exclusion

_is_cooldown_required skips cooldown evaluation for any 4XX status outside
{429, 401, 408, 404} by default, since a generic client error is usually not
the deployment's fault. BadRequestError and ContentPolicyViolationError both
carry status 400, so their AllowedFailsPolicy fields (BadRequestErrorAllowedFails,
ContentPolicyViolationErrorAllowedFails, both router-level pre-existing and the
new deployment-level ones) were silently unreachable: an operator could set
them to any value with no effect, since _is_cooldown_required blocked cooldown
evaluation before that policy was ever consulted.

_should_run_cooldown_logic now also checks whether an explicit allowed_fails_policy
entry (deployment-level or router-level) covers the exception's type, and if so,
proceeds with cooldown evaluation regardless of the generic status-code exclusion.
The exclusion remains the default for exception types with no explicit policy.

Verified live against a mock-triggered ContentPolicyViolationError (config-level
mock_response, azure/gpt-4.1-mini deployment) with BadRequestErrorAllowedFails=100
and ContentPolicyViolationErrorAllowedFails=0 on the same deployment: it now cools
down after exactly one ContentPolicyViolationError instead of never cooling down.

* fix(router): use the router-stamped failed_deployment_id for fallback cooldown targeting

Greptile flagged a real gap in the metadata-bucket-based deployment lookup:
for a generic-API-call fallback, the router writes the current attempt into
litellm_metadata, but a stale "metadata" bucket carrying the same
deployment_model_name marker (from an earlier point) would be picked first,
cooling the wrong deployment.

Router already has a more robust, pre-existing mechanism for this exact
problem: _set_failed_deployment_id_on_exception stamps the failing
deployment's id directly onto the exception at the point of failure,
immune to metadata-bucket ambiguity since a caller can't influence it and
it doesn't depend on which bucket the current call type happens to use.
It just wasn't called from _ageneric_api_call_with_fallbacks_helper's
except block, unlike _completion/_acompletion.

Added the missing call there (matching the existing pattern exactly), and
changed _trigger_cooldown_for_failed_deployment to prefer
exception.failed_deployment_id when present, falling back to metadata-bucket
inspection only for call paths that don't stamp it yet.

Verified live: the standard fallback-cooldown-gap scenario (two bad-key
deployments in a fallback chain) still correctly cools down both the
originally-called and fallback deployment.

* fix(router): address human review on per-deployment cooldown overrides

Scope allowed_fails_policy override to deployment-level only (a router-level
policy predates this feature and must keep its existing behavior), exempt
advisor-orchestration failures from the fallback cooldown trigger, keep the
single-deployment model group protection intact against a generic
deployment-level allowed_fails, make cooldown_time precedence consistent
across resolution paths, fix a falsy-zero swallowing bug in the router-level
allowed_fails fallback, and make allowed_fails_policy resolution fall through
to the next matching exception type instead of stopping at the first unset
field.

Also restrict allowed_fails/allowed_fails_policy/cooldown_time to model_info:
litellm_params gets copied into the actual provider request, so a router-only
setting placed there would leak into that request.

* test(router): update test_cooldown_handlers.py for the deployment-policy signature change

Surfaced by the rebase: this mirrored test file (tests/test_litellm/ mirrors
litellm/) predates the router_unit_tests/ coverage added earlier in this PR and
was still calling _should_cooldown_based_on_deployment_policy with its old
4-argument signature and asserting the now-removed litellm_params cooldown_time
location.

* test(router): update test_fallback_event_handlers.py for model_info-only cooldown_time

Another mirrored test file surfaced by the rebase that still asserted the
now-removed litellm_params.cooldown_time location.

* fix(router): match cooldown-duration precedence in the fallback path to the primary path

_trigger_cooldown_for_failed_deployment only checked deployment config before
falling back to the router default, skipping the response Retry-After header
step that Router.deployment_callback_on_failure applies on the primary path.

* fix(router): restore litellm_params.cooldown_time as a pre-existing fallback

cooldown_time already had litellm_params support on Router.deployment_callback_on_failure
before this PR; the earlier model_info-only restriction (aimed at the leak concern
for the genuinely new allowed_fails/allowed_fails_policy fields) incorrectly dropped
that pre-existing capability too. model_info still takes priority when both are set.

* fix(router): keep the fallback-cooldown trigger in sync with #35104's review fixes

Applies the same two fixes landed on the split-out PR #35104 (which #34416
still duplicates until it's rebased onto the merged base): increment the
deployment's per-minute failure counter before evaluating cooldown, and
require the server-stamped failed_deployment_id instead of trusting a
metadata bucket, since neither "metadata" nor "litellm_metadata" can be told
apart from a caller-supplied one without knowing the call's function_name.

* fix(router): freeze the model_info fallback mapping to satisfy the type-discipline gate

* fix(router): defer f-string interpolation in fallback-cooldown debug logs

* fix(router): annotate cooldown-path locals with Final to satisfy the LIT010 budget

* fix(router): suppress reportPrivateUsage for cross-module cooldown helpers

* fix(router): don't cool down deployments for request-scoped 404s on generic API fallbacks

* fix(router): stamp the dynamic client-side-credential deployment id, not the shared static one

* fix(router): keep up with upstream typing modernization and Final-annotation ratchet

* fix(router): don't cool down deployments for a caller-supplied x-litellm-timeout

* fix(router): stamp dynamic client-side-credential id in completion fallback paths too

The generic-API-call helper already stamped the effective (dynamic-if-client-side-credential)
deployment id on exceptions, but the regular _completion/_acompletion exception handlers still
stamped the static shared deployment's id. A tenant using invalid forwarded credentials could
generate repeated failures attributed to, and eventually cooling down, the shared deployment
other tenants rely on. Extracted the stamping logic into one shared helper used by all three
call sites (generic API, sync completion, async completion) so the fix and future changes to it
stay in one place.

* fix(proxy): recognize body-supplied timeout/request_timeout/stream_timeout as caller-controlled

client_side_timeout was only set when the caller used the x-litellm-timeout header, but
Router._get_timeout also resolves the effective timeout from kwargs["timeout"],
kwargs["request_timeout"], and kwargs["stream_timeout"], all settable directly in the
request body (and x-litellm-stream-timeout wasn't marked either). A caller could set any
of those to a near-zero value, force a 408 on every deployment in a fallback chain, and
cool down deployments other tenants rely on without the guard in
_trigger_cooldown_for_failed_deployment recognizing it as caller-controlled. Also strip
any client-forged client_side_timeout from the request body so the marker is always
server-computed.

---------

Co-authored-by: Deepanshu <deepanshu.lulla@alpha-sense.com>
2026-08-10 11:02:06 -07:00
..
agent_tests test: repair stale CircleCI contracts 2026-08-08 12:19:29 -07:00
audio_tests
base_sdk_tests fix(deps): move pydantic-settings into the base dependencies 2026-08-01 15:25:26 -07:00
basic_proxy_startup_tests
batches_tests test(batches): run Responses coverage in CI 2026-07-31 10:24:10 -04:00
benchmarks test(benchmarks): run shared logging executor inline to make CodSpeed measurements deterministic (#32435) 2026-07-09 11:14:22 +03:00
code_coverage_tests fix(guardrails): scan /v1/messages tool traffic 2026-08-05 14:11:27 -07:00
documentation_tests fix(ci): make the env-key doc gate see bare get_secret and get_secret_str reads (#35996) 2026-08-05 14:49:55 -07:00
e2e Merge pull request #36277 from BerriAI/litellm_make_check_fallback 2026-08-08 12:08:34 -07:00
enterprise fix(proxy): report has_more false on caller-scoped file list pages 2026-08-08 17:28:43 -07:00
guardrails_tests fix(guardrails): honor configured timeout in Zscaler AI Guard (#36110) 2026-08-07 00:25:52 +00:00
image_gen_tests test(bedrock): switch image gen live test off EOL Titan to Nova Canvas (#31937) 2026-07-01 22:57:29 -07:00
integration
litellm fix(proxy): deny agent access when key and team grants resolve to nothing (#36221) 2026-08-07 20:44:11 +00:00
litellm-proxy-extras
litellm_core_utils
litellm_utils_tests test: repair stale CircleCI contracts 2026-08-08 12:19:29 -07:00
llm_responses_api_testing test: repair stale CircleCI contracts 2026-08-08 12:19:29 -07:00
llm_translation test(bedrock): port the openai-route invoke tests onto the live config 2026-07-29 20:59:08 -07:00
load_tests
local_testing test: repair three failing suites on litellm_internal_staging 2026-08-04 16:07:02 -07:00
logging_callback_tests feat(logging): add opt-in session_id and trace_id correlation to JSON log records via contextvars (#34418) 2026-08-10 10:40:13 -07:00
mcp_tests fix(mcp): keep REST tool listing in step with key/team grant enforcement 2026-07-30 22:13:10 -07:00
multi_instance_e2e_tests
ocr_tests test(ocr): use mistral-document-ai-2512 in azure_ai OCR tests 2026-07-15 18:13:22 -07:00
old_proxy_tests/tests
openai_endpoints_tests fix(batches): register managed output files on batch cancel 2026-08-05 18:28:52 -07:00
otel_tests
pass_through_tests test(pass-through): de-flake vertex spend-log test by routing through the proxy (#31689) 2026-06-30 15:27:48 -07:00
pass_through_unit_tests fix(proxy): re-assert the authenticated identity on passthrough requests (#36121) 2026-08-07 00:41:29 +00:00
proxy_admin_ui_tests Revert "chore: remove _experimental/out (#31546)" 2026-07-01 13:25:47 -07:00
proxy_behavior test: repair stale CircleCI contracts 2026-08-08 12:19:29 -07:00
proxy_e2e_anthropic_messages_tests
proxy_migration_tests test(docker): gate the componentized gateway and backend images on an arbitrary-uid offline boot (#36136) 2026-08-07 09:57:37 -07:00
proxy_security_tests
proxy_unit_tests feat(router): add per-deployment allowed_fails_policy and cooldown_time override support (#34416) 2026-08-10 11:02:06 -07:00
router_unit_tests feat(router): add per-deployment allowed_fails_policy and cooldown_time override support (#34416) 2026-08-10 11:02:06 -07:00
scim_tests
search_tests feat(tinyfish): make search provider permissive, attribute errors (#31997) 2026-07-03 10:17:11 -07:00
spend_tracking_tests
store_model_in_db_tests feat(team): custom metadata validation hook for team create and update (#33353) 2026-08-03 18:37:45 -07:00
test_litellm feat(router): add per-deployment allowed_fails_policy and cooldown_time override support (#34416) 2026-08-10 11:02:06 -07:00
unified_google_tests
vector_store_tests
windows_tests
__init__.py
_fake_openai_endpoint_server.py
_flush_vcr_cache.py
_live_test_helpers.py
_openai_record_replay_proxy.py
_vcr_conftest_common.py
_vcr_redis_persister.py
_ws_vcr.py test(realtime): record and replay websocket traffic in redis vcr cassettes (#32390) 2026-07-08 00:19:06 -07:00
eval_swe_bench.py
fake_openai_endpoint.py
gettysburg.wav
large_text.py
openai_batch_completions.jsonl
pyrightconfig.json
README.MD
test_anthropic_compaction_usage.py
test_budget_management.py
test_callbacks_on_proxy.py
test_config.py
test_debug_warning.py
test_default_encoding_non_root.py
test_end_users.py
test_entrypoint.py
test_fallbacks.py
test_gpt5_azure_temperature_support.py
test_health.py
test_keys.py
test_litellm_proxy_responses_config.py
test_logging.conf
test_models.py
test_new_vector_store_endpoints.py
test_openai_endpoints.py
test_organizations.py
test_otel_thread_leak.py
test_passthrough_endpoints.py
test_presidio_latency.py
test_proxy_server_non_root.py
test_ratelimit.py
test_resource_cleanup.py
test_service_logger_otel.py fix(langfuse): send v4 ingestion header for otel callback (#33907) 2026-07-18 20:36:51 -07:00
test_spend_logs.py
test_team.py
test_team_logging.py
test_team_members.py
test_users.py

In total litellm runs 1000+ tests

[02/20/2025] Update:

To make it easier to contribute and map what behavior is tested,

we've started mapping the litellm directory in tests/test_litellm

This folder can only run mock tests.