feat(spend): surface per-request auto-router savings to logging callbacks - #37894
Conversation
…acks The auto-router savings figure was computed only inside the spend-update writer, downstream of where logging callbacks consume the standard logging payload, so Datadog-style callbacks never received it. Compute it once in the payload builder, stamp it as a top-level payload field beside cost_breakdown, thread it into the spend log metadata, and have both spend-writer call sites read the recorded value with recomputation as the fallback for rows written before the field shipped. Internal sub-calls (classifier, shadow eval) are never stamped, and a caller-forged metadata value is discarded by the unconditional overwrite. Resolves LIT-5973
|
@greptileai can you reply here with a score and why that score? |
|
bugbot run |
|
Score: 4/5 What works well:
What holds it back from 5/5:
None of these are blockers — the core design is sound and the feature delivers exactly what the description promises. |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a6c67c1. Configure here.
Greptile SummaryThis PR computes auto-router savings while constructing the standard logging payload, exposes the value to callbacks, and carries the same recorded figure into spend persistence.
Confidence Score: 4/5The PR appears safe to merge, with one non-blocking metadata-mutation cleanup needed to match repository conventions. The savings value is consistently computed, propagated, and preferred by spend writers, while the only accepted concern is the mutable construction of the new metadata field. Files Needing Attention: litellm/proxy/spend_tracking/spend_tracking_utils.py
|
| Filename | Overview |
|---|---|
| litellm/litellm_core_utils/litellm_logging.py | Computes auto-router savings during standard payload construction and exposes the result as a callback field. |
| litellm/proxy/spend_tracking/savings.py | Extracts reusable per-request savings calculation and gives valid recorded values precedence during rollup calculation. |
| litellm/proxy/spend_tracking/spend_tracking_utils.py | Propagates the standard payload value into spend metadata, but the new assignment violates the repository's immutability guidance. |
| litellm/proxy/db/db_spend_update_writer.py | Passes recorded auto-router savings from spend metadata into both persistence calculation paths. |
| litellm/types/utils.py | Extends the standard logging callback contract with the optional-value auto-router savings field. |
| litellm/proxy/_types.py | Extends spend-log metadata typing with the recorded auto-router savings field. |
| tests/test_litellm/proxy/spend_tracking/test_savings.py | Covers recorded-value precedence, legacy recomputation, invalid values, and internal-call exclusion. |
| tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py | Verifies propagation into serialized spend metadata and rejection of caller-forged values. |
Reviews (1): Last reviewed commit: "feat(spend): surface per-request auto-ro..." | Re-trigger Greptile
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
Checked each of the three "holds it back from 5/5" items in #37894 (comment) against the repo and this PR's CI. Evidence below — none required a code change: 1. schema.d.ts sync — the check exists and already ran on this PR. 2. 3. Early-return provenance not type-enforced — this matches the field's design precedent exactly: The inline P2 on the @greptileai given the above, please reply here with your updated confidence score and why. |
|
All three points hold up against the evidence: 1. schema.d.ts sync — confirmed green on the CI run linked. 2. 3. Provenance — The implementation is defensive throughout ( Updated score: 5/5. The PR is ready for maintainer review. |
TLDR
Problem this solves:
How it solves it:
autorouter_savingsbesidecost_breakdownon the payloadUser Flow
Before: a platform team routing traffic through an auto-router cannot ship the reported savings into Datadog, because the per-request records their callbacks receive carry no savings figure
"model": "auto_router1"/auto_router/benchmarksendpoint, which their pipeline does not readAfter: the same request record now carries the savings figure, so their existing pipeline forwards it with zero changes
"model": "auto_router1""autorouter_savings": 0.0123931alongside the routing decision and cost breakdownRelevant issues
Linear ticket
Resolves LIT-5973
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Screenshots / Proof of Fix
Rig: proxy on localhost:4620 with real Postgres, an auto-router (
auto_router1, tiers nano/mini/5.4) over an instrumented upstream with explicit per-token prices, and a probe callback that appends every standard logging payload it receives to a JSONL file. Expected per-request figure at these prices: baseline gpt-5.4 minus routed gpt-5-nano on 4242 in / 137 out = 0.0123931Before (9821b45)
/v1/chat/completions: callback payload has no savings field
for n in 1 2 3; do curl -s http://127.0.0.1:4620/v1/chat/completions -H "Authorization: Bearer sk-rig-1234" -d '{"model": "auto_router1", "messages": [{"role": "user", "content": "repro probe unique turn N"}]}'; done{"model_group": "auto_router1", "routing_decision": {..., "savings_baseline_model": "openai/gpt-5.4", ...}, "savings_fields": {}}routing_decisionandcost_breakdownbut no savings key:savings keys anywhere: ['compression_savings'](null)The figure exists, only in the rollup tables
psql -c 'select autorouter_savings_spend from "LiteLLM_DailyUserSpend"'returns0.0371793(3 x 0.0123931)psql -c 'select sum(saved_spend) from "LiteLLM_AutoRouterSession"'returns0.0371793After (a6c67c1)
/v1/chat/completions: callback receives the figure
{"model_group": "auto_router1", "routing_decision_present": true, "savings_fields": {"autorouter_savings": 0.0123931}}autorouter_savings: 0.0123931 | routed: gpt-5-nano/v1/messages
curl -s http://127.0.0.1:4620/v1/messages -H "Authorization: Bearer sk-rig-1234" -d '{"model": "auto_router1", "max_tokens": 100, "messages": [{"role": "user", "content": "messages surface probe"}]}'{"call_type": "anthropic_messages", "model_group": "auto_router1", "savings": {"autorouter_savings": 0.0123931}}/v1/responses
curl -s http://127.0.0.1:4620/v1/responses -H "Authorization: Bearer sk-rig-1234" -d '{"model": "auto_router1", "input": "responses surface probe"}'{"call_type": "aresponses", "model_group": "auto_router1", "savings": {"autorouter_savings": 0.0123931}}aresponses -> autorouter_savings: 0.0123931Internal classifier calls and forged values stay excluded
"savings_fields": {"autorouter_savings": null}"metadata": {"autorouter_savings": 999.0}lands in the spend log with the real computed0.0123931, the forged value discarded"model": "gpt-5-mini") recordsautorouter_savings: NoneType
🆕 New Feature
Caveats (if any)
metadata.*keys only, so this top-level field needs a follow-up OTel attribute if wanted theretest_datadog.py(missing DD_API_KEY env) fail identically at the merge baseFinal Attestation
Note
Medium Risk
Touches spend/cost reporting: a wrong stamp would misreport savings in callbacks and daily rollups. Safeguards exclude internal calls and caller-forged metadata.
Overview
Adds
autorouter_savingsto the standard logging payload so logging callbacks (Datadog, GCS, S3, etc.) receive the same per-request auto-router savings that previously lived only in daily rollups.The figure is computed once when the payload is built (lazy proxy import so SDK-only installs skip it). Spend writers prefer that recorded value over recomputation so logs, turn tables, and rollups cannot disagree; older rows without the field still recompute. Internal classifier/shadow-eval calls are not stamped, and client-supplied
autorouter_savingsin request metadata is overwritten.Nonemeans not auto-routed;0.0is a real routed figure.Reviewed by Cursor Bugbot for commit a6c67c1. Bugbot is set up for automated code reviews on this repo. Configure here.