Skip to content

feat(sgr): make the gateway middleware the source of truth for successful requests - #35717

Merged
yassin-berriai merged 1 commit into
litellm_internal_stagingfrom
litellm_sgr_gateway_request_table
Aug 5, 2026
Merged

feat(sgr): make the gateway middleware the source of truth for successful requests#35717
yassin-berriai merged 1 commit into
litellm_internal_stagingfrom
litellm_sgr_gateway_request_table

Conversation

@yassin-berriai

@yassin-berriai yassin-berriai commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • SGR (successful gateway requests) has two definitions that disagree, and the dashboard reads the weaker one
  • The Usage tiles derive it from SpendLogs, so they count what litellm's logging callbacks observed and could attribute, not what the gateway answered
  • That count goes to zero when spend logging is off or the database is unavailable
  • The request-metrics middleware already counts the real thing at the ASGI edge, but only exported it to OTLP for enterprise metering

How it solves it:

  • New LiteLLM_DailyGatewayRequests table written by the middleware
  • The Successful Requests and Failed Requests tiles read that table
  • New by-endpoint breakdown chart the SpendLogs path cannot produce
  • The old SpendLogs path is left running, marked with TODOs

Relevant issues

Linear ticket

Resolves LIT-5147

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Live proxy on real Postgres against the real OpenAI API, with real spend. The proxy logged Enterprise billing metrics disabled, so this is the unlicensed path, which is the case the middleware's old early return would have switched the counter off in

Both runs used the same config with disable_spend_updates: true, which is the case the ticket is about: a deployment that turned spend tracking off still wants to know how many requests it served. Both sent the same 14 requests, 7 chat completions and 4 embeddings that succeed and 3 for a model that does not exist. The only difference is the branch

$ for i in 1 2 3 4 5 6 7; do curl -s -o /dev/null -w "chat #$i -> %{http_code}\n" $P/v1/chat/completions -H "$K" -H "$J" \
    -d "{\"model\":\"gpt-5.6\",\"messages\":[{\"role\":\"user\",\"content\":\"say ok $i\"}]}"; done
chat #1 -> 200 ... chat #7 -> 200

$ for i in 1 2 3 4; do curl -s -o /dev/null -w "embedding #$i -> %{http_code}\n" $P/v1/embeddings -H "$K" -H "$J" \
    -d "{\"model\":\"text-embedding-3-small\",\"input\":\"gateway counting sample $i\"}"; done
embedding #1 -> 200 ... embedding #4 -> 200

$ for i in 1 2 3; do curl -s -o /dev/null -w "failed chat #$i -> %{http_code}\n" $P/v1/chat/completions -H "$K" -H "$J" \
    -d '{"model":"model-that-does-not-exist","messages":[{"role":"user","content":"hi"}]}'; done
failed chat #1 -> 400
failed chat #2 -> 400
failed chat #3 -> 400

On litellm_internal_staging, 14 answered requests leave nothing behind and the dashboard reports zero:

$ psql -c 'SELECT (SELECT count(*) FROM "LiteLLM_SpendLogs") AS spend_logs,
                  (SELECT count(*) FROM "LiteLLM_DailyUserSpend") AS daily_user_spend;'
 spend_logs | daily_user_spend
------------+------------------
          0 |                0
$ curl -s $P/gateway/daily/activity -H "$K" -o /dev/null -w '%{http_code}\n'
404

Usage tiles on the base branch, all zero

On this branch the spend tables are still empty, and the counts are there anyway:

$ psql -c 'SELECT (SELECT count(*) FROM "LiteLLM_SpendLogs") AS spend_logs,
                  (SELECT count(*) FROM "LiteLLM_DailyUserSpend") AS daily_user_spend;'
 spend_logs | daily_user_spend
------------+------------------
          0 |                0

$ psql -c 'SELECT date, category, route, successful_requests, failed_requests
           FROM "LiteLLM_DailyGatewayRequests" ORDER BY route;'
    date    | category |       route       | successful_requests | failed_requests
------------+----------+-------------------+---------------------+-----------------
 2026-08-05 | llm      | /chat/completions |                   7 |               3
 2026-08-05 | llm      | /embeddings       |                   4 |               0

$ curl -s $P/gateway/daily/activity -H "$K"
{
    "total_successful_requests": 11,
    "total_failed_requests": 3,
    "by_date": [{"date": "2026-08-05", "successful_requests": 11, "failed_requests": 3}],
    "by_route": [
        {"category": "llm", "route": "/chat/completions", "successful_requests": 7, "failed_requests": 3},
        {"category": "llm", "route": "/embeddings", "successful_requests": 4, "failed_requests": 0}
    ]
}

Usage tiles on this branch, 11 successful and 3 failed

The new chart, which the spend path cannot produce at all when spend logging is off:

Gateway Requests by Endpoint chart

Full pages for context, base and this branch

A key without an admin role is refused. Two independent gates cover this; the route-level management-route check answers first, which is why the status is 401 rather than the handler's own 403:

$ NEWKEY=$(curl -s $P/key/generate -H "$K" -d '{"models":["gpt-5.6"]}' | jq -r .key)
$ curl -s -w '%{http_code}\n' $P/gateway/daily/activity -H "Authorization: Bearer $NEWKEY"
{"error":{"message":"Authentication Error, Only proxy admin can be used to generate, delete, update info
 for new keys/users/teams. Route=/gateway/daily/activity. Your role=unknown. ...","code":"401"}}
401

The table as the migration file creates it, piped into an empty database rather than pushed from schema.prisma:

$ psql -d litellm_mig -f litellm-proxy-extras/.../20260803000000_add_daily_gateway_requests/migration.sql
CREATE TABLE
CREATE INDEX
$ psql -d litellm_mig -c '\d "LiteLLM_DailyGatewayRequests"'
 date                | text   | not null
 category            | text   | not null
 route               | text   | not null
 successful_requests | bigint | not null | 0
 failed_requests     | bigint | not null | 0
Indexes:
    "LiteLLM_DailyGatewayRequests_pkey" PRIMARY KEY, btree (date, category, route)
    "LiteLLM_DailyGatewayRequests_date_idx" btree (date)

Type

🆕 New Feature

Changes

SGR has two independent definitions. The admin UI derives it from SpendLogs, so it counts what litellm's logging callbacks observed and could attribute and price. BillableRequestMetricsMiddleware counts what the proxy actually answered at the ASGI edge, but only exported to OTLP for enterprise metering. This makes the middleware the source of truth for the dashboard

Table. LiteLLM_DailyGatewayRequests, keyed (date, category, route) with successful_requests and failed_requests. Added to all three schema.prisma copies plus a migration following the add_daily_tool_spend precedent

Every part of that key is chosen by the proxy and drawn from a closed set: the date is the server clock, the category is a three-value enum, and the route is one of a fixed list the classifier maps paths onto rather than the raw path. So the table's size is a function of how long the deployment has been running and how many route classes it serves, never of traffic. There is deliberately no deployment dimension, for two reasons. The read endpoint groups by (date, category, route) and never selects it, so it would be write-only. And the deployment id available at the ASGI edge, the x-litellm-model-id header, is a sha256 over the deployment's litellm_params including credentials, so a caller who puts a credential in the request body mints a fresh one per distinct value; api_base and base_url need allow_client_side_credentials, but api_key is not on _BANNED_REQUEST_BODY_PARAMS and needs no configuration at all. Keying on it would let a caller grow a persisted table

Write path. Requests fold into an in-memory map at record time rather than going through a queue like the spend path. A count is a pure aggregate and the fold is bounded as above, whereas the spend queue caps at 1000 items and blocks when full, which is not acceptable in the response path. A scheduler job drains it on the existing batch interval, with a final flush on shutdown that sits inside the same if prisma_client: guard as disconnect() and immediately before it; a write attempted after disconnect raises ClientNotConnectedError and loses the interval silently, so a test pins the call order. A failed flush merges its counts back into the fold and retries on the next tick, mirroring the neighbouring tool-spend writer, so a database blip undercounts nothing. That buys at-least-once rather than exactly-once and the docstring says so: the batch commits inside its context manager's __aexit__, so a failure raised after the transaction committed restores counts that are already persisted and the next flush increments them again. For a traffic-volume metric a rare overcount on a dropped acknowledgement beats losing a whole interval to every blip

Many workers and many pods. The accumulator is per-process state, so the flush job is deliberately not behind the PodLockManager that gates the key-rotation and spend-reset jobs: every worker and every pod runs its own scheduler and drains its own fold, where a lock would leave every non-holder's counts stranded in memory. Correctness across processes comes from the write rather than from coordination. Each row goes out as one statement, INSERT ... ON CONFLICT ("date","category","route") DO UPDATE SET "successful_requests" = ("successful_requests" + $n), so the arithmetic happens server side under the row lock rather than as a read followed by a write, and any number of processes summing into one row is correct however they interleave. The same statement covers two processes racing to create a row that does not exist yet, since the conflict resolves into the increment instead of a unique violation. Each flush also sorts its rows by (date, category, route) so concurrent writers take row locks in the same order, which a test pins

Verified on a live proxy at --num_workers 4, four distinct worker processes: 24 concurrent requests produced exactly 24 in one row, then 8 more followed immediately by SIGTERM well inside the flush interval produced 32, so each worker's shutdown drain lands. The cost of holding counts in memory is bounded by that interval: a graceful shutdown drains it, while an ungraceful kill (OOM, or a scale-down that outruns terminationGracePeriodSeconds) loses at most one interval for that one process

Middleware. It classifies once and fans out to two sinks, and returns early only when both are absent, which is what lets the SGR sink run on an unlicensed deployment. The billing recorder keeps its 2xx-only gate and its model id, unchanged. The SGR sink takes every status so failed_requests is real, and is not handed the model id at all: the recorder exports to a metering backend that can drop a series, while the sink writes a row that stays

Read and UI. GET /gateway/daily/activity is restricted to proxy admin roles, because the table carries no key, user or team dimension and the totals are deployment-wide. The Successful Requests and Failed Requests tiles read it, with a tooltip saying they will not match the per-key and per-model breakdowns below, and there is a new stacked Gateway Requests by Endpoint chart. Both tiles read the same source deliberately, or the tile row contradicts the chart directly beneath it. The per-key and per-model charts keep reading the daily spend tables. schema.d.ts regenerated via npm run gen:api

Range staleness. Every source the request tiles can read is stamped with the range it answers and selected during render: the gateway counts, the spend aggregate, and the failure flag that gates the paginated pages behind it. Clearing state inside a fetch effect is one render too late, since an effect runs after the render that follows a range change and that render still holds the previous range's numbers. All three need it because the tiles read the first and fall through to the second and then the third, so a stamp on any one of them is escaped by the next. The aggregate's key also names the selected user, since the same dates describe different numbers for different users. The rule, the range key and the chart's route folding live in gatewayActivity.ts with unit tests, following this directory's convention of testing logic directly rather than through a render

Two deliberate choices worth calling out. The DB write is not license-gated even though the OTLP export is, otherwise the dashboard would blank on OSS. And a non-admin keeps seeing the old per-user numbers, since the new table cannot answer "my requests"

One inbound request is counted once however many upstream calls litellm made to serve it, so router retries, fallbacks and internal fan-out do not inflate the total

Docs. BerriAI/litellm-docs#768 documents the new table, the endpoint and why the gateway counts deliberately do not tie out against the spend rollups. Merge this first

Mutation results

Fifteen backend mutants, all killed:

Mutation Result
sink records only on 2xx 4 failed
middleware returns early when only the billing recorder is absent 3 failed
billing recorder records every status 6 failed
the serving deployment added to the key, so a caller can mint rows 1 failed
success boundary widened past 2xx 1 failed
deterministic commit ordering removed 1 failed
drain stops resetting the fold 3 failed
failed flush drops counts instead of restoring 2 failed
shutdown drain moved after prisma_client.disconnect() 1 failed
admin-only gate also accepts view-only 1 failed
default lookback shortened from 30 days 1 failed
by_route ordered ascending instead of busiest first 2 failed
by_route folds every category together 1 failed
caller-supplied dates ignored 1 failed
the db-not-connected guard removed 1 failed

Seven frontend mutants, all killed. The first two drive the page rather than the helper, because the defect they pin is in the wiring:

Mutation Result
the spend fallback read without checking its range stamp 1 failed
the failure flag read without checking its range stamp, so the paginated pages leak 1 failed
drop the range-key guard, so a stale range renders 1 failed
drop the admin guard 1 failed
widen the top-N route cap 1 failed
stop prefixing non-llm routes with their category 1 failed
range key ignores the end date 2 failed

157 backend tests across the three feature test files and 48 frontend tests pass unmutated, plus the shutdown-ordering tests in test_lifecycle.py, 189 backend in total. The full dashboard suite is 6169 passing across 585 files. gateway_request_endpoints.py, gateway_request_tracking.py, billable_request_metrics_middleware.py and types/proxy/gateway_requests.py are at 100% line coverage

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

@yassin-berriai
yassin-berriai requested a review from a team August 3, 2026 23:16
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR makes middleware-recorded gateway responses the source of truth for deployment-wide successful and failed request metrics.

  • Adds a bounded daily gateway request accumulator, persistence table, migration, and admin-only activity endpoint
  • Updates the usage dashboard with range-stamped data selection, gateway-backed request tiles, and an endpoint breakdown chart
  • Adds backend, lifecycle, middleware, endpoint, and dashboard regression coverage

Confidence Score: 5/5

The PR appears safe to merge

No blocking failure remains, and the previously reported stale gateway, aggregate, and paginated fallback paths are now withheld during date or user transitions

Important Files Changed

Filename Overview
litellm/proxy/db/gateway_request_tracking.py Adds bounded in-memory request aggregation, retry restoration, and batched Prisma upserts without a confirmed correctness issue
litellm/proxy/middleware/billable_request_metrics_middleware.py Fans classified responses out to gateway and billing sinks while preserving the billing sink's successful-response behavior
litellm/proxy/management_endpoints/gateway_request_endpoints.py Adds an admin-restricted deployment-wide request-count endpoint with typed query validation and date and route aggregation
litellm/proxy/proxy_server.py Wires request accumulation into middleware, scheduled persistence, and shutdown ordering
litellm-proxy-extras/litellm_proxy_extras/migrations/20260803000000_add_daily_gateway_requests/migration.sql Creates the daily gateway request table with the intended bounded composite primary key and date index
ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.tsx Uses render-time range stamps for all fallback sources and displays deployment-wide gateway request totals and endpoint activity for administrators
ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/gatewayActivity.ts Centralizes range-key selection and endpoint chart shaping with no remaining stale-range failure identified
ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.test.tsx Adds component-level regression coverage for stale aggregate and paginated fallback behavior during range changes

Reviews (11): Last reviewed commit: "feat(sgr): make the gateway middleware t..." | Re-trigger Greptile

@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_sgr_gateway_request_table (5e5844a) with litellm_internal_staging (2792887)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (d3d3035) during the generation of this report, so 2792887 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please review the current head b4c3abe112. Since your 4/5 the branch was rebased onto current staging and the UI finding is fixed: a fetched gateway result now carries the date range it was fetched for, and the render selects it only when that stamp matches the range currently picked, so a previous range's counts are unrepresentable rather than merely unlikely. That rule, the range key and the chart's route folding moved into gatewayActivity.ts with unit tests. The Failed Requests tile also now reads the gateway counter alongside Successful Requests; pointing only one of them at it made the tile row contradict the chart directly below, which the live capture caught.

@yassin-berriai
yassin-berriai force-pushed the litellm_sgr_gateway_request_table branch from b4c3abe to 02b895e Compare August 4, 2026 22:24
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please review the current head 02b895e56b. The only change since the 5/5 on b4c3abe112 is one added test file: gateway_request_endpoints.py had no mapped test and sat at 57% line coverage, which codecov flagged correctly. It is at 100% now, with six mutants killed (admin gate widened to view-only, shortened default lookback, by_route ordered ascending, by_route folding categories together, caller dates ignored, db-not-connected guard removed). Production code is byte-identical to the commit you scored.

@yassin-berriai
yassin-berriai force-pushed the litellm_sgr_gateway_request_table branch from 02b895e to 0bf4166 Compare August 4, 2026 22:31
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please review the current head 0bf4166f2a. Production code is unchanged from the 02b895e56b you just scored 5/5; the only delta is the test file, which had a flake I introduced. test_omitted_dates_fall_back_to_the_default_window recomputed the expected window in the assertion after the request had already computed its own, so a run crossing UTC midnight between the two evaluations would fail on a one-day-off mismatch. Both date assertions now pin the clock, against a date in the past so they cannot pass on a day the fixture silently failed to patch; neutering the fixture fails them. The file also now pins throwaway DATABASE_URL and LITELLM_MASTER_KEY before importing proxy_server and restores the prior values, matching test_component_allowlists.py, so it does not depend on a tier-zero runner having them set.

@yassin-berriai
yassin-berriai force-pushed the litellm_sgr_gateway_request_table branch from 0bf4166 to 42f98c3 Compare August 4, 2026 22:36
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please review the current head 42f98c35c2. The only delta from the 02b895e56b you scored 5/5 is one test file plus a docstring; the executable code is identical (parsing both with docstrings stripped gives the same AST). The docstring names a semantic the module previously left implicit: because the batch commits in its context manager's __aexit__, a failure raised after the transaction committed restores counts that are already persisted and the next flush increments them again, so the retry is at-least-once rather than exactly-once. Exactly-once would need a dedup key the upserts could ignore on replay; for a traffic-volume metric a rare overcount beats losing an interval to every database blip, so the trade is deliberate and is now written down instead of only the guarantee it buys.

@yucheng-berri

Copy link
Copy Markdown
Contributor

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 42f98c3. Configure here.

Comment thread litellm/proxy/proxy_server.py Outdated

# same for SGR: the fold lives in memory, so an un-drained interval is lost
if prisma_client is not None:
await flush_gateway_requests(prisma_client, gateway_request_accumulator)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shutdown flush after DB disconnect

High Severity

During proxy shutdown, flush_gateway_requests is called after prisma_client has already disconnected. This prevents the final write of accumulated Gateway Request (SGR) counts to the database, resulting in the loss of up to one batch interval of SGR data on every proxy restart or deploy.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 42f98c3. Configure here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in ce7e8442be. This was real: proxy_shutdown_event disconnected prisma at the top and the SGR drain ran further down, so the final flush always raised ClientNotConnectedError. I verified that rather than assuming it, by driving a batch upsert on a disconnected client: ClientNotConnectedError: Client is not connected to the query engine. Because flush_gateway_requests catches Exception and merges the counts back onto an accumulator the process is about to discard, it failed silently, so up to one batch interval was lost on every restart exactly as you described.

The drain now runs inside the same if prisma_client: guard, immediately before disconnect(), so the two cannot drift apart again. Added a regression test in tests/test_litellm/proxy/proxy_server/test_lifecycle.py that asserts the call order rather than merely that both ran, plus one covering the no-database case; restoring the original ordering fails exactly that first test and nothing else.

Worth noting this was missed by a Greptile 5/5 and by my live-proxy capture, which never exercised shutdown. Good catch.

@yassin-berriai
yassin-berriai force-pushed the litellm_sgr_gateway_request_table branch from 42f98c3 to ce7e844 Compare August 4, 2026 23:29
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please review the current head ce7e8442be. Cursor Bugbot found a real High-severity bug that this pass and the previous 5/5 both missed, so it is worth re-reviewing rather than carrying the score forward: proxy_shutdown_event disconnected prisma at the top of the function while the SGR drain ran further down, so the shutdown flush always raised ClientNotConnectedError, was swallowed by the flush's own except Exception, and lost up to one batch interval on every restart. I confirmed the failure mode by driving a batch upsert on a disconnected client rather than reasoning about it. The drain now sits inside the same if prisma_client: guard immediately before disconnect(), with a regression test asserting the call ORDER (restoring the old ordering fails it) and a second covering the no-database case.

Comment thread litellm/proxy/db/gateway_request_tracking.py Outdated
@veria-ai

veria-ai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

@yassin-berriai
yassin-berriai force-pushed the litellm_sgr_gateway_request_table branch from ce7e844 to 68a77a0 Compare August 5, 2026 17:34
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please review the current head 68a77a06be. No code change from the ce7e8442be you scored 5/5; this is a rebase onto current staging to clear a ui-unit-tests failure that was base drift rather than a defect. Staging added a "template presets" suite to add_auto_router_tab.test.tsx after my branch point, and since CI evaluates the merge ref those new tests ran against my tree. The mechanism was that both staging and this branch had edited the generated ui/litellm-dashboard/src/lib/http/schema.d.ts, so git merged the two textually without reporting a conflict and produced a file that was neither side's. Rebasing applies my hunks on top of staging's version instead; regenerating with npm run gen:api afterwards produces a byte-identical file, which confirms the committed one is now correct. That suite is 26/26 locally and the full changed-file run is 3550 passing.

@yassin-berriai
yassin-berriai force-pushed the litellm_sgr_gateway_request_table branch from 68a77a0 to 0e0b2ff Compare August 5, 2026 17:44
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

Note on frontend-lint, which is red on this PR for a reason that is not in this diff.

The committed ceilings on staging are complexity: 121 and local/no-large-inline-object-arg: 469, but staging's own counts are already 122 and 471, so the gate fails for any PR that touches a frontend file. I measured this rather than inferred it: running npx eslint . -f json on a clean origin/litellm_internal_staging worktree gives 122 and 471, and this branch now gives exactly 122 and 471 too, so this PR's delta on both rules is zero. #35956 is failing on the identical two lines, and #35960 (chore(ui): sync dashboard eslint budgets after crossed staging merges) is the fix; its merge tree reads max: 122 and max: 471 with zero headroom.

This branch did add one no-large-inline-object-arg violation of its own, an inline 10-property session object in UsagePageView.test.tsx. That is fixed in 0e0b2ff7aa by hoisting it to a named nonAdminSession fixture, which reads better anyway. I have deliberately not touched eslint-budgets.json; raising a ceiling to absorb someone else's drift does not belong in a feature PR, so frontend-lint here should clear once #35960 merges.

@yassin-berriai
yassin-berriai force-pushed the litellm_sgr_gateway_request_table branch from 0e0b2ff to 97d24af Compare August 5, 2026 17:51
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please review the current head 97d24af8da. Since your 5/5 the branch was rebased onto current staging and the one open veria finding is fixed, so the delta is worth a fresh pass rather than carrying the score forward.

LiteLLM_DailyGatewayRequests is now keyed (date, category, route). It previously also carried the serving deployment, taken from the x-litellm-model-id response header, and veria was right that a caller can vary it. I measured that rather than reasoning about it: the deployment id is a hash over the deployment's litellm_params, so with general_settings.allow_client_side_credentials: true three requests to /v1/chat/completions differing only in a body-supplied api_base came back with three distinct ids and wrote three separate rows on the previous head, against one row now. Without that opt-in the proxy rejects the request outright ("api_base is not allowed in request body"), so the finding needed an admin setting to be reachable, which is a supported configuration rather than a misconfiguration.

Dropping the column rather than pinning it to "" was the smaller change: the read endpoint's GROUP BY never included it, so the dimension was written and never read, and removing it also retires the "" sentinel and the "a composite primary key cannot hold a NULL" comment that existed only to support it. The sink Protocol no longer takes a model id at all; the billing recorder still does, since it exports to a metering backend rather than writing a row that stays.

The regression test drives three requests through the real ASGI middleware into a real accumulator with a different x-litellm-model-id on each response and asserts one key. Restoring model_id on GatewayRequestKey, on GatewayRequestAccumulator.record and on the middleware's call into the sink fails exactly that test. The live before/after and the migration's \d output are in the PR body.

@yassin-berriai
yassin-berriai force-pushed the litellm_sgr_gateway_request_table branch from 97d24af to 7e41b24 Compare August 5, 2026 18:09
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please review the current head 7e41b24710. This picks up your 4/5 as well; the previous comment covered the veria cardinality finding only, and the tile fallback is fixed here.

You were right about the mechanism. setAggregatedData(null) sits inside the fetch effect, and an effect runs after the render that follows a date change, so that render still holds the previous range's aggregate and can paint it. Withholding a superseded gateway result while the fallback stayed stale meant the tile showed the previous range's number by the other route, which is the guarantee the range key exists to provide.

The aggregate now carries its own range key and is selected during render through the same helper as the gateway counts, so both go through one rule rather than one rule and one effect. Its key also carries the selected user, since the same dates describe different numbers for different users and the effect's clear was the only thing covering that. The redundant setAggregatedData(null) is gone with it, which removes a render pass per range change. Total Requests and Average Cost per Request read the same aggregate, so they stop showing the previous range too.

The regression test drives the page rather than the helper, because the defect is in the wiring: it resolves one range, changes the date with the next fetch held open, and asserts the first range's total is off screen before releasing it. Reading the aggregate without checking its stamp fails it. The helper's own rule keeps its unit tests, including the new one covering two users over identical dates.

@yassin-berriai
yassin-berriai force-pushed the litellm_sgr_gateway_request_table branch from 7e41b24 to c576b7e Compare August 5, 2026 18:30
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please review the current head c576b7ef05. Your 4/5 on 7e41b24710 was right and this fixes it.

aggregatedFailed was a bare boolean, so on the render following a range change it still held the previous range's failure and the paginated pages behind it were read. It is now a stamped value like the two data sources, selected through the same helper. That keeps the paginated hook disabled while a new range is in flight, and disabled is what empties it, so its previous rows cannot reach a tile.

usePaginatedDailyActivity.ts is unchanged on purpose. It already resets to EMPTY_DATA whenever enabled goes false and it already discards stale in-flight pages by fetch id, so gating enabled on the stamped failure is sufficient; stamping inside the hook as well would be a second mechanism for the same rule. If you think there is a path through it that survives enabled: false, name it and I will drive it rather than argue.

That is three sources now behind one rule, which was the point of your finding: the tiles read the gateway counts, fall through to the aggregate, and fall through again to the paginated pages, so a stamp on any one of them is escaped by the next. The new test drives the page rather than the helper, since the defect lives in the wiring: it fails the first range's aggregated call so the paginated pages render, changes the range with the second aggregated call held open, and asserts the first range's total is off screen before releasing it. Reverting the flag to aggregatedFailure !== null fails exactly that test.

@yassin-berriai

Copy link
Copy Markdown
Contributor Author

Correcting myself on the cardinality finding, since I put a wrong claim on this PR earlier today.

I wrote that it "needed an admin setting to be reachable", based on a live probe where a body-level api_base came back 401 with "api_base is not allowed in request body". That generalised from one parameter to the family, and it is wrong. api_base and base_url are on _BANNED_REQUEST_BODY_PARAMS in litellm/proxy/auth/auth_utils.py; api_key is not, so it is never banned and the opt-in never enters the picture for it. Both names then take the identical branch at route_llm_request.py:439 into _handle_clientside_credential, so the id minting is the same either way.

Re-measured on a live proxy with no clientside-credentials setting of any kind, using an ordinary non-admin virtual key, one model configured:

  no api_key in the body            -> 200, x-litellm-model-id: 7bf6346cff2f4b26...
  "api_key": "<a working key>"      -> 200, x-litellm-model-id: 277940395e89bacb...
  "api_key": "<the same key> "      -> 200, x-litellm-model-id: 92f333adb37cc575...

The third is that same working credential with one trailing space. OpenAI accepts it and the sha256 preimage does not, so one valid key is enough to mint unbounded ids; a caller does not need several. On the previous head each of those is its own primary-key row per day. On this head all eight requests of that run, three distinct ids across the successes, sit in a single row.

Two bounds so this is not overstated. The credential has to actually work, because a provider rejection carries no x-litellm-model-id and collapses onto the empty id. And the matching in-memory growth, one upsert_deployment router entry per distinct credential, is pre-existing and out of scope here; what this PR would have added is the durable copy in a table.

The fix does not change, and the PR body now carries the corrected version with the retraction in place rather than a quiet edit.

…sful requests

SGR has had two independent definitions. The admin UI derived it from
SpendLogs, so it counted what litellm's logging callbacks observed and could
attribute and price. BillableRequestMetricsMiddleware counted what the proxy
actually answered at the ASGI edge, but only exported to OTLP for enterprise
metering. The two disagree by design in places, and the SpendLogs figure goes
quiet whenever spend logging is disabled or the callbacks are bypassed.

This adds LiteLLM_DailyGatewayRequests, written by the middleware, and points
the dashboard's Successful Requests tile at it.

Requests fold into an in-memory map at record time rather than going through a
queue like the spend path. A count is a pure aggregate, and every dimension of
the key is chosen by the proxy from a closed set: the date, the category, and a
route that the classifier maps to one of a fixed list of strings rather than
passing the raw path through. Nothing a caller sends can add a key, so the fold
and the table are bounded by (days x categories x routes) however much traffic
arrives; the spend queue blocks once full, which is not acceptable in the
response path. A scheduler job drains it on the existing batch interval, and a
failed flush merges its counts back so a database blip undercounts nothing.

The middleware previously returned early when no billing recorder was
injected, which is the unlicensed case. The new sink is not license-gated, so
that early return now requires both sinks to be absent. The billing recorder
keeps its 2xx-only gate; the sink takes every status so failed_requests is
real. The sink is not told which deployment served the request, unlike the
billing recorder. That id is a sha256 over litellm_params, credentials
included, so a caller who puts a credential in the request body mints a fresh
one per distinct value. No configuration is needed for that: api_base and
base_url are on _BANNED_REQUEST_BODY_PARAMS and need allow_client_side_
credentials, but api_key is not on that list, and both reach the same
_handle_clientside_credential branch. The read endpoint aggregates the
dimension away regardless, so the key is better off without it.

The new table carries no key, user or team dimension, so /gateway/daily/activity
is restricted to proxy admin roles and the per-key and per-model breakdowns
keep reading the daily spend tables. The old path is left running and marked
with TODOs.

A fetched result carries the range key it was fetched for, and the render
selects it only when that key matches the range on screen. Both the gateway
counts and the spend aggregate go through that rule: the request tiles read the
first and fall through to the second, so stamping only one of them would leave
the tile showing a superseded range by the other route.

The paginated pages behind that aggregate are reached through a failure flag,
so the flag is stamped too. A flag left over from the previous range would let
those pages through while a new range is in flight, which is the same defect
one fallback further down.
@yassin-berriai
yassin-berriai force-pushed the litellm_sgr_gateway_request_table branch from c576b7e to 5e5844a Compare August 5, 2026 19:02
@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai please review the current head 5e5844abb0. The tree is byte-identical to the c576b7ef05 you scored 5/5 (git diff c576b7ef05 5e5844abb0 is empty); the only delta is the commit message, which still implied the cardinality exposure required an admin opt-in. It does not, and the message now says so, matching the correction already in the PR body. Re-anchoring rather than leaving your score pinned to a commit that is no longer in the PR.

@yassin-berriai

Copy link
Copy Markdown
Contributor Author

Flagging a stale approval before anyone acts on the "approved but unmerged" bump.

@yucheng-berri approved this at 68a77a06be. That commit is no longer in the PR, and what has landed since is not cosmetic:

  • The table's primary key changed. LiteLLM_DailyGatewayRequests was keyed (date, category, route, model_id) and is now keyed (date, category, route), with the column and its migration line removed. That came out of a veria finding that the model_id dimension was caller-influenceable; a caller putting a credential in the request body mints a fresh deployment id per distinct value, with no configuration required, and the read endpoint never selected the column anyway. Measured live: three requests to one route wrote three rows before and one row now
  • Three dashboard fixes for a stale-range defect Greptile raised over two rounds. The request tiles read the gateway counts, fall through to the spend aggregate, and fall through again to the paginated pages; only the first was range-stamped, so a superseded range could still reach a tile through either fallback. All three now go through one rule

Greptile is 5/5 on the current tree and CI is re-running after a commit-message correction, but no human has looked at the schema change. Please re-review rather than merging on the earlier approval.

@yassin-berriai
yassin-berriai merged commit 09dd167 into litellm_internal_staging Aug 5, 2026
85 of 87 checks passed
@yassin-berriai
yassin-berriai deleted the litellm_sgr_gateway_request_table branch August 5, 2026 19:40
doonga pushed a commit to greyrock-labs/home-ops that referenced this pull request Aug 17, 2026
…7.0) (#336)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/berriai/litellm](https://images.chainguard.dev/directory/image/wolfi-base/overview) ([source](https://github.com/BerriAI/litellm)) | minor | `v1.96.2` → `v1.97.0` |

---

### Release Notes

<details>
<summary>BerriAI/litellm (ghcr.io/berriai/litellm)</summary>

### [`v1.97.0`](https://github.com/BerriAI/litellm/releases/tag/v1.97.0)

[Compare Source](https://github.com/BerriAI/litellm/compare/v1.97.0...v1.97.0)

##### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.97.0
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.97.0/cosign.pub \
  ghcr.io/berriai/litellm:v1.97.0
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

##### What's Changed

- feat(proxy): resolve Cursor thinking/fast model-name suffixes on /cursor/chat/completions by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35554](https://github.com/BerriAI/litellm/pull/35554)
- fix(team-callbacks): actually stop logging when disable\_logging is called by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;35520](https://github.com/BerriAI/litellm/pull/35520)
- refactor(lint): drop redundant !s f-string conversion flags and fix displaced import-group comments by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35546](https://github.com/BerriAI/litellm/pull/35546)
- fix(proxy): backfill null user\_email on existing users during JWT auth by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;34588](https://github.com/BerriAI/litellm/pull/34588)
- feat(playground): add non-streaming response toggle by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35560](https://github.com/BerriAI/litellm/pull/35560)
- feat(teams): apply default organization to new teams from default team settings by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35540](https://github.com/BerriAI/litellm/pull/35540)
- fix(ui): block Playground page for viewer roles on direct URL access by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35676](https://github.com/BerriAI/litellm/pull/35676)
- fix(caching): close evicted LLM clients so their connections are reclaimed by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35492](https://github.com/BerriAI/litellm/pull/35492)
- chore(deps): update brace-expansion, postcss, and gitpython to current patch releases by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35692](https://github.com/BerriAI/litellm/pull/35692)
- refactor(ui): rename the create MCP server component to PascalCase by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35686](https://github.com/BerriAI/litellm/pull/35686)
- fix(openai): drop undefined Union from owns\_wrapped\_http\_client annotation by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35706](https://github.com/BerriAI/litellm/pull/35706)
- fix(openai): drop the undefined Union from owns\_wrapped\_http\_client by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35704](https://github.com/BerriAI/litellm/pull/35704)
- chore(ui): note Google's Agent Platform rename in vector store setup by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28076](https://github.com/BerriAI/litellm/pull/28076)
- fix(proxy): apply key/team router\_settings.model\_group\_alias by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35486](https://github.com/BerriAI/litellm/pull/35486)
- feat(complexity\_router): default session affinity off and expose it in the UI by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35714](https://github.com/BerriAI/litellm/pull/35714)
- fix(datadog): read team callback dd\_\* params from kwargs instead of blocked dynamic params ([#&#8203;35115](https://github.com/BerriAI/litellm/issues/35115) port) by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35687](https://github.com/BerriAI/litellm/pull/35687)
- refactor(ui): extract the MCP create form's logic and field groups by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35694](https://github.com/BerriAI/litellm/pull/35694)
- test(ui): tier the MCP create tests into unit and integration by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35697](https://github.com/BerriAI/litellm/pull/35697)
- fix(proxy): redact credential headers from request logging copies by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;35678](https://github.com/BerriAI/litellm/pull/35678)
- feat(guardrails/rubrik): prompt moderation, response-text blocking, streaming buffer, failure logging by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35722](https://github.com/BerriAI/litellm/pull/35722)
- fix(ui): render Responses API request and response in the logs drawer by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35718](https://github.com/BerriAI/litellm/pull/35718)
- fix(ui): hide guardrail review buttons from non-admin users by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;27535](https://github.com/BerriAI/litellm/pull/27535)
- feat(team): custom metadata validation hook for team create and update by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33353](https://github.com/BerriAI/litellm/pull/33353)
- ci(circleci): install a pinned Rust toolchain on the Linux jobs by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35519](https://github.com/BerriAI/litellm/pull/35519)
- fix(bedrock): stop forwarding no-op toolSpec.strict to Converse by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35688](https://github.com/BerriAI/litellm/pull/35688)
- fix(ui): reject an auto-router keyword rule left empty instead of dropping it by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35705](https://github.com/BerriAI/litellm/pull/35705)
- fix(guardrails/rubrik): attribute blocked requests to the caller that made them by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;35734](https://github.com/BerriAI/litellm/pull/35734)
- fix(responses): forward client headers to the provider on /v1/responses by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;34531](https://github.com/BerriAI/litellm/pull/34531)
- feat(spend): add net auto-router savings to the cost-optimization dashboard by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35521](https://github.com/BerriAI/litellm/pull/35521)
- chore(typing): clear basedpyright Any errors in budget reset, access groups, and cache settings by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35719](https://github.com/BerriAI/litellm/pull/35719)
- fix(spend): read what a request cost from the record instead of pricing it again by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35736](https://github.com/BerriAI/litellm/pull/35736)
- perf: install hiredis so redis-py parses replies with its C parser by [@&#8203;Classic298](https://github.com/Classic298) in [#&#8203;35709](https://github.com/BerriAI/litellm/pull/35709)
- feat(ui): show auto-router savings on the cost-optimization dashboard by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35522](https://github.com/BerriAI/litellm/pull/35522)
- perf: build log messages lazily so filtered-out log records cost nothing by [@&#8203;Classic298](https://github.com/Classic298) in [#&#8203;35703](https://github.com/BerriAI/litellm/pull/35703)
- fix(proxy): retry model cost map fetch with Retry-After-aware backoff and keep current map on reload failure by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35739](https://github.com/BerriAI/litellm/pull/35739)
- feat(otel): stamp service tier attributes on inference spans by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35679](https://github.com/BerriAI/litellm/pull/35679)
- fix(proxy): log the model cost map reload failure lazily by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35750](https://github.com/BerriAI/litellm/pull/35750)
- fix(groq): translate web\_search\_options to the browser\_search tool by [@&#8203;hMED22](https://github.com/hMED22) in [#&#8203;34971](https://github.com/BerriAI/litellm/pull/34971)
- feat(ui): add admin-configurable user banner by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35729](https://github.com/BerriAI/litellm/pull/35729)
- fix(e2e): make spend-counter redis connection env-driven for non-cluster deployments by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35732](https://github.com/BerriAI/litellm/pull/35732)
- fix(proxy): make /cursor/chat/completions work with Cursor agent mode by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;34029](https://github.com/BerriAI/litellm/pull/34029)
- fix(proxy): propagate user\_email and bind api\_key on JWT auth attribution paths by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;34331](https://github.com/BerriAI/litellm/pull/34331)
- chore(build): move the Admin UI toolchain to Node 24 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35801](https://github.com/BerriAI/litellm/pull/35801)
- test(e2e): vendor API strategy coverage across endpoints by [@&#8203;mubashir1osmani](https://github.com/mubashir1osmani) in [#&#8203;34649](https://github.com/BerriAI/litellm/pull/34649)
- chore(deps): upgrade cryptography to 50.0.0 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35803](https://github.com/BerriAI/litellm/pull/35803)
- test(e2e): cover legacy text /completions endpoint by [@&#8203;mubashir1osmani](https://github.com/mubashir1osmani) in [#&#8203;34431](https://github.com/BerriAI/litellm/pull/34431)
- feat(gemini): add gemini-robotics-er-2-preview and gemini-robotics-er-1.6-preview by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35555](https://github.com/BerriAI/litellm/pull/35555)
- test(e2e): move load/perf testing out of the main suite and drop the vllm passthrough test by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35820](https://github.com/BerriAI/litellm/pull/35820)
- feat(lint): enforce Final on locals and freeze function parameters (LIT010, LIT011) by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35807](https://github.com/BerriAI/litellm/pull/35807)
- chore: bump litellm-proxy-extras 0.4.81 -> 0.4.82, litellm 1.96.0 -> 1.97.0 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35810](https://github.com/BerriAI/litellm/pull/35810)
- fix(bedrock): drop conflicting tool\_choice.type when toolConfig.toolChoice is set by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35738](https://github.com/BerriAI/litellm/pull/35738)
- docs(CLAUDE.md): prefer commas over semicolons when replacing em dashes by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35825](https://github.com/BerriAI/litellm/pull/35825)
- chore(lint): zero out basedpyright headroom for purely local rules by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35828](https://github.com/BerriAI/litellm/pull/35828)
- test(e2e): retry provider-transient statuses at the transport with bounded backoff by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35824](https://github.com/BerriAI/litellm/pull/35824)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35836](https://github.com/BerriAI/litellm/pull/35836)
- refactor(ui): route MCP session tokens through the shared storage helper by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35835](https://github.com/BerriAI/litellm/pull/35835)
- docs(helm): replace the classic chart's 128Mi resource example with the documented 4Gi sizing by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35830](https://github.com/BerriAI/litellm/pull/35830)
- fix(proxy): persist periodic reload schedule state so status survives restarts and fires without store\_model\_in\_db by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35165](https://github.com/BerriAI/litellm/pull/35165)
- fix(router): eagerly fetch Vertex AI deferred stream to surface HTTP errors in \_acompletion fallback path by [@&#8203;deepanshululla](https://github.com/deepanshululla) in [#&#8203;34627](https://github.com/BerriAI/litellm/pull/34627)
- fix(azure\_storage): honor AZURE\_STORAGE\_ENDPOINT\_SUFFIX for sovereign clouds by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;35806](https://github.com/BerriAI/litellm/pull/35806)
- fix(proxy): apply key\_alias/key\_hash filters to all /key/list visibility branches by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35840](https://github.com/BerriAI/litellm/pull/35840)
- fix(proxy): enforce per-model budgets against resolved cursor model variants by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35834](https://github.com/BerriAI/litellm/pull/35834)
- feat(ui): reorder Add Auto Router into name + template, with a collapsible detailed config by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35746](https://github.com/BerriAI/litellm/pull/35746)
- test: repair three failing suites on litellm\_internal\_staging by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35845](https://github.com/BerriAI/litellm/pull/35845)
- fix(guardrails): scan model output on the /openai/v1/responses alias by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;35818](https://github.com/BerriAI/litellm/pull/35818)
- ci: pin Node on the Playwright UI lanes so npm ci meets the engines floor by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35848](https://github.com/BerriAI/litellm/pull/35848)
- fix(pricing): apply OpenAI's gpt-5.6 terra/luna cut to Azure cost map by [@&#8203;mubashir1osmani](https://github.com/mubashir1osmani) in [#&#8203;35481](https://github.com/BerriAI/litellm/pull/35481)
- feat(spend): add caller-scoped key/user/team/organization spend report endpoints by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35725](https://github.com/BerriAI/litellm/pull/35725)
- revert: "fix(caching): close evicted LLM clients so their connections are reclaimed ([#&#8203;35492](https://github.com/BerriAI/litellm/issues/35492))" by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35856](https://github.com/BerriAI/litellm/pull/35856)
- refactor(repositories): add prisma protocol seams and a spend-reset unit of work by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35748](https://github.com/BerriAI/litellm/pull/35748)
- perf(streaming): assemble streamed tool-call arguments in linear time by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35826](https://github.com/BerriAI/litellm/pull/35826)
- fix(s3\_v2): sign S3 object URLs with S3SigV4Auth so encoded paths verify by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35726](https://github.com/BerriAI/litellm/pull/35726)
- test(e2e): self-seed the ui suite's password-login users in global setup by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35863](https://github.com/BerriAI/litellm/pull/35863)
- fix(claude-code): create-only skill registration with a PUT update route (LIT-4110) by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;31752](https://github.com/BerriAI/litellm/pull/31752)
- fix(proxy):  fix zguard  httpcode when block input by [@&#8203;jwang-gif](https://github.com/jwang-gif) in [#&#8203;31948](https://github.com/BerriAI/litellm/pull/31948)
- fix(lint): pick the merge-aware base so in-progress merges are not blamed for base drift by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35868](https://github.com/BerriAI/litellm/pull/35868)
- chore: bump litellm-proxy-extras 0.4.82 -> 0.4.83 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35877](https://github.com/BerriAI/litellm/pull/35877)
- feat(ui): add Test Routing to the auto router create form by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35859](https://github.com/BerriAI/litellm/pull/35859)
- fix(ui): derive auto-router preset tests from the bundled preset JSON by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35882](https://github.com/BerriAI/litellm/pull/35882)
- revert: "test(e2e): vendor API strategy coverage across endpoints" ([#&#8203;34649](https://github.com/BerriAI/litellm/issues/34649)) by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35881](https://github.com/BerriAI/litellm/pull/35881)
- chore(deps): bump grpc and golang.org/x modules in the terraform provider by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35844](https://github.com/BerriAI/litellm/pull/35844)
- test(e2e): skip view-backed global spend probes pending LIT-5211 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35875](https://github.com/BerriAI/litellm/pull/35875)
- fix(lint): move the basedpyright heap flag into the type check gate by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35869](https://github.com/BerriAI/litellm/pull/35869)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35876](https://github.com/BerriAI/litellm/pull/35876)
- feat(ui): add role capability gating, migrate Tool Policies route by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35812](https://github.com/BerriAI/litellm/pull/35812)
- refactor(ui): inject the fetch client's base url instead of reading it at import by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35802](https://github.com/BerriAI/litellm/pull/35802)
- chore: remove unused .flake8 config and flake8 dev dependency by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35888](https://github.com/BerriAI/litellm/pull/35888)
- chore: stop advising pre-commit and bootstrap by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35884](https://github.com/BerriAI/litellm/pull/35884)
- fix(auth): name enable\_jwt\_auth when a JWT-shaped key is rejected by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35831](https://github.com/BerriAI/litellm/pull/35831)
- feat(auto-router): make reminder marker pair configurable by [@&#8203;akapur99](https://github.com/akapur99) in [#&#8203;35874](https://github.com/BerriAI/litellm/pull/35874)
- fix(UI): update anthropic model presets by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35896](https://github.com/BerriAI/litellm/pull/35896)
- fix(bootstrap): switch to the dashboard node floor via nvm or fnm by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35895](https://github.com/BerriAI/litellm/pull/35895)
- perf(pre-commit): run python, dashboard, and gen-api checks concurrently by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35903](https://github.com/BerriAI/litellm/pull/35903)
- feat(spend): derive a default auto-router savings baseline from the hardest tier by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35907](https://github.com/BerriAI/litellm/pull/35907)
- fix(http\_handler): self-heal handler clients closed after cache eviction by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35862](https://github.com/BerriAI/litellm/pull/35862)
- fix(cost\_tracking): keep OpenAI prompt cache token details through usage reassembly by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;34812](https://github.com/BerriAI/litellm/pull/34812)
- fix(cost): bill gpt-5.6 prompt cache reads at the cache read rate by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;34957](https://github.com/BerriAI/litellm/pull/34957)
- fix(batches): account for Responses API usage by [@&#8203;rimysore](https://github.com/rimysore) in [#&#8203;35367](https://github.com/BerriAI/litellm/pull/35367)
- ci: retry Codecov uploads and stop failing jobs on OIDC token flakes by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35251](https://github.com/BerriAI/litellm/pull/35251)
- feat(complexity\_router): let operators rename the four complexity tiers by [@&#8203;akapur99](https://github.com/akapur99) in [#&#8203;35893](https://github.com/BerriAI/litellm/pull/35893)
- chore(lint): zero stale ruff and LIT headroom and strip inert type: ignore comments by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35928](https://github.com/BerriAI/litellm/pull/35928)
- chore(lint): zero out seven more purely local basedpyright rules by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35927](https://github.com/BerriAI/litellm/pull/35927)
- chore(ui): zero stale headroom on local dashboard eslint budgets by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35929](https://github.com/BerriAI/litellm/pull/35929)
- fix(managed-files): skip rows without file objects by [@&#8203;rimysore](https://github.com/rimysore) in [#&#8203;35365](https://github.com/BerriAI/litellm/pull/35365)
- fix(router): redact fallback tracebacks at the call site and cover the sync deferred stream by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35843](https://github.com/BerriAI/litellm/pull/35843)
- fix(migrations): recover from an interrupted Prisma toolchain install by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35832](https://github.com/BerriAI/litellm/pull/35832)
- fix(lint): bring basedpyright rule counts back under their budget limits by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35962](https://github.com/BerriAI/litellm/pull/35962)
- chore(ui): don't zero out stale headroom except no-console by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35964](https://github.com/BerriAI/litellm/pull/35964)
- fix(proxy): give proxy\_admin\_viewer read parity with proxy\_admin by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35851](https://github.com/BerriAI/litellm/pull/35851)
- refactor(ui): address UI lint budget issues by refactoring UI by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35960](https://github.com/BerriAI/litellm/pull/35960)
- fix(ci): make the env-key doc gate see get\_secret\_bool reads by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35833](https://github.com/BerriAI/litellm/pull/35833)
- fix(caching): re-land evicted LLM client closing ([#&#8203;35492](https://github.com/BerriAI/litellm/issues/35492)) atop self-healing handlers by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35870](https://github.com/BerriAI/litellm/pull/35870)
- fix(proxy): keep the connected DB client when a startup health check fails by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35837](https://github.com/BerriAI/litellm/pull/35837)
- chore(lint): remove litellm/types from the ruff lint exclusion by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35926](https://github.com/BerriAI/litellm/pull/35926)
- feat(sgr): make the gateway middleware the source of truth for successful requests by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35717](https://github.com/BerriAI/litellm/pull/35717)
- feat(auto-router): let operators replace the LLM classifier's system prompt by [@&#8203;akapur99](https://github.com/akapur99) in [#&#8203;35855](https://github.com/BerriAI/litellm/pull/35855)
- fix(docker): bake the pip image's prisma engines at a world-readable path by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35976](https://github.com/BerriAI/litellm/pull/35976)
- fix(auth): return 403 from the OAuth2 enterprise gate by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35838](https://github.com/BerriAI/litellm/pull/35838)
- fix(router): keep custom model\_info across a price data reload by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35491](https://github.com/BerriAI/litellm/pull/35491)
- fix(proxy): resolve pass-through credentials live from router deployments by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35916](https://github.com/BerriAI/litellm/pull/35916)
- fix(ci): fetch only head and merge-base in lint jobs instead of every branch by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35982](https://github.com/BerriAI/litellm/pull/35982)
- fix(autorouter): match CJK keyword\_tier\_rules that regex word boundaries miss by [@&#8203;akapur99](https://github.com/akapur99) in [#&#8203;35984](https://github.com/BerriAI/litellm/pull/35984)
- feat(spend): rebuild the auto-router benchmarks backend as a per-session rollup by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35910](https://github.com/BerriAI/litellm/pull/35910)
- refactor(ui): replace hand-rolled query-param routing with nuqs by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35871](https://github.com/BerriAI/litellm/pull/35871)
- fix(docker): bake the componentized prisma engines at /opt/prisma so any uid can start by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35989](https://github.com/BerriAI/litellm/pull/35989)
- fix(migrations): keep the toolchain heal from raising on an unreadable nodeenv cache by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35986](https://github.com/BerriAI/litellm/pull/35986)
- fix(bedrock): sign Bedrock managed-file S3 requests with S3SigV4Auth by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35983](https://github.com/BerriAI/litellm/pull/35983)
- chore(typing): replace Any seams with real types across responses, proxy, and provider adapters by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35809](https://github.com/BerriAI/litellm/pull/35809)
- fix(ai21): resolve the documented AI21\_API\_KEY instead of a misspelled name by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35985](https://github.com/BerriAI/litellm/pull/35985)
- fix(docker): fail the image build when the generated prisma engine paths drift off /opt/prisma by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35979](https://github.com/BerriAI/litellm/pull/35979)
- fix(jina\_ai): resolve the documented JINA\_API\_KEY as a fallback by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35992](https://github.com/BerriAI/litellm/pull/35992)
- fix(proxy): only treat a recoverable database outage as grounds to serve without one by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35864](https://github.com/BerriAI/litellm/pull/35864)
- fix(ci): make every remaining CI checkout shallow by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35997](https://github.com/BerriAI/litellm/pull/35997)
- fix(auto-router): stop the embedding model's context window from failing long requests by [@&#8203;akapur99](https://github.com/akapur99) in [#&#8203;35956](https://github.com/BerriAI/litellm/pull/35956)
- fix(ci): make the env-key doc gate see bare get\_secret and get\_secret\_str reads by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35996](https://github.com/BerriAI/litellm/pull/35996)
- fix(logging): extend secret redaction to records litellm does not emit directly by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35977](https://github.com/BerriAI/litellm/pull/35977)
- test(utils): pin the register\_model replay test to the recorded half by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35994](https://github.com/BerriAI/litellm/pull/35994)
- fix(ci): run every helm test suite, not just the first one per file by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35993](https://github.com/BerriAI/litellm/pull/35993)
- ci: fail the build when a test file or Dockerfile is invoked by no job by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35991](https://github.com/BerriAI/litellm/pull/35991)
- fix(langfuse): stop a collected httpx handler from closing a shared client by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35981](https://github.com/BerriAI/litellm/pull/35981)
- fix(bedrock): grant bedrock:CountTokens in OIDC session policy by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33145](https://github.com/BerriAI/litellm/pull/33145)
- feat(pre-commit): save full lint output to a per-worktree log file by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36004](https://github.com/BerriAI/litellm/pull/36004)
- feat(ui): match auto-router preset models against deployments' underlying model IDs by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35972](https://github.com/BerriAI/litellm/pull/35972)
- fix(core\_helpers): map generic 'error' finish\_reason to 'stop' by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;33972](https://github.com/BerriAI/litellm/pull/33972)
- fix(proxy)!: apply request-parameter checks consistently across body, path and form inputs by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;36011](https://github.com/BerriAI/litellm/pull/36011)
- fix: rebuild models\_by\_provider in add\_known\_models so cost map reloads reach wildcard expansion by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;36010](https://github.com/BerriAI/litellm/pull/36010)
- feat(complexity\_router): report LLM classifier cost per request via routing\_decision and x-litellm-classifier-cost header by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;36015](https://github.com/BerriAI/litellm/pull/36015)
- fix(model-prices): correct replicate model key typo by [@&#8203;AkashNaickar](https://github.com/AkashNaickar) in [#&#8203;34800](https://github.com/BerriAI/litellm/pull/34800)
- fix(proxy): register managed batch output files on terminal retrieve by [@&#8203;Souravrajvi0](https://github.com/Souravrajvi0) in [#&#8203;34092](https://github.com/BerriAI/litellm/pull/34092)
- perf(pre-commit): fetch basedpyright base counts from CI artifacts by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35970](https://github.com/BerriAI/litellm/pull/35970)
- fix(ui): sync projects list page index to ?page= so back and reload keep the page by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;36003](https://github.com/BerriAI/litellm/pull/36003)
- fix(ui): link project page keys to their virtual key detail by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;36002](https://github.com/BerriAI/litellm/pull/36002)
- refactor(ui): drop unreferenced locals from dashboard route components by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35819](https://github.com/BerriAI/litellm/pull/35819)
- fix(ui): opening a project now pushes ?project= so back and deep links work by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;36001](https://github.com/BerriAI/litellm/pull/36001)
- refactor(ui): drop unreferenced locals from shared dashboard components by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35821](https://github.com/BerriAI/litellm/pull/35821)
- refactor(ui): drop unreferenced locals from tests and narrow destructures by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;36025](https://github.com/BerriAI/litellm/pull/36025)
- fix(guardrails): allow litellm\_content\_filter to run on post\_mcp\_call by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35980](https://github.com/BerriAI/litellm/pull/35980)
- fix(guardrails): scan /v1/messages tool traffic by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35999](https://github.com/BerriAI/litellm/pull/35999)
- refactor(ui): drop dead locals and unused React state across the dashboard by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;36026](https://github.com/BerriAI/litellm/pull/36026)
- feat(ui): add the auto-router usage tab to cost optimization by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35995](https://github.com/BerriAI/litellm/pull/35995)
- fix(managed\_files): derive unified output file ids deterministically so concurrent registrations converge by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36019](https://github.com/BerriAI/litellm/pull/36019)
- fix(proxy): send keepalive pings on anthropic messages SSE streams during upstream silence by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36024](https://github.com/BerriAI/litellm/pull/36024)
- fix(managed\_files): return unified ids from unscoped file listing by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36031](https://github.com/BerriAI/litellm/pull/36031)
- fix(arize\_phoenix): lowercase OTLP/gRPC auth metadata key by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;34883](https://github.com/BerriAI/litellm/pull/34883)
- fix(auto-router): accept every reminder marker pair a harness emits by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;36029](https://github.com/BerriAI/litellm/pull/36029)
- fix(pricing): sync flex/priority tier keys to dated OpenAI snapshot variants by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35923](https://github.com/BerriAI/litellm/pull/35923)
- fix(cost): bill reasoning tokens at the service tier output rate by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35925](https://github.com/BerriAI/litellm/pull/35925)
- fix(proxy): include today's UTC bucket when a daily activity range ends at the caller's current day by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;36051](https://github.com/BerriAI/litellm/pull/36051)
- fix: expired-miss share over all measured turns + cost-optimization tab labels by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;36037](https://github.com/BerriAI/litellm/pull/36037)
- fix(router): include Bedrock batch/S3 fields and model in deployment credentials by [@&#8203;mpcusack-altos](https://github.com/mpcusack-altos) in [#&#8203;24548](https://github.com/BerriAI/litellm/pull/24548)
- fix(batch): track cost for managed batches with no attributable key/u… by [@&#8203;elinacse](https://github.com/elinacse) in [#&#8203;35468](https://github.com/BerriAI/litellm/pull/35468)
- feat(guardrails): add scan\_only\_tool\_results to scope unified guardrails to tool results by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36014](https://github.com/BerriAI/litellm/pull/36014)
- fix(cost): stop token-pricing the placeholder input on file content calls by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35140](https://github.com/BerriAI/litellm/pull/35140)
- fix(proxy): fetch background responses through the router in CheckResponsesCost by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35137](https://github.com/BerriAI/litellm/pull/35137)
- fix(proxy): yaml store\_prompts\_in\_spend\_logs should take precedence over DB cached value by [@&#8203;Praveena-617](https://github.com/Praveena-617) in [#&#8203;35769](https://github.com/BerriAI/litellm/pull/35769)
- fix(lint): measure the basedpyright budget gate in a gate-owned venv by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36050](https://github.com/BerriAI/litellm/pull/36050)
- docs: cap all GitHub comments at 15-25 words, curb semicolon splices by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36059](https://github.com/BerriAI/litellm/pull/36059)
- chore(lint): name MappingProxyType in the mutable-collection fix messages by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36072](https://github.com/BerriAI/litellm/pull/36072)
- test: roll back runtime model registrations between tests by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36039](https://github.com/BerriAI/litellm/pull/36039)
- refactor(types): cut 653 implicit and explicit Any diagnostics across 11 modules by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36054](https://github.com/BerriAI/litellm/pull/36054)
- fix(proxy): stop resolving the UI session sentinel team on /search\_tools/list by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;36061](https://github.com/BerriAI/litellm/pull/36061)
- fix(batches): persist managed file ids for cancelled/failed/expired batches by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36048](https://github.com/BerriAI/litellm/pull/36048)
- fix(batches): register managed output files on batch cancel by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36034](https://github.com/BerriAI/litellm/pull/36034)
- fix(proxy): allow non-admins to reach /user/daily/activity/aggregated by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;36062](https://github.com/BerriAI/litellm/pull/36062)
- fix(anthropic): coerce explicit additionalProperties to false in output\_format schema by [@&#8203;dkindlund](https://github.com/dkindlund) in [#&#8203;35811](https://github.com/BerriAI/litellm/pull/35811)
- fix(batches): prevent managed file fallbacks by [@&#8203;rimysore](https://github.com/rimysore) in [#&#8203;35371](https://github.com/BerriAI/litellm/pull/35371)
- chore: ignore the mechanical lint and typing sweeps in git blame by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36076](https://github.com/BerriAI/litellm/pull/36076)
- fix(proxy): warn at startup when max\_budget is set but no database is connected by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;36041](https://github.com/BerriAI/litellm/pull/36041)
- fix(proxy): promote caller metadata trace fields into litellm\_metadata by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;35866](https://github.com/BerriAI/litellm/pull/35866)
- feat(terraform): sync provider 0.3.0 from the mirror and cut 0.4.0 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;36098](https://github.com/BerriAI/litellm/pull/36098)
- fix(guardrails): honor configured timeout in Zscaler AI Guard by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;36110](https://github.com/BerriAI/litellm/pull/36110)
- fix(logging): fall back to litellm\_metadata when metadata is empty by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;36105](https://github.com/BerriAI/litellm/pull/36105)
- fix(proxy): re-assert the authenticated identity on passthrough requests by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;36121](https://github.com/BerriAI/litellm/pull/36121)
- chore: bump litellm-enterprise 0.1.53 -> 0.1.54, litellm-proxy-extras 0.4.83 -> 0.4.84 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;36139](https://github.com/BerriAI/litellm/pull/36139)
- fix(ui): match auto-router preset models against wildcard-expanded model groups by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;36111](https://github.com/BerriAI/litellm/pull/36111)
- test(router): assert the auto-router max\_input\_chars kwarg by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;36109](https://github.com/BerriAI/litellm/pull/36109)
- fix(ui): allow clearing a key's budget reset from the Edit Key form by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;36140](https://github.com/BerriAI/litellm/pull/36140)
- fix(managed\_files): skip unparseable rows when listing managed files by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36021](https://github.com/BerriAI/litellm/pull/36021)
- fix(a2a): stop writing per-caller headers onto the shared cached httpx client by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35978](https://github.com/BerriAI/litellm/pull/35978)
- build(deps): bump h2 to 4.4.1 and js-yaml to 4.3.1 by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;36147](https://github.com/BerriAI/litellm/pull/36147)
- chore: promote staging to main by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36057](https://github.com/BerriAI/litellm/pull/36057)
- fix(azure\_sentinel): respect AZURE\_AUTHORITY\_HOST and derive the Azure Monitor audience per cloud by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;36137](https://github.com/BerriAI/litellm/pull/36137)
- fix(bedrock): pass SSE-KMS key through to the batch input-file S3 upload by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35148](https://github.com/BerriAI/litellm/pull/35148)
- fix(anthropic adapter): stop indexing choices\[0] on choiceless streaming chunks by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35314](https://github.com/BerriAI/litellm/pull/35314)
- fix(bedrock): normalize /v1/completions and /v1/responses batch records by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35675](https://github.com/BerriAI/litellm/pull/35675)
- fix(proxy): return the real status code when a credential update is rejected by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;36166](https://github.com/BerriAI/litellm/pull/36166)
- fix(proxy): improve Headroom /v1/compress HTTP 404 diagnostics by [@&#8203;aayush598](https://github.com/aayush598) in [#&#8203;35952](https://github.com/BerriAI/litellm/pull/35952)
- fix(proxy): invalidate cached project object on project update and delete by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;36028](https://github.com/BerriAI/litellm/pull/36028)
- feat(proxy): add apply\_user\_budget\_to\_team\_keys opt-in by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;36102](https://github.com/BerriAI/litellm/pull/36102)
- fix(proxy): stop alerting on health probes that lose the planned engine-restart race by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;36141](https://github.com/BerriAI/litellm/pull/36141)
- test(docker): gate the componentized gateway and backend images on an arbitrary-uid offline boot by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;36136](https://github.com/BerriAI/litellm/pull/36136)
- fix(http): stop pooled clients persisting cookies on the aiohttp jar too by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;36149](https://github.com/BerriAI/litellm/pull/36149)
- fix(router): bound fallback-walk work and error-log volume by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;36148](https://github.com/BerriAI/litellm/pull/36148)
- ci: wire credential\_endpoints tests into the proxy endpoints job by [@&#8203;cursor](https://github.com/cursor)\[bot] in [#&#8203;36187](https://github.com/BerriAI/litellm/pull/36187)
- docs(keys): document /key/info fields and clarify budget\_reset\_at is the next reset by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;36127](https://github.com/BerriAI/litellm/pull/36127)
- fix(azure\_sentinel): add AZURE\_SENTINEL\_AUTHORITY\_HOST as a Sentinel scoped override by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;36165](https://github.com/BerriAI/litellm/pull/36165)
- docs(pr-template): add a User Flow section with authoring instructions by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36162](https://github.com/BerriAI/litellm/pull/36162)
- fix(proxy): derive config agent ids from agent\_name so grants survive secret rotation by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;36020](https://github.com/BerriAI/litellm/pull/36020)
- chore(ui): regenerate schema.d.ts for the /key/info docstring update by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;36210](https://github.com/BerriAI/litellm/pull/36210)
- build(deps): bump gitpython to 3.1.58 to clear osv-scan on staging by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;36212](https://github.com/BerriAI/litellm/pull/36212)
- fix(proxy): deny agent access when key and team grants resolve to nothing by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;36221](https://github.com/BerriAI/litellm/pull/36221)
- build(deps): defer the second pypdf advisory until the 6.15.0 bump by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;36218](https://github.com/BerriAI/litellm/pull/36218)
- fix(a2a): align agent list annotation and test with the tuple return type by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;36217](https://github.com/BerriAI/litellm/pull/36217)
- ci: always run the UI API types sync check so it can be required by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;36213](https://github.com/BerriAI/litellm/pull/36213)
- build(deps): bump nanoid to 3.3.17 in the dashboard lockfile by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;36227](https://github.com/BerriAI/litellm/pull/36227)
- feat(ui): show user email or alias in usage data export by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;36232](https://github.com/BerriAI/litellm/pull/36232)
- feat(auto-router): track turns per complexity tier (LIT-5302) by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;36209](https://github.com/BerriAI/litellm/pull/36209)
- fix(websearch): restore snippet text in native web\_search\_tool\_result blocks (LIT-5315) by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;36228](https://github.com/BerriAI/litellm/pull/36228)
- fix(proxy): resolve entity access groups in the model listing endpoints by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;36230](https://github.com/BerriAI/litellm/pull/36230)
- fix(ui): let access groups be a team's only model source, with hover provenance by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;36234](https://github.com/BerriAI/litellm/pull/36234)
- fix(managed\_files): return unified output file ids from GET /batches by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36049](https://github.com/BerriAI/litellm/pull/36049)
- test(proxy): compare empty agent list to the tuple get\_agent\_list returns by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;36225](https://github.com/BerriAI/litellm/pull/36225)
- fix(otel): name the RPC system and upstream on MCP tool-call spans by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;35857](https://github.com/BerriAI/litellm/pull/35857)
- fix(guardrails): chunk oversized Bedrock ApplyGuardrail requests instead of failing by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;36119](https://github.com/BerriAI/litellm/pull/36119)
- test(e2e): settle control-plane writes across every replica, not just one by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;36247](https://github.com/BerriAI/litellm/pull/36247)
- fix(responses): forward allowed\_openai\_params through the chat completions bridge by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35885](https://github.com/BerriAI/litellm/pull/35885)
- test(proxy): assert the copy \_add\_team\_member\_budget\_table returns by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;36244](https://github.com/BerriAI/litellm/pull/36244)
- chore(ui): regenerate dashboard api types for tier\_turns by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;36243](https://github.com/BerriAI/litellm/pull/36243)
- refactor(types): declare mirrored pricing fields on ModelInfo by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;36215](https://github.com/BerriAI/litellm/pull/36215)
- fix(lint): make strict-gate noqas survive base ruff and flag stale ones by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36257](https://github.com/BerriAI/litellm/pull/36257)
- fix(vertex\_ai): surface real error/status on vertex batch create instead of IndexError 500 by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35141](https://github.com/BerriAI/litellm/pull/35141)
- ci: give the remaining pull\_request workflows a concurrency group by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;36252](https://github.com/BerriAI/litellm/pull/36252)
- refactor(lint): graduate zero-violation strict rules and guard the budget ratchet by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36161](https://github.com/BerriAI/litellm/pull/36161)
- fix(proxy): enforce require\_managed\_files on every route that accepts a raw provider id by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35551](https://github.com/BerriAI/litellm/pull/35551)
- chore(typing): clear 1.4k basedpyright Any errors across 21 hotspot files by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36282](https://github.com/BerriAI/litellm/pull/36282)
- test: roll back live router replay membership between tests by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36278](https://github.com/BerriAI/litellm/pull/36278)
- chore(ci): sync main into internal staging by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;36288](https://github.com/BerriAI/litellm/pull/36288)
- build(lint): rename make pre-commit to make check with a working-tree fallback by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36277](https://github.com/BerriAI/litellm/pull/36277)
- fix(ui): show team BYOK models in team fallback settings by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;36241](https://github.com/BerriAI/litellm/pull/36241)
- fix(otel): mark v2 server spans as failed for pre-call errors by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;34546](https://github.com/BerriAI/litellm/pull/34546)
- fix(websearch\_interception): bill intercepted searches to the calling key by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35708](https://github.com/BerriAI/litellm/pull/35708)
- chore: remove pre-commit rule by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;36295](https://github.com/BerriAI/litellm/pull/36295)
- docs: clarify guideline priority ordering in CLAUDE.md by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;36296](https://github.com/BerriAI/litellm/pull/36296)
- feat(router): independent, default-on deployment affinity for the auto-router by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;36146](https://github.com/BerriAI/litellm/pull/36146)
- test: repair stale CircleCI contracts by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;36293](https://github.com/BerriAI/litellm/pull/36293)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;36286](https://github.com/BerriAI/litellm/pull/36286)
- chore: rebuild Admin UI bundle for the 2026-08-08 release by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;36297](https://github.com/BerriAI/litellm/pull/36297)
- chore(ci): promote internal staging to main by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;36304](https://github.com/BerriAI/litellm/pull/36304)

##### New Contributors

- [@&#8203;rimysore](https://github.com/rimysore) made their first contribution in [#&#8203;35367](https://github.com/BerriAI/litellm/pull/35367)
- [@&#8203;AkashNaickar](https://github.com/AkashNaickar) made their first contribution in [#&#8203;34800](https://github.com/BerriAI/litellm/pull/34800)
- [@&#8203;Souravrajvi0](https://github.com/Souravrajvi0) made their first contribution in [#&#8203;34092](https://github.com/BerriAI/litellm/pull/34092)
- [@&#8203;elinacse](https://github.com/elinacse) made their first contribution in [#&#8203;35468](https://github.com/BerriAI/litellm/pull/35468)
- [@&#8203;aayush598](https://github.com/aayush598) made their first contribution in [#&#8203;35952](https://github.com/BerriAI/litellm/pull/35952)
- [@&#8203;cursor](https://github.com/cursor)\[bot] made their first contribution in [#&#8203;36187](https://github.com/BerriAI/litellm/pull/36187)

**Full Changelog**: <https://github.com/BerriAI/litellm/compare/v1.96.0...v1.97.0>

### [`v1.97.0`](https://github.com/BerriAI/litellm/releases/tag/v1.97.0)

[Compare Source](https://github.com/BerriAI/litellm/compare/v1.96.2...v1.97.0)

##### Verify Docker Image Signature

All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](https://github.com/BerriAI/litellm/commit/0112e53046018d726492c814b3644b7d376029d0).

**Verify using the pinned commit hash (recommended):**

A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \
  ghcr.io/berriai/litellm:v1.97.0
```

**Verify using the release tag (convenience):**

Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules:

```bash
cosign verify \
  --key https://raw.githubusercontent.com/BerriAI/litellm/v1.97.0/cosign.pub \
  ghcr.io/berriai/litellm:v1.97.0
```

Expected output:

```
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
```

***

##### What's Changed

- feat(proxy): resolve Cursor thinking/fast model-name suffixes on /cursor/chat/completions by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35554](https://github.com/BerriAI/litellm/pull/35554)
- fix(team-callbacks): actually stop logging when disable\_logging is called by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;35520](https://github.com/BerriAI/litellm/pull/35520)
- refactor(lint): drop redundant !s f-string conversion flags and fix displaced import-group comments by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35546](https://github.com/BerriAI/litellm/pull/35546)
- fix(proxy): backfill null user\_email on existing users during JWT auth by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;34588](https://github.com/BerriAI/litellm/pull/34588)
- feat(playground): add non-streaming response toggle by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35560](https://github.com/BerriAI/litellm/pull/35560)
- feat(teams): apply default organization to new teams from default team settings by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35540](https://github.com/BerriAI/litellm/pull/35540)
- fix(ui): block Playground page for viewer roles on direct URL access by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35676](https://github.com/BerriAI/litellm/pull/35676)
- fix(caching): close evicted LLM clients so their connections are reclaimed by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35492](https://github.com/BerriAI/litellm/pull/35492)
- chore(deps): update brace-expansion, postcss, and gitpython to current patch releases by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35692](https://github.com/BerriAI/litellm/pull/35692)
- refactor(ui): rename the create MCP server component to PascalCase by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35686](https://github.com/BerriAI/litellm/pull/35686)
- fix(openai): drop undefined Union from owns\_wrapped\_http\_client annotation by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35706](https://github.com/BerriAI/litellm/pull/35706)
- fix(openai): drop the undefined Union from owns\_wrapped\_http\_client by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35704](https://github.com/BerriAI/litellm/pull/35704)
- chore(ui): note Google's Agent Platform rename in vector store setup by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;28076](https://github.com/BerriAI/litellm/pull/28076)
- fix(proxy): apply key/team router\_settings.model\_group\_alias by [@&#8203;yassin-berriai](https://github.com/yassin-berriai) in [#&#8203;35486](https://github.com/BerriAI/litellm/pull/35486)
- feat(complexity\_router): default session affinity off and expose it in the UI by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35714](https://github.com/BerriAI/litellm/pull/35714)
- fix(datadog): read team callback dd\_\* params from kwargs instead of blocked dynamic params ([#&#8203;35115](https://github.com/BerriAI/litellm/issues/35115) port) by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35687](https://github.com/BerriAI/litellm/pull/35687)
- refactor(ui): extract the MCP create form's logic and field groups by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35694](https://github.com/BerriAI/litellm/pull/35694)
- test(ui): tier the MCP create tests into unit and integration by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35697](https://github.com/BerriAI/litellm/pull/35697)
- fix(proxy): redact credential headers from request logging copies by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;35678](https://github.com/BerriAI/litellm/pull/35678)
- feat(guardrails/rubrik): prompt moderation, response-text blocking, streaming buffer, failure logging by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35722](https://github.com/BerriAI/litellm/pull/35722)
- fix(ui): render Responses API request and response in the logs drawer by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35718](https://github.com/BerriAI/litellm/pull/35718)
- fix(ui): hide guardrail review buttons from non-admin users by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;27535](https://github.com/BerriAI/litellm/pull/27535)
- feat(team): custom metadata validation hook for team create and update by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;33353](https://github.com/BerriAI/litellm/pull/33353)
- ci(circleci): install a pinned Rust toolchain on the Linux jobs by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35519](https://github.com/BerriAI/litellm/pull/35519)
- fix(bedrock): stop forwarding no-op toolSpec.strict to Converse by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35688](https://github.com/BerriAI/litellm/pull/35688)
- fix(ui): reject an auto-router keyword rule left empty instead of dropping it by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35705](https://github.com/BerriAI/litellm/pull/35705)
- fix(guardrails/rubrik): attribute blocked requests to the caller that made them by [@&#8203;yucheng-berri](https://github.com/yucheng-berri) in [#&#8203;35734](https://github.com/BerriAI/litellm/pull/35734)
- fix(responses): forward client headers to the provider on /v1/responses by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;34531](https://github.com/BerriAI/litellm/pull/34531)
- feat(spend): add net auto-router savings to the cost-optimization dashboard by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35521](https://github.com/BerriAI/litellm/pull/35521)
- chore(typing): clear basedpyright Any errors in budget reset, access groups, and cache settings by [@&#8203;mateo-berri](https://github.com/mateo-berri) in [#&#8203;35719](https://github.com/BerriAI/litellm/pull/35719)
- fix(spend): read what a request cost from the record instead of pricing it again by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35736](https://github.com/BerriAI/litellm/pull/35736)
- perf: install hiredis so redis-py parses replies with its C parser by [@&#8203;Classic298](https://github.com/Classic298) in [#&#8203;35709](https://github.com/BerriAI/litellm/pull/35709)
- feat(ui): show auto-router savings on the cost-optimization dashboard by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35522](https://github.com/BerriAI/litellm/pull/35522)
- perf: build log messages lazily so filtered-out log records cost nothing by [@&#8203;Classic298](https://github.com/Classic298) in [#&#8203;35703](https://github.com/BerriAI/litellm/pull/35703)
- fix(proxy): retry model cost map fetch with Retry-After-aware backoff and keep current map on reload failure by [@&#8203;ryan-crabbe-berri](https://github.com/ryan-crabbe-berri) in [#&#8203;35739](https://github.com/BerriAI/litellm/pull/35739)
- feat(otel): stamp service tier attributes on inference spans by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;35679](https://github.com/BerriAI/litellm/pull/35679)
- fix(proxy): log the model cost map reload failure lazily by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;35750](https://github.com/BerriAI/litellm/pull/35750)
- fix(groq): translate web\_search\_options to the browser\_search tool by [@&#8203;hMED22](https://github.com/hMED22) in [#&#8203;34971](https://github.com/BerriAI/litellm/pull/34971)
- feat(ui): add admin-configurable user banner by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35729](https://github.com/BerriAI/litellm/pull/35729)
- fix(e2e): make spend-counter redis connection env-driven for non-cluster deployments by [@&#8203;yuneng-berri](https://github.com/yuneng-berri) in [#&#8203;35732](https://github.com/BerriAI/litellm/pull/35732)
- fix(proxy): make /cursor/chat/completions work with Cursor agent mode by [@&#8203;tin-berri](https://github.com/tin-berri) in [#&#8203;34029](https://github.com/BerriAI/litellm/pull/34029)
- fix(proxy): propagate user\_email and bind api\_key on JWT auth attribution paths by [@&#8203;devin-ai-integration](https://github.com/devin-ai-integration)\[bot] in [#&#8203;34331](https://github.com/BerriAI/litellm/pull/34331)
- chore(build): move the Admin UI toolchain to Node 24 by [@&#8203;yuneng-berri](https://g…
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.

4 participants