litellm/tests/unit/router_strategy
joshua-berri 3c3f266322
fix(router): a cancelled or retried attempt releases its io-token reservation and least-busy count (#50486)
* fix(router): a cancelled or retried attempt releases its io-token reservation and least-busy count

A routed attempt cancelled mid-call (client disconnect with cancel_on_disconnect) kept its ITPM/OTPM
reservation and its least-busy in-flight count, and retries sharing one logging object leaked the
count of every attempt after the first. Each async attempt now runs under its own attempt id, so the
attempt and its own callbacks release what it holds exactly once, in tasks a cancellation can't stop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(router): settle an io-token reservation once even through a logged copy of its metadata

/v1/messages and /v1/responses stash the reservation in litellm_metadata, which the logging object
copies. A cancelled attempt refunded it, then the proxy's failure logging refunded the copy again.
Each stash now carries an id, and a reservation already settled under that id is not settled again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(router): keep spent tokens counted, refund a previous attempt's stash, and fit every call in flight

A request cancelled after its provider answered now keeps the tokens it spent counted instead of
refunding them. A retry refunds the reservation its previous attempt left behind even though it runs
under a new attempt. The least-busy reservation store holds twice the keys, since each call in flight
now keeps one more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(router): never evict a settled reservation id before its counters' window has passed

Settled ids lived in a capacity-bounded cache, so past 10,000 settlements in two minutes a late copy
of an evicted reservation was refunded a second time. They now leave only when their window expires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(router): start releases eagerly and keep attempt settlement out of router_utils

A release that never waits (an in-memory counter) now finishes before its caller goes on, as it did
when the caller awaited it inline, so a websocket turn's usage is counted by the time its logging is
flushed. Each reservation carries its own id, and the attempt settlement module moves to
litellm_core_utils, since litellm/utils.py may not import from router_utils.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(router): count only finished answers as spent, and declare the least-busy redis family

A stream's wrapper returning is not an answer: a stream cancelled before its first chunk now hands
back its whole reservation. Only a non-streaming result marks its attempt as answered. The module
level least-busy shared decrement carries its redis family, as every direct redis caller must.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 16:54:26 -08:00
..
adaptive_router refactor(typing): clear about 1,100 basedpyright Any errors across 303 files (#49986) 2026-11-06 13:31:15 -08:00
complexity_router fix(router): restore four base behaviors the RoutingContext refactor changed (#49395) 2026-10-31 22:58:34 +00:00
switchyard_router feat(router): add the Switchyard stage router as an auto router strategy (#50407) 2026-11-06 12:07:57 -08:00
__init__.py test: add __init__.py to intermediate tests/unit packages 2026-09-20 12:55:27 +00:00
test_auto_router.py refactor(router): route the router family through RoutingContext and close the outside surface (#49118) 2026-10-30 19:18:54 -07:00
test_auto_router_model_naming.py feat(router): infer custom classifier tiers from model mappings (#48987) 2026-10-29 11:30:23 -07:00
test_auto_router_tuning_baseline.py refactor(router): fence the router surface and document the router-core boundary (#48839) 2026-10-29 00:01:52 +00:00
test_base_routing_strategy.py fix(router): unregister strategy selectors from global callbacks on discard (#45795) 2026-10-21 02:39:36 +00:00
test_baseline_request.py refactor(router): delete the python router core, the rust core is the only core (#50178) 2026-11-05 10:40:46 -08:00
test_budget_limiter.py fix(proxy): charge a video's provider-reported cost once, on the first poll that sees it completed (#49824) 2026-11-04 20:22:06 +00:00
test_budget_limiter_hotpath.py test(router_strategy): keep foreign asyncio task noise out of the budget limiter caplog asserts (#43375) 2026-10-29 06:22:06 -07:00
test_complexity_router.py feat(redis): serve the proxy's common Redis call with Rust stores (#50170) 2026-11-06 06:01:11 +00:00
test_complexity_tier_predictor.py test: move tests/test_litellm core utils, routing, responses, caching and rust_bridge into tests/unit (#43199) 2026-09-25 17:10:13 -07:00
test_fuse_presets.py test: move tests/test_litellm core utils, routing, responses, caching and rust_bridge into tests/unit (#43199) 2026-09-25 17:10:13 -07:00
test_lar1_routing.py refactor(router): route the router family through RoutingContext and close the outside surface (#49118) 2026-10-30 19:18:54 -07:00
test_least_busy.py fix(router): a cancelled or retried attempt releases its io-token reservation and least-busy count (#50486) 2026-11-07 16:54:26 -08:00
test_litellm_encoder.py fix(router): restore four base behaviors the RoutingContext refactor changed (#49395) 2026-10-31 22:58:34 +00:00
test_llm_v2.py refactor(router): delete the python router core, the rust core is the only core (#50178) 2026-11-05 10:40:46 -08:00
test_lowest_cost.py fix(router): price cost-based routing through model_info.base_model (#47617) 2026-10-20 22:20:16 +00:00
test_lowest_latency.py fix(router): keep latency routing alive when latency samples are unusable (#48216) 2026-10-24 13:43:07 -07:00
test_lowest_tpm_rpm.py test: move routing, caching and callback tests out of local_testing (#45761) 2026-10-10 09:33:09 -07:00
test_lowest_tpm_rpm_v2.py fix(rate-limit): estimate embedding image and audio data URIs as media in TPM pre-call checks (#49831) 2026-11-03 22:06:59 +00:00
test_quality_router.py refactor(router): delete the python router core, the rust core is the only core (#50178) 2026-11-05 10:40:46 -08:00
test_router_routing_groups.py refactor(router): delete the python router core, the rust core is the only core (#50178) 2026-11-05 10:40:46 -08:00
test_router_routing_plugins.py refactor(router): delete the python router core, the rust core is the only core (#50178) 2026-11-05 10:40:46 -08:00
test_router_tag_regex_routing.py refactor(router): route the router family through RoutingContext and close the outside surface (#49118) 2026-10-30 19:18:54 -07:00
test_router_tag_routing.py feat(router): add the Switchyard stage router as an auto router strategy (#50407) 2026-11-06 12:07:57 -08:00
test_savings_baseline.py refactor(router): route the router family through RoutingContext and close the outside surface (#49118) 2026-10-30 19:18:54 -07:00
test_simple_shuffle.py test: move tests/test_litellm core utils, routing, responses, caching and rust_bridge into tests/unit (#43199) 2026-09-25 17:10:13 -07:00
test_stall_detector.py test: move tests/test_litellm core utils, routing, responses, caching and rust_bridge into tests/unit (#43199) 2026-09-25 17:10:13 -07:00