litellm/tests/unit/router_strategy
tessellark 8ab0321816
fix(proxy): charge a video's provider-reported cost once, on the first poll that sees it completed (#49824)
* fix(proxy): charge a video's provider-reported cost once, on the first poll that sees it completed

A custom video provider can report a video's cost on the status poll that
sees it completed. Every poll of a video shared the video id as its spend
log request_id, so the paid poll's row was dropped as a duplicate and every
later poll of the finished video charged the cost again.

Video status rows now carry their own `_video_cost` request_id, and a poll
that reports a cost goes through the same settle-once claim batches use, so
the first poll that sees the video completed charges it and writes the row,
and every later poll charges nothing.

* fix(proxy): charge a video's provider-reported cost once on model, router and session budgets too

A video status poll gets the _video_cost row suffix only when it reports a cost, so a $0 poll keeps the bare video id and adds no row of its own, and the spend log writer claims a row by that suffix.

The batch charge-once marker now covers videos, keyed on the video id under {spend_key}:video:{id}, with the batch marker key unchanged. Model and router budgets use it, and per-session budgets now charge a repolled batch or video once and ignore a cost that is not a finite number

* fix(proxy): charge a finished video's own cost once, keyed by the id the client polled

Guardrail cost on a running poll no longer takes the charge-once claim, only a completed poll charges, the session marker TTL refreshes like the others, and two deployments whose provider ids match each keep their own charge.

* fix(proxy): charge repeat poll guardrails, key video charges by canonical id and survive redaction

* fix(proxy): keep video status polls in-process for the collector and give guardrail-only job polls an unclaimable row id

* fix(proxy): keep repeat batch polls free and group guardrail-only batch rows under their batch's trace

* fix(proxy): settle a finished job whose own cost is below its poll's guardrail cost under the job's charge row

* test(proxy): count charged_poll_cost as covered through the finished job guardrail poll tests

* fix(proxy): settle a finished batch that cost nothing itself on its first priced retrieve

* fix(proxy): charge no budget for a batch retrieve before its cost is final, and read the retrieved batch from before redaction

* fix(proxy): charge sessions a poll's guardrail-inclusive cost, count a job cost row's guardrail run, and keep batch_cost_is_final importable from litellm.batches

* fix(proxy): claim a job and charge the session budget in one Redis call

The session limiter claimed the job marker and then raised spend in a second Redis call. A failure between the two left the job claimed while its charge sat only in local memory, so later polls skipped it. One script now does both, and a refused script claims and charges locally

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-11-04 20:22:06 +00:00
..
adaptive_router fix(anthropic): answer an upstream Anthropic 529 as 529 overloaded_error on /v1/messages (#46063) 2026-11-03 17:37:09 -08:00
complexity_router fix(router): restore four base behaviors the RoutingContext refactor changed (#49395) 2026-10-31 22:58:34 +00: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): fence the router surface and document the router-core boundary (#48839) 2026-10-29 00:01:52 +00: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(decisions): add Parallel AI web-grounded decisions (#49188) 2026-11-03 04:47:02 +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 test: move routing, caching and callback tests out of local_testing (#45761) 2026-10-10 09:33:09 -07: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): route the router family through RoutingContext and close the outside surface (#49118) 2026-10-30 19:18:54 -07: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): route the router family through RoutingContext and close the outside surface (#49118) 2026-10-30 19:18:54 -07:00
test_router_routing_groups.py refactor(router): extract PythonCore behind RouterCore and pass the router its config (#49119) 2026-10-31 12:54:23 -07:00
test_router_routing_plugins.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_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 fix(router): restore four base behaviors the RoutingContext refactor changed (#49395) 2026-10-31 22:58:34 +00: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