Skip to content

fix(logging): backport upstream PR #28476 — recalc cost after router retry#74

Merged
songkuan-zheng merged 1 commit into
ship/v1.83.10from
fix/cost-recalc-after-retry
Jun 5, 2026
Merged

fix(logging): backport upstream PR #28476 — recalc cost after router retry#74
songkuan-zheng merged 1 commit into
ship/v1.83.10from
fix/cost-recalc-after-retry

Conversation

@songkuan-zheng

Copy link
Copy Markdown
Collaborator

Tier classification

  • C — Universal bug fix in litellm/ core

If Tier C or D, did you try upstream first?

Relevant issues

Fixes the production-observed bug where Router-retried calls record $0 spend in SpendLog despite a successful response with real usage.

Root cause

When Router retries an LLM call, every attempt shares one Logging instance.

  1. Attempt 1 fails (e.g. Key1 503). failure_handler writes model_call_details["response_cost"] = 0 (upstream [Bug/Feature]: Langfuse should be sent input_cost, output_cost, and total_cost BerriAI/litellm#4604).
  2. Router falls back to attempt 2 (Key2 200). _process_hidden_params_and_response_cost runs.
  3. The preserve branch added by upstream fix(logging): preserve pass-through endpoint response_cost BerriAI/litellm#21844 sees model_call_details["response_cost"] is not None (it's 0), assumes a pass-through handler already calculated the cost, and passes — skipping recalculation.
  4. SpendLog gets $0 despite real usage.

Fix

Tighten the preserve branch with a walrus-bound != 0 guard so a stale zero from failure_handler falls through to recalc. Legitimate zero costs (cache_hit, pass-through handlers writing 0 to result._hidden_params) are still preserved by the earlier two branches.

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 b55749248d. Kept:

  • litellm/litellm_core_utils/litellm_logging.py — the 4-line fix
  • tests/test_litellm/litellm_core_utils/test_litellm_logging.py — 3 new regression tests (140 lines)

Dropped the tests/local_testing/test_amazing_vertex_completion.py hunk in the upstream commit — that's an upstream-CI Google Maps Platform flake skip, unrelated to this fix and not run in our CI.

Pre-Submission checklist

  • Added testing in tests/test_litellm/litellm_core_utils/test_litellm_logging.py (3 new regression tests)
  • My PR passes targeted unit tests:
    $ 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
    
  • My PR's scope is isolated — only the spend=0-after-retry fix + its regression tests
  • Black 24.10.0 --target-version py310 clean on changed files

Screenshots / Proof of Fix

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 (real Gemini call through proxy with fallback routing).

New regression tests explicitly cover all three cases:

Test Scenario Expected
test_process_hidden_params_recalculates_cost_after_failure_handler_zero Retry: prior attempt set response_cost=0 via failure_handler, success attempt has usage Recalculate cost > 0
test_process_hidden_params_preserves_zero_cost_in_hidden_params Pass-through handler writes response_cost=0 to result._hidden_params Preserve 0
test_process_hidden_params_uses_hidden_params_cost_after_failure_handler_zero Both stale 0 in model_call_details AND a real value in hidden_params hidden_params wins

Conflict resolutions

File Strategy
litellm/litellm_core_utils/litellm_logging.py Manual surgical Edit (cherry-pick + black would have introduced unrelated black version drift in the same file; restored to HEAD then applied only the 4-line PR diff)
tests/test_litellm/litellm_core_utils/test_litellm_logging.py Clean cherry-pick — pure append, no conflicts
tests/local_testing/test_amazing_vertex_completion.py Dropped — upstream-CI hygiene, out of scope
$ git diff ship/v1.83.10..HEAD --stat
 litellm/litellm_core_utils/litellm_logging.py      |   7 +-
 .../litellm_core_utils/test_litellm_logging.py     | 140 +++++++++++++++++++++
 2 files changed, 145 insertions(+), 2 deletions(-)

…router retry

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.
@songkuan-zheng songkuan-zheng merged commit a4c73d5 into ship/v1.83.10 Jun 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant