fix(logging): recalculate cost after router retry failures#28476
Conversation
Do not preserve response_cost=0 from failure_handler when processing a successful response; only keep pre-calculated costs > 0 (pass-through). Co-authored-by: Cursor <[email protected]>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Greptile SummaryFixes a zero-spend regression on router-retried calls where
Confidence Score: 5/5Safe to merge — the change is a single-line guard tightening in the cost-resolution branch, with four targeted unit tests that verify both the fixed path and the preserved pass-through paths. The fix is minimal and well-contained: one walrus-operator condition in No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/litellm_core_utils/litellm_logging.py | Adds != 0 guard to the existing-cost preserve branch so stale 0 from failure_handler on router retries no longer blocks cost recalculation on success |
| tests/test_litellm/litellm_core_utils/test_litellm_logging.py | Adds four unit tests covering the retry-zero-cost regression, hidden-params zero preservation, hidden-params cost priority after failures, and pass-through cost preservation |
| tests/local_testing/test_amazing_vertex_completion.py | Adds InternalServerError catch to skip (not fail) on transient upstream 500s from Google Maps Platform |
Reviews (3): Last reviewed commit: "test(vertex): skip google maps tool test..." | Re-trigger Greptile
Use != 0 for pre-calculated cost preservation (Greptile feedback). Add tests for zero cost in _hidden_params and for hidden_params overriding failure 0. Co-authored-by: Cursor <[email protected]>
|
@greptileai check again |
The test test_gemini_google_maps_tool_simple calls real Vertex AI with the
googleMaps tool, which depends on Google Maps Platform. CI has been
failing on local_testing_part1 across many unrelated PRs (including this
one and the litellm_internal_staging base) with an InternalServerError
500 from Maps Platform ('Internal server error. Please retry. ...maps-
platform-support'), which is an external upstream flake unrelated to
the change under test.
Catch litellm.InternalServerError and skip (mirroring the existing
RateLimitError handler) so transient upstream outages don't block CI.
…router retry (#74) Backports BerriAI#28476 (merged 2026-05-22) to ship/v1.83.10. ## Root cause When Router retries an LLM call, every attempt shares one `Logging` instance. `failure_handler` sets `model_call_details["response_cost"] = 0` on each failed attempt (introduced by upstream BerriAI#4604). The success path in `_process_hidden_params_and_response_cost` then short-circuits via a preserve branch (introduced by BerriAI#21844) that treats any non-None existing cost as authoritative — including the stale 0 from the prior failed attempt — so SpendLog records $0 despite a successful response with real usage. ## Fix Tighten the preserve branch with a walrus-bound `!= 0` guard so a stale zero from failure_handler falls through to recalculation. Legitimate zero costs (cache_hit, pass-through handlers writing `result._hidden_params["response_cost"] = 0`) are still preserved by the earlier two branches. ```python elif ( existing_cost := self.model_call_details.get("response_cost") ) is not None and existing_cost != 0: pass # preserve pass-through cost ``` ## Scope of backport Cherry-picked from upstream merge commit b557492. Kept the `litellm_logging.py` fix + the 3 new regression tests in `tests/test_litellm/litellm_core_utils/test_litellm_logging.py`. Dropped the `tests/local_testing/test_amazing_vertex_completion.py` hunk — that's an upstream-CI Google Maps Platform flake skip, unrelated to this fix and not run in our CI. ## Tier C — universal bug fix in `litellm/` core. Already merged upstream (BerriAI#28476), so this is purely a backport for v1.83.10. ## Verification ``` $ pytest tests/test_litellm/litellm_core_utils/test_litellm_logging.py \ -k "process_hidden_params or async_success_handler_preserves" -v ... 4 passed in 0.72s ``` E2E reproduction (Key1 503 → fallback Key2 200) was previously confirmed against this exact mechanism — spend went from 0 to 0.000968 with the fix applied.
…8476) * fix(logging): recalculate cost after router retry failures Do not preserve response_cost=0 from failure_handler when processing a successful response; only keep pre-calculated costs > 0 (pass-through). Co-authored-by: Cursor <[email protected]> * test(logging): guard pass-through zero cost; use != 0 preserve check Use != 0 for pre-calculated cost preservation (Greptile feedback). Add tests for zero cost in _hidden_params and for hidden_params overriding failure 0. Co-authored-by: Cursor <[email protected]> * test(vertex): skip google maps tool test on transient upstream 500 The test test_gemini_google_maps_tool_simple calls real Vertex AI with the googleMaps tool, which depends on Google Maps Platform. CI has been failing on local_testing_part1 across many unrelated PRs (including this one and the litellm_internal_staging base) with an InternalServerError 500 from Maps Platform ('Internal server error. Please retry. ...maps- platform-support'), which is an external upstream flake unrelated to the change under test. Catch litellm.InternalServerError and skip (mirroring the existing RateLimitError handler) so transient upstream outages don't block CI. --------- Co-authored-by: Cursor <[email protected]> Co-authored-by: mateo-berri <[email protected]>
Relevant issues
Fixes zero spend on successful router-retried calls when intermediate failures pin
response_costto0(regression interaction with #21844).Root cause:
failure_handlersetsresponse_cost = 0on each failed attempt (#4604). PR #21844 added a preserve branch in_process_hidden_params_and_response_costthat kept any non-Noneresponse_cost, including0/0.0, so a later success with full usage never recalculated cost.How the fix helps the customer case: Router retries share one
Loggingobject. Failed attempts setresponse_costto0; the successful Bedrock/passthrough response has real usage. We no longer treat that stale0as a pre-calculated cost—we recalculate from the success response so spend logs andcost_breakdownmatch token usage (~$0.03 for ~9.7k + 30 tokens). Pass-through handlers that set a non-zero cost (orresponse_costonresult._hidden_params) are unchanged.Linear ticket
Resolves LIT-3261
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit(CI on PR)@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewDelays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
CI (LiteLLM team)
Branch creation CI run
Link: (fill after PR opened)
CI run for the last commit
Link: (fill after PR opened)
Merge / cherry-pick CI run
Links: (fill after PR opened)
Screenshots / Proof of Fix
Before (on
litellm_internal_stagingwithout this PR)Simulate two failed attempts (
failure_handler→response_cost = 0), then process a successfulModelResponsewith usage (same sharedLoggingobject as router retries):Expected before fix:
response_cost: 0andslo response_cost: 0.0despite 9728 tokens (matches customer report on v1.85.0).After (this branch)
Run all regression tests:
Expected after fix:
Manual check on this branch (same script as above) prints non-zero cost (~
0.0014727forgpt-4o-miniat 9698+30 tokens).test_process_hidden_params_recalculates_cost_after_failure_handler_zero0+ success usage → non-zero costtest_process_hidden_params_preserves_zero_cost_in_hidden_params0.0on_hidden_paramsstays0(no regression)test_process_hidden_params_uses_hidden_params_cost_after_failure_handler_zero_hidden_paramscost wins over stale0test_async_success_handler_preserves_response_cost_for_pass_through_endpointsCustomer symptom (context)
allm_passthrough_route,attempted_retries: 2, ~9.7k prompt + 30 completion tokens,cost_breakdownall zeros on success row.set_response_headersrecalc differ from Bedrock passthrough path). Unit repro above matches the logging bug mechanism.Greptile P2 (
!= 0vs> 0)Greptile suggested
existing_cost != 0instead of> 0. For0and0.0,== 0is true in Python, so both guards recalculate the same way. We use!= 0per review; legitimate zero cost onresult._hidden_paramsis still preserved via the earlier branch ("response_cost" in hidden_params).Type
🐛 Bug Fix
Changes
litellm/litellm_core_utils/litellm_logging.py: In_process_hidden_params_and_response_cost, only preserve existingresponse_costwhen it is!= 0(pass-through pre-calculated non-zero). Do not preserve0/0.0fromfailure_handleron intermediate router retries; recalculate from usage on success.tests/test_litellm/litellm_core_utils/test_litellm_logging.py:test_process_hidden_params_recalculates_cost_after_failure_handler_zerotest_process_hidden_params_preserves_zero_cost_in_hidden_paramstest_process_hidden_params_uses_hidden_params_cost_after_failure_handler_zero