chore(release): backport #33899, #33978, #34582, #34675 to rc/1.94.0 and bump litellm-proxy-extras to 0.4.79.post2 - #34855
Conversation
* feat(ui): add configuration tabs to Cost Optimization page Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * feat(ui): reuse AutoRouter v2 and Router Settings prompt-caching panel in Cost Optimization; clarify Headroom compression Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * feat(ui): add experimental dashboard banner with feedback discussion link to Cost Optimization Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * feat(ui): add savings methodology note and per-key/team compression enterprise callout to Cost Optimization Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(ui): assert active tab state in Cost Optimization tab-switch test 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 3456148)
feat(cost-optimization): add spend-by-tool and cache leakage views (cherry picked from commit 43e7b96)
fix(proxy): cap /v1/tool/spend window at 30 days and bound every SpendLogs read (cherry picked from commit b9b27c2)
fix(proxy): roll up tool spend daily instead of scanning SpendLogs (cherry picked from commit 9bb75d6)
Ships the 20260724000000_add_spend_log_tool_index_start_time_idx and 20260725000000_add_daily_tool_spend migrations, which live in that package
|
Too many files changed for review. ( |
|
|
|
Two verification findings, both checked against 1. #34582's window bound does not survive the stack. #34582 adds Scoping this correctly, because my first pass overstated it. The endpoint reads only What genuinely remains is narrower: #34582 still earns its place in the stack: its tool-index retention half survives, and #34675's diff is written against a tree where it landed. 2. The retry narrowing changes spend-write durability on upgrade. #34675 moves eight retry arms from A transient This is deliberate upstream and the tradeoff is documented in |
Relevant issues
Backports the Cost Optimization tool-spend surface onto
rc/1.94.0as a chain, so the line gets the dashboard together with the rollup that keeps it offLiteLLM_SpendLogsThe line was cut on 2026-07-18, before any of this existed on
litellm_internal_staging. #33899 added the Cost Optimization tabs on 07-20, #33978 added the spend-by-tool views andGET /v1/tool/spendon 07-23, #34582 bounded that endpoint's reads on 07-24, and #34675 replaced them with a daily rollup on 07-27. #34675 alone does not apply here; the endpoint it rewrites does not exist on this line, so the four land together or not at allThe ordering matters for a reviewer: #33978 is what introduces the unbounded
query_raw ... JOIN "LiteLLM_SpendLogs"inget_tool_spend, which is the read this line must never ship at customer scale. #34582 bounds it and #34675 removes it, so the endpoint ends up readingLiteLLM_DailyToolSpendwith no SpendLogs access at all. Taking the first two without the last two would put a full-table scan on the dashboard's load pathNo root version bump. This line pins
pyprojectat1.94.0acrossrc.Nand ships asv1.94.0-rc.N; the tip is untagged andv1.94.0-rc.3sits behind it, so anrc.4would tag the tip. Onlylitellm-proxy-extrasmoves,0.4.79.post1->0.4.79.post2, because the two new migrations live in that packageLinear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaito re-request a review after pushing changes)What is included
Cherry-picks in staging merge order, each carrying its
-xfooter:feat(ui): add configuration tabs to the Cost Optimization page(34561482ed)feat(cost-optimization): add spend-by-tool and cache leakage views(43e7b96b83)fix(proxy): cap /v1/tool/spend window at 30 days and bound every SpendLogs read(b9b27c2beb)fix(proxy): roll up tool spend daily instead of scanning SpendLogs(9bb75d67af)Then two generated commits:
chore: update Next.js build artifacts, the line's own Node v20 rebuild oflitellm/proxy/_experimental/out/, without which the proxy would keep serving the pre-backport bundle and the dashboard would never appearchore: bump litellm-proxy-extras to 0.4.79.post2, covering20260724000000_add_spend_log_tool_index_start_time_idxand20260725000000_add_daily_tool_spendPicks 1 through 3 applied verbatim (patch-id identical to their staging sources). Pick 4 is adapted; see below
Adaptation notes (#34675 only)
Six conflicts, all in UI files and all caused by staging drift the line predates, none semantic. The Python side applied without a single conflict
UsageTab.tsx,UsageTab.test.tsx,CostOptimizationView.activity.test.tsx: the shared charts module import moved fromDEFAULT_COLOR_CYCLEtoCustomLegend+SEQUENTIAL_COLOR_RAMP. Took the incoming side; both symbols resolve here because fix(proxy): roll up tool spend daily instead of scanning SpendLogs #34675's owncolors.tsandindex.tshunks applied cleanly, and both are genuinely used by itsUsageTabhunksUsageTab.test.tsxalso dropped theshows no cap note ...test, which pinned fix(proxy): cap /v1/tool/spend window at 30 days and bound every SpendLogs read #34582's clamp that fix(proxy): roll up tool spend daily instead of scanning SpendLogs #34675 deletesUsageTab.test.tsxone further divergence from the staging source: staging'srenderWithtakes an options object ({ toolSpend }) after a later PR changed its signature; on this line it still takes a positionaltoolSpend, so the call was adapted. Without this the test renders no chart and times outbar_chart.tsx: took fix(proxy): roll up tool spend daily instead of scanning SpendLogs #34675'scolorByDatumfills line only. The incoming side also carried an unrelated empty-state block from a later staging PR, which no picked test exercises, so it was left outToolDetail.tsx: took only theRecent logs->Recent invocationsrename. The incoming side also carried the shadcn token migration (bg-card, lucideHistory) that this line predates, so the line's existing styling is kepteslint-suppressions.json: net change against the line is zero. The incoming block listed suppressions this line does not need, andeslint --prune-suppressionsremoved them againKnown noise on this line
Judged as deltas against the line tip, captured before any pick:
npx tsc --noEmit: 46 files report errors at the tip, all of them test files. None is a file this backport touchesnpx eslint .: 0 errors, 2955 warnings, all pre-existingScreenshots / Proof of Fix
All captured at
7fec728c7f, against a live proxy running from this branch's own worktree venv (solitellm_proxy_extrasresolves to the branch, not a shared checkout) on an isolated Postgres started withlog_statement=all, hitting the real Anthropic APIBoth migrations apply on a database whose latest was
20260718000000_add_savings_spendInvoked-not-declared attribution, with prompt storage OFF. One request declaring five tools where the model calls one.
STORE_PROMPTS_IN_SPEND_LOGSis unset, which is the point: the old payload-mining path needed it for non-MCP coverageOne row, not five. The declared-tools mining would have attributed the full request cost to all five
The read path never touches SpendLogs. The endpoint serves from the rollup
Measured from the Postgres statement log rather than asserted from the source. The slice is bounded by the log line count before and after the call, with a positive control so the absence is not vacuous
Tests, judged as deltas against the line tip
Targeted suite 369 passed / 0 failed at the tip and 419 passed / 0 failed here, so the picks add 50 tests and break none. Full mirrored suite shows zero new failures; eight looked new until matched-set isolation re-runs showed the same result on both trees, leaving one failure (
test_s3_cache_concurrent_async_operations) that fails at the tip too. Vitest over the cost-optimization and shared-chart components is 14 files and 70 tests green.ruff format --checkandruff checkclean on all 11 changedlitellm/files.tscandeslintdeltas are zero; both are red at the tip in files this PR does not touchA deep adversarial review over eight lenses returned two findings, both reproduced and both verified byte-identical to staging rather than introduced here. They are written up in a comment on this PR
Type
🆕 New Feature
🐛 Bug Fix
Changes
The read path is the point. Before this chain
rc/1.94.0has no/v1/tool/spendat all; after it, the endpoint exists and serves fromLiteLLM_DailyToolSpend, a per(date, tool_name)rollup written at request time, so a Cost Optimization load never touchesLiteLLM_SpendLogsTool attribution counts invoked tools rather than declared ones. The index writer previously mined the request body, so a request declaring five tools while the model called one attributed its full spend to all five. Names now come from the response's
tool_callsthrough the sharedget_tool_calls_from_responsenormalizer, which also means non-MCP coverage no longer depends onstore_prompts_in_spend_logsSpend writers retry only
ConnectError, throughDB_RETRY_SAFE_ERROR_TYPES, because that is the failure that proves the statements never reached the database. Ambiguous post-send timeouts drop the batch with an error log rather than risk applying an increment twiceQA runbook
python litellm/proxy/proxy_cli.py --config litellm/proxy/dev_config.yaml --detailed_debug --use_v2_migration_resolveron this branch; the startup log should showApplying migration 20260724000000_add_spend_log_tool_index_start_time_idxandApplying migration 20260725000000_add_daily_tool_spendSELECT * FROM "LiteLLM_DailyToolSpend"should hold one row for the invoked tool only, with spend equal to the request's cost/v1/messagesrequest with tools and nostore_prompts_in_spend_logsset anywhere; its invoked tool should also land in the rollupFinal Attestation