Commit graph

5061 commits

Author SHA1 Message Date
tin-berri
f93d9b6b67
feat(complexity_router): escalate oversized prompts to a tier that fits before dispatch (#38844)
* feat(complexity_router): escalate oversized prompts to a tier that fits before dispatch

The classifier scores complexity and never prompt size, so a long agentic
session whose newest ask is trivial classifies SIMPLE onto a small-window
tier and the provider rejects it with a context-window 400 that nothing
retries. The gate runs after classification on every decision path
(classify tail and session-affinity pin), estimates prompt tokens
including the out-of-band carriers (top-level system, tools,
instructions), and when the decided tier provably cannot hold the prompt
moves the request to the lowest configured tier with a model whose
declared window fits, restricting the pick to fitting models when the
decided tier can keep it. Models with no resolvable window are never
escalated away from or onto, escalated decisions are never written as
session pins, and the decision records context_escalated plus the
original tier in spend logs.

Resolves LIT-6503

* fix(complexity_router): judge groups by smallest window, bound skips by bytes, filter adaptive picks

Review-round rework, one mechanism per finding. A group is judged by its
smallest resolvable deployment window, since the core router picks within
a group with no fit check. The counting skip is gated on UTF-8 byte
length, which BPE token counts can never exceed, so token-dense scripts
cannot slip past it; only a real tokenizer count ever moves a request and
a failed count leaves the placement alone. The fit facts now filter every
adaptive phase including cold start and the tier fallbacks. Window
questions adopt the declared provider and never resolve authenticating
providers, and a router instance without get_model_list degrades the gate
to a no-op. Tests rebuilt on real Router instances resolving deployment
model_info end to end, plus a full-path test through
async_get_available_deployment
2026-08-31 16:12:59 -07:00
Yuneng Jiang
3eb1eee1fa
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/flaky-e2e-tests-d022f6 2026-08-31 15:57:51 -07:00
tin-berri
296bde0d0d
feat(complexity-router): add classification_mode to skip classifier on continuation turns (#38861) 2026-08-31 15:50:04 -07:00
mateo-berri
eb00986f18 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_gigachat_passthrough_25886
# Conflicts:
#	osv-scanner.toml
2026-08-31 15:25:10 -07:00
Yuneng Jiang
6abb85155a
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/flaky-e2e-tests-d022f6 2026-08-31 14:42:01 -07:00
Mateo Wang
b518be45fb
Merge pull request #38997 from BerriAI/litellm_add_responses_input_tokens_endpoint
feat(proxy): add /v1/responses/input_tokens token counting endpoint
2026-08-31 14:30:44 -07:00
Yuneng Jiang
9c577c6045
test(e2e): assert user-observable behavior instead of DOM structure
The UI e2e suite had a class of assertions that pin how the dashboard is
built rather than what it does, so an ordinary refactor turns them red
without any user-visible change.

Geometry. The auto-router template select had two tests made of pixel
arithmetic plus a data-side="bottom" check, which is Base UI's own
positioner signal. The regression they guard (#38554) is a popup opening
on top of the control that spawned it, so both cases collapse to one
invariant: the options never cover the trigger. It now runs at both
viewport heights and reads the popup as role=listbox. The models header
test compared the tabs and refresh centers within 2px, which a padding
change flips; it now asserts the two share a row.

Structure. The logs drawer test walked xpath=../../.. from a text node
and read collapsed state off chevron icon classes. SectionHeader now
renders a real disclosure button with aria-expanded, and its two copy
buttons carry distinct names instead of both being "Copy". Sidebar group
toggles expose aria-expanded too, so the migration spec can ask for a
collapsed group by state rather than by nesting depth.

Positional lookups. keyRow.locator("button").first(), row.locator("td")
.first() and getByTestId(grid).locator("div").first() all named a
position where they meant an action; they now name the control. Table
scoping moves from "table tbody" to role=row.

Timing. Nine waitForTimeout calls are gone. Every assertion that followed
them already retried to its own timeout, so the sleeps only slowed the
run down.

Both files under tests/users/ were wrapped in test.skip("...", () => {}),
which registers one skipped test and never runs the body, so the four
tests inside had never executed and were written against a UI that has
since changed (the search placeholder is "Search by email…", the ID
filters moved into a drawer, pagination is labelled "Go to previous
page"). Rewritten against the current surface: the suite goes from 104
collected tests to 107.

Left in place deliberately: the chip and dialog-footer data-slot
selectors, because the accessible names they work around live in
components/ui/, which is shadcn CLI-managed and not hand-edited.
2026-08-31 14:04:15 -07:00
mateo-berri
59732f068b Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_gigachat_passthrough_25886 2026-08-31 13:16:41 -07:00
Mateo Wang
0c21b30cb7
feat(spend_tracking): persist router metadata in spend logs for internal router models (#39001)
* feat(spend_tracking): persist router metadata in spend logs for internal router models

* test(spend_tracking): expect router_metadata key in exact-payload tests, type the routed-kwargs helper
2026-08-31 12:52:34 -07:00
mateo-berri
c7c382402a feat(proxy): add /v1/responses/input_tokens token counting endpoint 2026-08-31 12:04:44 -07:00
Mateo Wang
c09fa5b712
Merge pull request #38883 from BerriAI/litellm_docs_user_spend_endpoint_semantics
docs(proxy): clarify spend semantics on /v2/user/info and /user/daily/activity
2026-08-31 11:47:41 -07:00
mateo-berri
e938e89d13 docs(proxy): account for budget rollover and daily upserts in spend wording 2026-08-31 11:19:11 -07:00
yucheng-berri
30f3228510
test(newrelic): cover static default_team_settings per-team routing (#38857)
* test(newrelic): cover static default_team_settings per-team routing

The dynamic POST /team/{team_id}/callback path for New Relic is tested, but
the static default_team_settings twin had no regression coverage. Add a test
that drives default_team_settings -> add_team_based_callbacks_from_config and
asserts the resolved trusted vars dispatch to BOTH the per-team metrics logger
(cost/usage) and the trace logger (LLM/agent spans), so a config-file customer
gets the same per-team routing as the API customer.

Also correct the /team/callback docstring: callback_name is a str validated
against the credential-capable callbacks, not a fixed langfuse/langsmith/gcs
Literal, and document the newrelic_api_key / newrelic_region vars.

* chore(ui): sync schema.d.ts with the /team/callback docstring

Regenerate the dashboard OpenAPI types for the add_team_callbacks description
change: callback_name is a validated str (not a langfuse/langsmith/gcs
Literal) and the newrelic_api_key / newrelic_region vars are documented.

* docs(newrelic): note LITELLM_OTEL_V2 prerequisite, trim test comments

Address review: team-scoped New Relic config is rejected with a 400 unless the
proxy runs with LITELLM_OTEL_V2=true, so document that in the /team/callback
endpoint and sync schema.d.ts. Drop the narrative setup comments in the new
test per the repo comment convention; the test name and docstring already say why.
2026-08-31 16:58:35 +00:00
devin-ai-integration[bot]
f079e4061b
fix(proxy): deliver budget alerts on webhook-only alerting and accept ALERTING_WEBHOOK_URL (#38441)
* fix(proxy): deliver budget alerts on webhook-only alerting and accept ALERTING_WEBHOOK_URL

ProxyLogging.budget_alerts forwarded to the alerting pipeline only when
'slack' was in general_settings.alerting, so alerting: ['webhook'] plus
WEBHOOK_URL silently never delivered a budget alert (the config
/health/services?service=webhook exists to test). Forward when 'webhook'
is present too; SlackAlerting.send_alert already fans out per channel.

Also accept a provider-neutral ALERTING_WEBHOOK_URL env fallback for the
Slack-format channel (any Slack-compatible receiver works), mark it as a
sensitive var, and de-brand the admin UI alerting copy.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(ui): format settings.tsx with prettier

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* chore(ui): regenerate schema.d.ts for updated alerting description

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* ci: retrigger checks after ALERTING_WEBHOOK_URL docs merged

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-31 09:22:36 -07:00
mateo-berri
b6bd749c02 fix(ui): withhold team-scoped model writes from view-only sessions too
The route-level RBAC in litellm/proxy/auth/route_checks.py 403s
/model/new, /model/update, and /model/delete for proxy_admin_viewer on
the session role alone, before ModelManagementAuthChecks' team-admin
carve-out can run. A view-only session therefore gets no model write
affordance, team admin or not.
2026-08-30 10:01:44 -07:00
Devin AI
36c53e1288 chore(ui): regenerate schema.d.ts for updated endpoint descriptions
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-30 07:46:45 +00:00
mateo-berri
70e2f4e68f Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_gigachat_passthrough_25886
# Conflicts:
#	litellm/llms/gigachat/chat/transformation.py
2026-08-29 22:08:54 -07:00
mateo-berri
74fb398f9b fix(ui): hide model write affordances from view-only admin sessions 2026-08-29 21:40:28 -07:00
yuneng-jiang
8e45522117
Merge pull request #38852 from BerriAI/litellm_/model-test-connection-artifact-805be5
fix(ui): drop stray text next to Close in the model connection test dialog
2026-08-29 17:30:43 -07:00
ryan-crabbe-berri
9da0b30888
Merge pull request #38843 from BerriAI/litellm_mag_budget_ui
feat(ui): set a model access group's shared budget from the dashboard
2026-08-29 17:27:17 -07:00
Yuneng Jiang
795c036279
fix(ui): drop stray text next to Close in the model connection test dialog
The Add Model page's connection test dialog rendered a literal ", ]" beside the Close button, left over from converting an array of footer buttons into JSX children.
2026-08-29 17:26:38 -07:00
Mateo Wang
ff2f06e37f
Merge pull request #38444 from BerriAI/litellm_mcp_connector_bulk_import
feat(mcp): bulk-import Anthropic MCP connectors via API and admin UI
2026-08-29 17:21:57 -07:00
ryan-crabbe-berri
797a4dcbfe fix(ui): badge the Model Access Group Budgets tab as Beta rather than New 2026-08-29 17:10:20 -07:00
ryan-crabbe-berri
8530f80c78 chore(ui): sync schema.d.ts with the access group list docstring 2026-08-29 17:05:39 -07:00
ryan-crabbe-berri
243f8317f4 style(ui): drop decorative section banners from the access group budget hooks 2026-08-29 16:47:50 -07:00
ryan-crabbe-berri
7c0e58ed06 fix(ui): stop offering access group budget writes that the proxy refuses
An Admin Viewer and a group whose name contains a slash both reached an
enabled Set budget action that could only ever come back 403 or 404. Gate
the row actions on proxy admin and on the name being addressable, with the
reason in the tooltip.
2026-08-29 16:45:48 -07:00
ryan-crabbe-berri
a5cd3fae81 fix(ui): name the tab Model Access Group Budgets and keep sub-cent budgets readable
Marks the tab New, and stops a budget under a cent rendering as "of $0.00"
next to an over-budget meter.
2026-08-29 16:38:58 -07:00
ryan-crabbe-berri
0777e37849 feat(ui): set a model access group's shared budget from the dashboard
Model access group budgets shipped API-only, so the only way to give a group a
budget was a curl. Adds an Access Group Budgets tab under Models & Endpoints
that lists every group with the spend drawn against its shared pool, and a
modal to set, edit or clear the budget.

/access_group/list now carries each group's budget and spend inline, so the
table renders from one read instead of one follow-up request per row.
2026-08-29 16:32:28 -07:00
ryan-crabbe-berri
ec934c490b
Merge pull request #38784 from BerriAI/litellm_model_access_group_budgets
feat(budgets): enforce shared budgets on model access groups
2026-08-29 16:06:54 -07:00
Yuneng Jiang
02046fb7b7
fix(ui): keep a deleted-from search query instead of blanking the box
The paginated search select diffs the input against the selected option's
label to recover what the user just typed, so a query started from a picked
value searches for the new text rather than the label plus the new text.
That diff can only express an insertion: it walks a common prefix and a
common suffix and returns what sits between them. A deletion leaves nothing
between them, so every deletion-only edit returned the empty string.

Backspacing once in a field showing a selected label therefore threw the
edit away. The empty result was stored as the query, the controlled input
re-rendered blank, and the server was asked for the unfiltered page instead
of the text the user left in the box.

An edit that yields no insertion but did change the value is a deletion, and
there the remaining text is the query the user means. Insertions and
whole-selection replacements are untouched.
2026-08-29 15:52:48 -07:00
tin-berri
5c034fda74
fix(ui): allow in-place editing of classifier numeric inputs (#38803)
Backspacing the last digit of Context Window Size instantly refilled the default (3), since onChange mapped empty input to null and the handler coalesced null back to the default. The same defect affected Timeout (ms) and Context Character Budget. Add per-field raw draft state so an empty or partial value stays visible while focused, commit only finite values (rounded, clamped to each field's minimum), and clear the draft on blur so an abandoned edit falls back to the committed value. 0 stays a valid committed value for both context controls. Add stable ids and label associations; update tests to query by label
2026-08-29 14:43:20 -07:00
devin-ai-integration[bot]
645792955d
feat(proxy): cyberark conjur secret manager configuration via Admin UI (#38445)
* feat(proxy): CyberArk Conjur secret manager configuration via Admin UI

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(ui): mock networking base-url helpers in AdminPanel test

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy): restore deployment CyberArk env config on delete and roll back on persist failure

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(proxy): reinit env-configured hashicorp vault manager after cyberark persist rollback

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-29 13:36:08 -07:00
devin-ai-integration[bot]
f0340fef16
feat(mcp_gateway): add RFC 7662 introspection for gateway session tokens (#38726)
* feat(mcp_gateway): add RFC 7662 introspection for gateway session tokens

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(mcp_gateway): omit Bearer token_type for refresh introspection and allow mcp-scoped keys

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(mcp_gateway): cover introspection of RS256-signed session tokens

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(mcp_gateway): load the discoverable router on a cold /introspect request

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* chore(openapi): regenerate lazy snapshot and schema.d.ts for /introspect

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-29 12:56:04 -07:00
mateo-berri
177463e9b0 Merge branch 'litellm_internal_staging' into litellm_mcp_connector_bulk_import
Regenerates the lazy OpenAPI snapshot and dashboard schema.d.ts from the merged tree.
Passes list/dict copies into NewMCPServerRequest from the connector conversion and switches the
server-managed NewMCPServerRequest fields to keyword defaults so the basedpyright budget gate stays flat.
2026-08-29 12:49:45 -07:00
ryan-crabbe-berri
d2440639d5 feat(budgets): enforce shared budgets on model access groups
A model access group could gate which models a caller reaches but never how
much that group of callers could spend in total. Capping a shared pool meant
setting a per-entity budget on every key by hand, which caps each key
separately and still leaves no way to read what the group cost.

Spend is attributed to a group only when the group's name appears on an
allowlist the caller was granted (key, team, team-member scope, project or
org) and that group serves the requested model. Asking for a model that
merely belongs to a group attributes nothing, because nothing about the
caller named the group. Levels are unioned rather than ranked, so a team
granted "*" whose member is scoped to one group still counts as gated by
that group.

Enforcement runs on both paths tags already use: a reservation counter on
the pre-call path and a read-time max_budget check inside the existing
concurrent budget gather, so the ceiling still holds under
disable_budget_reservation.

Adds LiteLLM_ModelAccessGroupBudgetTable, which is the only place a group is
ever a row: the groups themselves stay free-text strings in
model_info.access_groups, so a row exists only once someone gives that group
a budget. GET, PUT and DELETE /access_group/{name}/budget manage it, and
/access_group/{name}/info now carries the spend and budget alongside the
models.
2026-08-29 12:13:55 -07:00
Yuneng Jiang
08c83c12e9
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/logs-reopen-shadcn-migration-2c9526 2026-08-29 11:55:20 -07:00
Yuneng Jiang
4a3dcd5e8e
refactor(ui): give the collapsed log drawer its own sidebar toggle
Open, the trace sidebar is byte-identical to before: the toggle sits over its
header exactly where it did, and the header keeps the padding that makes room
for it. Collapsed, that button has nowhere to live, so the drawer header shows
one instead, on the model row or the request id row when the log names no
model.

Both come from SidebarToggle, so they cannot drift in design. The chevrons now
point the way the sidebar will move: right while it is open, left while it is
collapsed.
2026-08-29 11:53:14 -07:00
Yuneng Jiang
20e6d6457a
refactor(ui): show the log drawer's sidebar toggle only where it has a row
Putting the toggle in the drawer header unconditionally stranded it on its own
line: the model row renders empty for a log that names no model or provider, so
the chevron sat alone above the request id.

The sidebar keeps the toggle whenever it is open, in its own header. Collapsed,
the toggle moves into the drawer header and joins the model row, or the request
id row when there is no model to join. Shared between both through
SidebarToggle so the two call sites cannot drift.
2026-08-29 11:48:04 -07:00
Yuneng Jiang
b27a1a13a2
refactor(ui): move the trace sidebar toggle into the log drawer header
The collapsed rail kept the toggle in flow but left a 40px stub of empty
sidebar on screen. The toggle now leads the drawer header's first row, ahead of
the provider logo and the model name, so it reads as part of the header and the
sidebar goes back to unmounting when collapsed.

Still no absolute positioning and no stacking level: the button is a normal
in-flow child of the header row it sits in. DrawerHeader takes the collapsed
state and the toggle handler as props rather than reaching for the drawer's
state.
2026-08-29 11:42:45 -07:00
Yuneng Jiang
2e3ae43b6f
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/json-readability-logs-661421 2026-08-29 11:31:08 -07:00
yuneng-jiang
fa25ff2a2e
Merge pull request #38626 from BerriAI/litellm_ui_model_links_team_key_info
feat(ui): link team and key model chips to the models page filtered to that group
2026-08-29 11:26:15 -07:00
Yuneng Jiang
fcd6ea46ce
refactor(ui): keep the log drawer's trace sidebar in flow as a collapsed rail
The toggle was absolutely positioned over the drawer's flex row, owned by
neither column. That forced two coupled workarounds: a stacking level so it
could beat whatever it landed on, and pl-12 on the sidebar header to reserve
space for a button that was not its child.

The sidebar column now always renders, at 224px expanded and a 40px rail
collapsed, and the toggle is a normal in-flow child of the column it controls.
No absolute, no z-index, no reserved padding, and nothing that can paint over
the button. It also stops the toggle from clipping the provider logo, which it
did in the collapsed state even before the z-index scale landed.

Costs 40px of drawer width while collapsed.
2026-08-29 11:00:47 -07:00
Yuneng Jiang
4c0bb1226c
fix(ui): make the logs JSON viewer follow the theme in dark mode
The request and response tree passed the library's light palette in every
theme, so in dark mode the string values rendered dark green and the
punctuation rendered black on a near black surface. Pick the palette from
the resolved theme instead, and let the tree inherit the themed surface
rather than painting the library's own background.
2026-08-29 10:54:48 -07:00
Yuneng Jiang
8278a92a06
fix(ui): let the log drawer's trace sidebar expand again once collapsed
The sidebar toggle sits absolutely positioned over the drawer's flex row. With
the sidebar expanded it lands on the sidebar header, but once collapsed it lands
on the drawer header, which is sticky at z-chrome (10). The named-z-scale
refactor moved the toggle from z-20 to z-raised (1), so from then on the header
painted over it and swallowed the click: collapse the trace list and there was
no way to bring it back.

Moves the toggle to z-floating (30) and folds the two mirrored buttons into one,
since they only ever differed by icon, label and handler.

Covered by a Playwright spec, which is the tier that can see the layering: the
button stays visible and enabled either way, so the pre-fix failure is a click
interception that jsdom cannot reproduce.
2026-08-29 10:42:48 -07:00
ryan-crabbe-berri
4411562a0f fix(ui): satisfy lint and the modelInfoCall arity in the models hook tests 2026-08-29 10:32:29 -07:00
ryan-crabbe-berri
fe63ebdb19 fix(ui): filter the models page by exact model group instead of substring search
Pass the selected group as the exact model= param on /v2/model/info rather than as the substring search, so a group like gpt-4 no longer pulls gpt-4o rows into the page and count. Drop two comments that restated helper behavior.
2026-08-29 10:18:06 -07:00
Yuneng Jiang
03f64200c2
fix(ui): stop CodeBlock painting a hardcoded light background in dark mode
The customStyle background only reaches the <pre>, so the prism theme's own
background stayed on the inner <code>. In dark mode that left a white card
with a dark box hugging every line. Let the block inherit the themed surface
from its wrapper instead.
2026-08-29 10:15:05 -07:00
yuneng-jiang
733d0b5af5
Merge pull request #38588 from BerriAI/litellm_/dark-mode-logo-strategy-7b99f2
feat(ui): make provider logos readable in dark mode
2026-08-28 16:25:11 -07:00
tin-berri
4e48d74455
feat(shadow_eval): measure both arms' cost so a job reports what the router would have saved (#38631)
The attempt row now prices the real arm (the payload's response_cost plus its own
routing classifier when it routed) beside the shadow arm (completion plus the
classifier cost the routing decision writes back), and flags turns litellm's
response cache served. A per-leg funnel table counts the eligible requests that
produced no row (lost the sampling dice, unjudgeable shape, concurrency shed),
so results can weigh judged rows against the traffic they stand for. Job results
gain per-slice and overall arm spends plus the coverage counts, the budget gates
charge the shadow arm's classifier spend against max_budget, and the dashboard
shows the measured cost comparison beside the win rate

Resolves LIT-6358

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 15:13:19 -07:00
tin-berri
8868aaa5db
feat(ui): run the Lite preset's medium and complex tiers at their documented efforts (#38482)
Lite ran Muse Spark 1.2 and Kimi K3 at whatever effort each provider happens to
default to. Set the ones their own docs name: Muse Spark 1.2 at xhigh, and Kimi
K3 at max, which is Kimi's own default and what the model map now declares for
that model.

Stacked on the map change, since without it kimi-k3 resolves to unknown levels
and the tier editor's capability-blind fallback list does not offer max.
2026-08-28 14:26:55 -07:00