chore(release): backport #33810, #33733 to rc/1.94.0 and bump litellm-proxy-extras to 0.4.79.post1 - #34215
Conversation
…regates (#33810) * feat(spend): track prompt compression saved tokens in daily spend aggregates Native compression interception now records tokens_before/after/saved into the request litellm_metadata so savings land in the SpendLog metadata JSON under a typed compression_savings key. A single normalizer (extract_compression_saved_tokens) sums that key with Headroom guardrail tokens_saved; the two writers are disjoint and run at different stages, so summing never double-counts. The spend-log redactor now preserves purely numeric compression stats inside guardrail_response so Headroom savings survive the store_prompts_in_spend_logs=false default. compression_saved_tokens is threaded through BaseDailySpendTransaction, queue aggregation, the daily upsert blocks, a new BigInt column on all six daily spend tables, and the daily activity read path (SpendMetrics, DailySpendMetadata, raw-SQL rollups) * fix(spend): normalize legacy guardrail shapes and float token stats in compression savings reader * feat(spend): aggregate compression and prompt caching dollar savings in daily rollups Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(spend): update daily spend aggregation fixtures for savings columns Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * feat(ui): add Cost Optimization dashboard page New left-nav Cost Optimization page under Observability that surfaces money saved by prompt compression and prompt caching. It reads the daily activity rollup (userDailyActivityCall / get_daily_activity) and never scans SpendLogs, so it stays fast at 1M+ rows. Renders a Total saved card, per-driver Compression and Prompt caching cards, a savings-over-time area chart, and a savings-by-driver donut, all aggregated in memory from the per-day metrics.compression_savings_spend and metrics.prompt_caching_savings_spend fields. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> (cherry picked from commit 3f3295b)
…_tags feat(bedrock): forward bedrock_tags to CreateModelInvocationJob for batch jobs (cherry picked from commit 3819ee5)
Greptile SummaryThis PR adds cost-optimization reporting and Bedrock batch tags to the release branch. The main changes are:
Confidence Score: 5/5No additional blocking issue was found in this follow-up.
|
| Filename | Overview |
|---|---|
| litellm/proxy/spend_tracking/savings.py | Adds per-request pricing for compression and prompt-cache savings. |
| litellm/proxy/db/db_spend_update_writer.py | Adds saved-token and savings-spend values to daily transactions and upserts. |
| litellm/proxy/db/db_transaction_queue/daily_spend_update_queue.py | Aggregates the three new savings fields across queued daily transactions. |
| litellm/llms/bedrock/batches/transformation.py | Validates configured Bedrock tags and forwards them to batch job creation. |
| litellm/proxy/auth/auth_utils.py | Prevents request bodies from overriding administrator-controlled Bedrock tags. |
| ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.tsx | Adds charts and totals for compression and prompt-caching savings. |
| ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/page.tsx | Adds the new dashboard route and passes authenticated user context to the view. |
| litellm-proxy-extras/litellm_proxy_extras/migrations/20260717000000_add_compression_saved_tokens/migration.sql | Adds saved-token columns to all six daily spend tables. |
| litellm-proxy-extras/litellm_proxy_extras/migrations/20260718000000_add_savings_spend/migration.sql | Adds compression and prompt-caching savings columns to all six daily spend tables. |
Reviews (2): Last reviewed commit: "bump: version 0.4.79 → 0.4.79.post1" | Re-trigger Greptile
| "use client"; | ||
|
|
||
| import CostOptimizationView from "./_components/CostOptimizationView"; | ||
| import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; | ||
|
|
||
| export default function CostOptimizationPage() { | ||
| const { accessToken, userId, userRole } = useAuthorized(); | ||
| return <CostOptimizationView accessToken={accessToken} userId={userId} userRole={userRole} />; | ||
| } |
There was a problem hiding this comment.
Production Bundle Omits New Page
Proxy deployments serve the checked-in export under litellm/proxy/_experimental/out/, but this change only adds the source route and navigation entry. Images and packages built from this commit will keep serving the old dashboard, so users cannot open the new Cost Optimization page until the static export is rebuilt and included.
Rule Used: What: Ensure that any PR claiming to fix an issue ... (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| except Exception as e: # noqa: BLE001 # get_model_info raises bare Exception for unmapped models; degrade to zero savings | ||
| verbose_proxy_logger.debug( | ||
| "savings: no model info for provider=%s model=%s (%s)", custom_llm_provider, model, e | ||
| ) | ||
| return 0.0, 0.0 | ||
| input_cost = float(info.get("input_cost_per_token") or 0.0) |
There was a problem hiding this comment.
When a spend log contains a deployment alias or custom model name that get_model_info cannot resolve, this branch silently prices both compression and prompt-cache savings at zero. Those requests still record saved tokens, but the new dashboard reports no dollar savings even when the deployment has a priced underlying model.
|
Moving this to draft pending a decision on one finding; not a problem with the picks themselves. Everything the backport is responsible for checks out. Both picks are patch-id identical to their staging sources, the targeted suite goes 738 passed at baseline to 802 passed post-pick with zero new failures, the full The finding is in This is byte-identical to staging, so it is a latent defect in the feature rather than something the backport introduced, and it is present on staging today. It is still a behavior change against the current Holding here rather than patching it inline, since a fix written directly on the rc line would be code that never went through staging review. The staging-first sequence would be to guard the coercion on staging, then bring that fix along with these picks. |
|
Taking this out of draft; the finding above resolved on inspection of the upstream service. I read the compression service's The two shapes the service can actually produce are zero and negative; the response is unclamped where the service's own telemetry applies So the missing non-finite guard is input validation against a misbehaving dependency rather than a defect on a live path. It is byte-identical to the staging source, so it is being tracked as a separate hardening change on staging instead of diverging this branch from what it was picked from. The body now carries the full verification evidence: targeted suite 738 to 802 passed with no regressions, a clean full-suite set delta, the two-stage migration replay against a database already at rc.2, and live proxy output for both the spend write path and the bedrock_tags rejection. Still outstanding and not part of this branch: the Next.js bundle rebuild for the three production |
Relevant issues
Backports two staging features onto
rc/1.94.0and bumpslitellm-proxy-extrasto0.4.79.post1so the new migrations actually ship.#33810 threads prompt-compression and prompt-caching savings through the daily spend aggregates: a normalizer sums compression-interception savings with Headroom guardrail
tokens_saved, the value is written into the SpendLog metadata JSON under a typedcompression_savingskey, andcompression_saved_tokensplus the two dollar-savings columns are threaded throughBaseDailySpendTransaction, the queue aggregation, the daily upsert blocks, six new BigInt columns, and the daily-activity read path. It also adds the Cost Optimization dashboard page.#33733 forwards
bedrock_tagsfrom config or optional params onto the BedrockCreateModelInvocationJobrequest, validated through a PydanticTypeAdapter, and addsbedrock_tagsto the banned request-body params so the value stays admin-controlled.The proxy-extras bump is the reason this is not a code-only backport.
0.4.79is already published and pins the pre-migration package, and0.4.80is taken by the nightly on the 1.95.0 line, so0.4.79.post1is the only version that both carries the new migrations and stays out of that line's way.cz bumprewrote the subpackage version, the root pin, and the lock in one commit, matching how #33855 did the previous bump on this branch.Linear ticket
Pre-Submission checklist
What is included
feat(spend): track prompt compression saved tokens in daily spend aggregates, cherry-picked from3f3295b3feat(bedrock): forward bedrock_tags to CreateModelInvocationJob for batch jobs, cherry-picked from3819ee5dwith-m 1bump: version 0.4.79 -> 0.4.79.post1coveringlitellm-proxy-extras/pyproject.toml, the rootpyproject.tomlpin, anduv.lockBoth picks are patch-id identical to their staging sources, so there are no adaptation notes. The root
litellmversion stays at1.94.0; this branch keepspyprojectpinned acrossdev.Nandrc.N, and the rc tag carries the release identity.UI rebuild still outstanding
Three production
.tsxfiles are in this branch and the Next.js bundle has NOT been rebuilt here:ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.tsxui/litellm-dashboard/src/app/(dashboard)/cost-optimization/page.tsxui/litellm-dashboard/src/components/leftnav.tsxThe proxy serves the prebuilt bundle from
litellm/proxy/_experimental/out/, and--reloadnever runsnext build, so without a rebuild the Cost Optimization page and its left-nav entry will not appear on a deployed image. The rebuild is being handled separately and must land on this branch before it ships.Known noise on this line
The full
tests/test_litellmsuite on therc/1.94.0tip already fails 79 tests with 3 collection errors before any of these picks, largely live-API tests and duplicate-basename collisions. Judge CI as a delta against that, not in absolute terms.Screenshots / Proof of Fix
All verification is complete. Every signal is judged as a delta against a baseline captured on the line tip at 6c6a07c before picking.
Targeted suite:
All 64 new tests from both PRs pass on this line, and no test that passed at baseline regressed.
Full
tests/test_litellmsuite, same delta treatment:Four failures appear only in the post-pick run. Three of them pass when re-run in isolation, and the fourth fails identically on both trees, so the delta is clean. The line carries substantial pre-existing failure noise from live-API tests and duplicate-basename collection errors, which is why the counts are read as a set difference rather than in absolute terms.
Live proxy against a Postgres migrated in two stages, first to the current
rc/1.94.0tip and then to this branch, to confirm the new migrations apply on top of a database that already ranv1.94.0-rc.2:20260717000000_add_compression_saved_tokenssorts lexicographically behind20260717000000_add_mcp_server_oauth_client_table, which is already applied on any deployment running rc.2. It applies without complaint, and all 18 columns land across the six daily spend tables.A real completion, then the daily activity read path:
Real spend flows through the widened daily spend write path and the three new fields read back, so the added
BaseDailySpendTransactionkeys do not break the pre-existing writer.The
bedrock_tagspath from #33733, with a control request to show it is not blanket-rejecting:A subagent gauntlet run over the two picks cleared identifier resolution and the
BaseDailySpendTransactionquestion, and separately flagged that_saved_tokens_or_zeroincompression_savings.pyscreens negatives but not NaN or Infinity, so a non-finitetokens_savedwould raise insideint()and be swallowed by the broad handler in_batch_database_updates. That code is byte-identical to the staging source, and reading the upstream compression service confirms it builds the field as an integer subtraction of tokenizer counts, hardcodes zero on every error path, and guards the only division in the response, so it cannot emit a non-finite value. The two shapes it can produce, zero and negative, are both already handled. Tracking the missing guard as a separate hardening change on staging rather than diverging this branch from its source.Type
🆕 New Feature
Changes
Two cherry-picks plus a proxy-extras version bump. No hand-written code; every commit is either a
-xcherry-pick traceable tolitellm_internal_stagingor thecz bumpoutput.Final Attestation