Commit graph

46143 commits

Author SHA1 Message Date
yuneng-jiang
ed9d29a9b4
Merge pull request #39186 from BerriAI/litellm_internal_staging
chore(ci): promote internal staging to main
2026-09-01 13:47:42 -07:00
Yassin Kortam
aab9abdd1d
fix: keep litellm_credential_name from LiteLLM Params JSON and gate stored credential attach to proxy admins (#39047)
* fix(ui): keep litellm_credential_name from LiteLLM Params JSON when no credential is selected

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

* test(ui): drop null litellm_credential_name from AddModelPanel payload fixture

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

* fix(ui): validate JSON litellm_credential_name against accessible credentials

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

* fix(proxy): enforce proxy-admin-only credential attachment on model create/update

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

* fix(proxy): raise ProxyException for unauthorized credential attach and gate /model/update

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

* refactor(proxy): fold credential-change detection into can_user_attach_credential to satisfy complexity budget

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

* fix(proxy): decrypt stored credential name before unchanged-credential comparison

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

* test(proxy): cover credential attach rejection on add_new_model and patch_model

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

* test(proxy): annotate proxy-global patches with test-quality suppressions

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

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-01 13:46:18 -07:00
Mateo Wang
8ce02c0666
Merge pull request #39184 from BerriAI/litellm_fable_5_1_structured_output 2026-09-01 13:41:22 -07:00
ryan-crabbe-berri
6d6c9af4ab
Merge pull request #39155 from BerriAI/litellm_agent_hub_search
feat(ui): add search to the Agent Hub tab and admin agents table
2026-09-01 13:36:36 -07:00
Mateo Wang
44b595f0bb
Merge pull request #39136 from BerriAI/litellm_lit6611_requested_model_label_cap
fix(prometheus): bound requested_model label cardinality on client failure paths
2026-09-01 13:35:33 -07:00
yuneng-jiang
6028afa2ab
Merge pull request #39178 from BerriAI/litellm_revert_v2_migration_resolver_default
revert: default the proxy back to the v1 migration resolver
2026-09-01 13:32:18 -07:00
yucheng-berri
cdb1245e74
fix(s3): bound s3 object keys and download filenames for long Responses API ids (#39164)
* fix(s3): bound object keys and download filenames to s3 limits

Long OpenAI-compatible Responses API ids pushed the s3 object key past s3's
1024 UTF-8 byte cap, so the PUT failed with a 400 and the log record was
dropped. Keys that still fit are unchanged, byte for byte. An oversized one
now keeps a readable head of the file name and appends the sha256 of the full
name. A configured path/alias prefix that is long enough to overflow on its
own keeps whole leading path segments, so a prefix-scoped IAM policy or
lifecycle rule still matches, and ends in a short digest of the full
configured value so two operators do not land in the same folder.

The Content-Disposition filename carried the same unbounded id and hit s3's
2048 byte metadata-header cap, so the upload still failed with
MetadataTooLarge once the key was bounded. It is bounded the same way, head
plus digest, so two records downloaded from the console stay distinct files.

The full response id stays in the uploaded JSON payload.

* fix(s3): keep the configured prefix whole and spend the whole key budget

Shorten the response id first and only trim the operator's configured prefix
when the prefix itself is what does not fit, so prefix scoped IAM policies and
lifecycle rules keep matching. Trim by bytes rather than whole segments so the
longest possible string prefix survives, and route the audit log key through
the same shared builder.

* chore(s3): trim the comments and docstrings the review flagged

Keep the two external facts that are not visible from the code, the 1024 byte
object key cap and the 2048 byte metadata header cap, and drop the rest.
2026-09-01 13:30:02 -07:00
Mateo Wang
286a754999
Merge pull request #38839 from BerriAI/litellm_fix_chat_anyof_tool_schema
fix(openai): flatten top-level tool schema combinators on chat completions
2026-09-01 13:25:03 -07:00
mateo
d568bbe58d fix(bedrock): use tool fallback without forced tool_choice for claude-fable-5-1 structured output
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-01 20:19:46 +00:00
Mateo Wang
c913b09e66
Merge pull request #31725 from Srivatsa03/time-based-cost-pricing
feat(cost): support time-based off-peak pricing in cost calculation
2026-09-01 13:18:30 -07:00
yuneng-jiang
6c26345ab5
Merge pull request #39175 from BerriAI/litellm_ui_select_popup_race
test(ui): pick select options by role instead of by text
2026-09-01 13:05:54 -07:00
mateo-berri
60de5468d7 fix(cost): inherit the backend's raw cost map entry for off-peak-only deployments
Filtering copied fields by name dropped companion billing rules like
web_search_billing_unit and the regional uplift multipliers, so grounding
and uplifts billed differently through the deployment entry. Copy the
backend's raw litellm.model_cost entry wholesale instead, which also
removes the synthesized-zero special case since the raw entry only holds
real values.
2026-09-01 13:05:25 -07:00
Yuneng Jiang
529ac12ba5
test(ui): drop the helper docblock
The repo does not take explanatory comments. The reason the helper queries by
role lives in the commit that introduced it and in the PR description.
2026-09-01 13:01:46 -07:00
mateo
3e3e4d6970 fix(anthropic): use native structured output for claude-fable-5-1 on Vertex AI and Bedrock Invoke
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-01 20:01:03 +00:00
Yuneng Jiang
fd72a39b1b
revert: default the proxy back to the v1 migration resolver
This reverts merge commit 2b1bd20834 (#31125)

Two CircleCI jobs on the staging-to-main promotion went red the moment
that PR landed. proxy_multi_instance_tests boots two proxies against one
database, and both now race the same migration:

  Error: P3018 A migration failed to apply
  Database error code: 40P01, deadlock detected
  Process 73 waits for ShareLock on virtual transaction 4/11;
  blocked by process 75. Process 75 waits for ExclusiveLock on
  advisory lock [16384,0,72707369,1]; blocked by process 73

Neither proxy comes up, so the job times out after 300s waiting on
localhost:4000. The same wait took 36.5s on the last green run

Timeline: #31125 merged at 18:46:14Z and the failing run started at
18:49:59Z. The merge commit is not an ancestor of the last green
revision (194a3cc) and is an ancestor of the first failing one
(01de2837)

The v2 resolver was meant to avoid exactly this class of contention, so
the deadlock looks like a bug in it rather than a reason to abandon it.
Putting the default back to v1 buys time to fix it without holding up
the release
2026-09-01 12:56:08 -07:00
yuneng-jiang
fae4743207
Merge pull request #39141 from BerriAI/litellm_internal_staging
chore(ci): promote internal staging to main
2026-09-01 12:55:57 -07:00
Mateo Wang
8ae072b501
Merge pull request #39147 from BerriAI/litellm_openai_drop_toolless_tool_choice
fix(openai): drop tool_choice when request has no tools on chat completions
2026-09-01 12:50:38 -07:00
Mateo Wang
ab8b8deb39
Merge pull request #39144 from BerriAI/litellm_fix_responses_tool_call_id_shape
fix(responses): tool call id shape breaks gpt-5 -> claude fallback conversations
2026-09-01 12:50:35 -07:00
mateo-berri
0ec3e936b7 fix(cost): inherit the backend's full price structure for off-peak-only deployments
Copying only the flat token rates dropped threshold, tiered, service-tier,
cache, character, and per-second rates from peak-hour billing once cost
lookup switched to the deployment entry, and get_model_info synthesizes
zero flat rates for backends without one, which would have marked
tiered-only backends explicitly priced free. Copy every price-bearing
field instead, deep-copied, rejecting the synthesized zeros the way
_inherit_builtin_tiered_output_rate already does.
2026-09-01 12:47:26 -07:00
Yuneng Jiang
116efee3f7
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_ui_select_popup_race 2026-09-01 12:42:33 -07:00
Yuneng Jiang
eb53639ecb
test(ui): pick select options by role instead of by text
Clicking a Base UI select entry found by text or by a title attribute is a
race. The text node exists one render before the popup finishes entering,
and until then the positioner still carries pointer-events: none, so
user-event refuses the click and the test throws. Querying by role only
matches once the popup is exposed to the accessibility tree, which is after
that window closes.

Route the 37 remaining select interactions through chooseSelectOption, which
does the role query. Instrumenting the converted files shows the text query
resolving while the popup was still pointer-blocked on 6 of 41 samples; the
role query was never blocked.

Seven files kept their text queries because their popup entries carry no
accessible role, so there is nothing to query by.
2026-09-01 12:42:27 -07:00
yuneng-jiang
a3e115f4cd
fix(ui): render the guardrail garden detail page with theme tokens (#39131)
The page set its headings, table borders, sidebar labels and tag pills
inline with a fixed light palette (#202124, #5f6368, #dadce0, #f8f9fa,
#fff), so in dark mode it drew dark text on hardcoded white surfaces.

Move those to the foreground/muted/border/card/info tokens, matching
the back link and Create Guardrail button that already used them.
2026-09-01 12:39:43 -07:00
yuneng-jiang
4c86b1d58c
test(e2e/ui): cover the Usage page activity tabs (#39061)
* test(e2e/ui): cover the Usage page activity tabs

Usage had one test, on Top Virtual Keys. The Key, Model and Endpoint Activity
tabs are the ones an admin reads to answer where the spend went, and none of
them was covered.

Also fixes waitForKeyInDailyActivity, which only read the first page of
/user/daily/activity. The route paginates, so once a run generates more keys
than one page holds, the helper spins for its full 120 seconds and then blames
the rollup for a key the rollup wrote correctly. The Usage page itself already
walks every page; the helper now matches it.

* test(e2e/ui): route the user-creation call through SERVER_ROOT_PATH

Review caught /user/new posting to the server root, which misses the proxy
when it is mounted under a prefix. traffic.ts already had the helper for
this; it is now exported so specs making their own management calls can use
it too.

Also drops the mutable accumulators from the daily-activity paging, which
the repo conventions ask for.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-01 12:36:17 -07:00
yuneng-jiang
284e96cfe7
test(e2e/ui): cover the team Settings tab (#39058)
The Teams tests covered creating, deleting and membership, but nothing on the
Settings tab, which is the form that posts the whole team back. That is the
shape behind the reports of a team losing its metadata or its model aliases
after an unrelated edit.

Each test creates its own team rather than editing a seeded one. The limits
test pins the models and members the edit had no business touching, and the
alias test calls the new alias with a team key instead of trusting the
readback, since an alias the router never resolves reads the same either way.

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-01 12:35:45 -07:00
yuneng-jiang
978aa2816b
test(e2e/ui): stop the suite failing on things that are not regressions (#39063)
* test(e2e/ui): stop the suite failing on things that are not regressions

Five tests in the UI suite fail for reasons that have nothing to do with the
product being broken, which is enough to keep the whole leg red.

Two need a premium proxy and fail hard without one: Regenerate Key renders
disabled when the proxy is unlicensed, and /model/new refuses a team-scoped
deployment. Both now skip without LITELLM_LICENSE, the way three other tests
in this suite already do.

Three consumed a seeded fixture: Delete key, Delete a team and remove a member
each destroyed the row they needed, so the retries CI runs with were guaranteed
to fail and the suite could not run twice against one database. They now create
what they destroy.

Top Virtual Keys ranks by spend and every mock deployment costs $0, so which
keys make the list came down to how ties happened to sort. It now sends its
traffic through a priced deployment and earns its place.

* test(e2e/ui): clean up the fixtures these tests create

Review caught two leaks: the priced deployment the usage test registers and
the user the team-admin test adds both outlived the run, so repeated runs
grew shared state that later routing and rosters can see.

Also brings in the paginated daily-activity read. /user/daily/activity pages
its per-key breakdown and the helper only read the first page, so the usage
test spent its full timeout blaming the rollup for a key the rollup wrote.

* test(e2e/ui): read the licence from the proxy, not the runner

Review pointed out that checking LITELLM_LICENSE in the runner's environment
describes the wrong machine: Playwright can be pointed at a proxy configured
somewhere else, and then the skip either hides coverage or runs a premium
test against an unlicensed target.

The admin session JWT already carries the premium_user claim the dashboard
itself reads to enable these controls, so both skips now use that.

* test(e2e/ui): clean up fixtures on the failure path too

Review caught both cleanups sitting at the end of the test body, where a
failing assertion skips them, and both discarding the response so a refused
delete passed quietly. They move to afterEach and assert the delete landed.

The priced deployment matters most: left behind it keeps its custom pricing
and goes on changing what later runs route and what they cost.

* test(e2e/ui): wait for the priced deployment to become routable

The Top Virtual Keys test registered a priced deployment and sent the key's
traffic through it on the next line, so on the deployed stack it failed with
"no healthy deployments for e2e-usage-priced-...": /model/new had written
the row but the router had not picked it up yet.

Polls a ping until the deployment answers before the test sends the request
it measures, matching what the addModel spec already does for a model added
through the UI. A ping that fails writes no spend log, so the retries cannot
move the ranking this test asserts.

* test(e2e/ui): register fixtures for cleanup before the step that can fail

Review found both helpers handing their id back to the caller to record, with
a failure-prone call in between: the priced deployment was registered after
the routability wait, and the added user after /team/member_add. Either
failing left the resource in the shared database with nothing tracking it.

Both now take the teardown list and add themselves as soon as the resource
exists, so the afterEach removes it however the rest of setup goes.

* test(e2e/ui): resolve the priced deployment for teardown by name

Review pointed out the remaining gap: /model/new can persist the deployment
and still answer non-2xx, and the id was only recorded after the response was
asserted, so that path left it behind with its custom pricing.

The name is now claimed before the request and teardown looks it up in
/model/info, so a create that saved without answering 2xx is still removed and
one that never saved is simply not there.

* test(e2e/ui): claim the member id before creating the user

/user/new can persist the user and still answer non-2xx, and the id is chosen
by the test rather than returned by the proxy, so registering it before the
call is what closes the last create-failure path.

Teardown now skips an id whose user is not there, so claiming it up front
cannot fail a run where the create never landed.

* test(e2e/ui): wait out the router reload when resolving a deployment to delete

/model/info answers from the router, not from the database, and /model/new
catches and logs a failed in-request reload while still answering 2xx. A
deployment can therefore be persisted and absent from the listing until the
next reload, which is where teardown was giving up and leaking it.

Teardown now retries the lookup for a little over one
PROXY_CONFIG_RELOAD_INTERVAL_SECONDS before treating the name as never
persisted, so the only names it skips are the ones that really are not there.

* test(e2e/ui): prove a stored credential survives a config reload before using it

The Test Connect assertion has been failing intermittently on the full-suite
runs. Artifacts from litellm-e2e-ui build 165 show the UI sending
litellm_credential_name and the proxy answering with the credential unapplied:
raw_request_api_base was https://api.openai.com/v1/ rather than the mock base
the credential carries, and the call died on an upstream 404 for the model. The
same credential had resolved on three probes eight seconds earlier.

The proxy's periodic credential refresh takes a database snapshot, prunes any
in-memory credential missing from it, then re-adds the snapshot. A credential
created while that is in flight gets pruned and stays gone until the next tick,
and load_credentials_from_list fails open onto the ambient key, so nothing in
the error names the credential.

The existing pre-check asked for three consecutive probe successes, but they
completed in under a second, so they could not span a refresh. Space them so
the run covers a whole interval, which is what proves the credential survived a
refresh and is therefore stable.

* test(e2e/ui): find a database-only deployment through the search listing

/model/info answers from the router, so a deployment that reached the database
while /model/new's in-request reload failed is invisible there, and waiting on
the next reload only helps if reconciliation eventually picks it up.

/v2/model/info?search= runs a bounded query against the model table and
deliberately returns rows the router does not hold, so it resolves those
deployments to the id /model/delete needs. Falling back to it removes the wait
as well: absent from both listings now means the deployment never persisted.

* test(e2e/ui): delete the temporary member without a lookup that can skip it

Teardown asked /user/info first and treated any non-2xx as absence, so a
transient failure on the lookup silently skipped the delete and left the user
behind, which is the leak the claimed id was meant to close.

/user/delete answers 404 for an id that is not there, so it can carry both
cases on its own: 404 means the create never persisted, and anything else that
is not 2xx now fails the teardown instead of passing quietly.

* test(e2e/ui): reach the database fallback when the router listing fails

Asserting on /model/info threw before the fallback could run, so a failure on
the router-backed listing aborted teardown and left the deployment persisted,
which is the leak the fallback was added to close.

The router listing is best-effort now: an unreadable response just falls
through to the search-backed one. That listing is the authoritative answer to
whether the deployment exists, so it is the one that has to be readable, and a
name missing from it is a create that never persisted.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-01 12:34:57 -07:00
yuneng-jiang
7809eacb8b
test(e2e/ui): cover the Logs page filter drawer (#39056)
The Logs page had coverage for opening a request and for the End User filter,
but nothing for the filters an on-call engineer actually reaches for: whose
key made the request, and which requests failed.

Each test mints its own keys and asserts on request ids it generated itself,
so a filter that quietly does nothing fails on the other key's row still being
on screen rather than passing because our own row happens to be there.

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-01 12:34:10 -07:00
Sean Yasnogorodski
8a4ba78869
feat(guardrails): add Alice guardrail (#38898)
* feat(guardrails): add Alice by ActiveFence guardrail

Adds `guardrail: alice` — policy-based guardrails for prompts and model
responses, evaluated against ActiveFence's Alice.

What makes this different from the other providers: Alice evaluates against
policies configured per *application*, and a proxy typically fronts several of
them, so the application cannot be a static config value. It is named on the
LiteLLM virtual key instead:

    curl $PROXY/key/generate -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
      -d '{"key_alias": "payments-bot",
           "metadata": {"alice_app_id": "payments-bot"}}'

read via `CustomGuardrail._get_admin_metadata`, with `key_alias` as the
fallback. That helper is what makes it trustworthy: it reads whichever metadata
holder the proxy wrote the authenticated key's values into — which differs by
route — and the proxy strips caller-supplied `user_api_key_*` from both, so a
caller cannot point its own traffic at an application with laxer policies than
the one its key was issued for. A request whose key names no application is
refused rather than evaluated against a guess.

Implements `apply_guardrail` only, so pre_call, during_call, post_call and
streaming all come from UnifiedLLMGuardrails. Blocks with
GuardrailRaisedException; masks by substituting Alice's redacted text; a MASK
carrying no replacement blocks rather than passing the original through. A
verdict reporting `errors[]` is treated as a failure, not a pass — otherwise a
half-evaluated message would be allowed. `unreachable_fallback` (already on
LitellmParams) chooses fail-closed or fail-open on transport failure.

Config:

    guardrails:
      - guardrail_name: alice
        litellm_params:
          guardrail: alice
          mode: [pre_call, post_call]
          api_key: os.environ/ALICE_API_KEY

21 tests in tests/test_litellm/proxy/guardrails/guardrail_hooks/test_alice.py
cover registration, credential resolution, the app-id ladder including the
forged-metadata case, every verdict, and both unreachable policies.

No new LitellmParams field, so no schema.d.ts regeneration is needed.

* refactor(guardrails): post to Alice's LiteLLM endpoint and forward verbatim

Switches from `/v2/evaluate/message` — Alice's single-text endpoint — to
`/v2/evaluate/litellm`, which takes the hook's arguments as they arrive and
answers with a verdict.

That inverts where the work happens, and shrinks this plugin accordingly. It
now selects nothing and renames nothing: it posts `{input_type, inputs,
request_data}` and enforces `{verdict, categories, correlation_id, message,
replacements}`. Which parts of a conversation are worth evaluating, and how a
verdict is reached, are decided by Alice — so changing either is a change on
their side rather than a LiteLLM upgrade for every user.

The app-id resolution this plugin carried is gone with it. Alice reads the
application off the authenticated key's metadata itself, from the payload it is
handed, so the ladder here was duplicating a decision the far side already
makes. The security property is unchanged and still comes from the proxy
stripping caller-supplied `user_api_key_*` before a guardrail sees the request.

Masking is now positional — the far side chose which texts it was answering
for, so it says which by index. Only `texts` is written; a new
`structured_messages` object would make the chat translation layer skip the
`texts` write-back and silently drop the edits. A mask that lands nowhere
blocks rather than passing the original through.

`request_data` carries live Python objects (an OpenTelemetry span among them),
so `_json_safe` copies it into something serialisable by a mechanical rule
rather than a field list — a list drifts from what the far side needs, a rule
cannot. Serialising naively raises, and that error would read as "guardrail
unavailable" on every request.

26 tests, covering verbatim forwarding, each verdict, positional masking, the
`structured_messages` identity trap, both unreachable policies, and the
serialiser's handling of unserialisable values and cycles.

* fix(alice guardrail): satisfy lint and code-quality CI gates

- Bound _json_safe's recursion and register it in recursive_detector's
  ignore list (it already caps depth and dedupes cycles by id, matching
  the repo's established pattern for legitimate bounded recursion).
- Clear ruff-strict budget breaches: annotate __init__'s return type,
  raise TypeError (not ValueError) for a bad response body, type
  _json_safe's payload as object instead of Any, and file-scope-ignore
  ANN401 for **kwargs (forwarding it as object broke the call into
  CustomGuardrail.__init__, confirmed via basedpyright).
- Clear type-discipline budget breaches: suppress the construction/
  annotation checks on one-shot HTTP payloads, the module-level
  guardrail registries, and _json_safe's bounded accumulator; narrow
  AliceVerdict's list fields to tuples and _evaluate's request_data to
  Mapping[str, object] where nothing downstream mutates them.

* test(alice guardrail): assert the guardrail actually registers

The registration test called init_guardrails_v2 and asserted nothing, so it
passed whether or not the guardrail was ever registered — TQ001 in the
test-quality gate, and a fair catch: a test that cannot fail is not covering
the thing it names.

Now asserts exactly one AliceGuardrail lands in litellm.callbacks under the
configured name.

This surfaced only after the ruff-strict and type-discipline gates stopped
failing ahead of it; the lint job runs its gates in sequence, so an earlier
failure masks every later one.

* fix(alice guardrail): reach 100% patch coverage, drop the ActiveFence naming

Codecov flagged 10 uncovered lines, all of them error paths — which is where a
guardrail most needs covering, since each one decides whether traffic flows
unscreened.

Two of the ten turned out to be dead rather than untested, and are removed:

- `except GuardrailRaisedException: raise` in apply_guardrail. `_evaluate`
  raises httpx errors, Timeout and TypeError, never that — so the clause could
  never fire.
- the trailing `json.dumps` probe in `_json_safe`. Everything json.dumps
  handles natively is caught by the isinstance branches above (a dict or list
  subclass included), so anything reaching the bottom — bytes, datetime, an
  OpenTelemetry span — cannot cross the wire regardless. It now says so and
  returns None.

The rest are now tested: a timeout, 502/503/504 as unreachable, a 4xx as NOT
unreachable (a rejected credential is our misconfiguration, not an outage, and
must not fail open), a non-object response body, and a model whose model_dump
raises.

Also drops "by ActiveFence" throughout — the product is Alice — and points the
header at alice.io. `ui_friendly_name` is now "Alice", which is the key
guardrailLogoMap and the garden card look up, so all three moved together.

* fix(alice guardrail): strip caller credentials, widen unreachable detection, block partial MASK

Addresses PR review: request_data no longer forwards secret_fields.raw_headers or
the root api_key to Alice (the caller's Authorization token in the clear otherwise);
HTTP 500, malformed JSON, and a non-object body now route through the configured
unreachable_fallback instead of raising raw, so fail_open still fails open on those;
a MASK verdict with even one out-of-range replacement now blocks entirely instead of
silently letting the rest through unmasked. Also tightens request_data's type and
documents the known streaming-mask limitation on the class.

* fix(alice guardrail): strip credentials at any depth, stop filtering on texts

secret_fields/api_key/headers/provider_specific_header can appear nested
under proxy_server_request, metadata, litellm_metadata, and their
requester_metadata/body sub-paths in a real captured payload — a
top-level-only strip missed all of those. _json_safe now drops these keys
by name wherever they occur during serialization, so a new nesting path
can't reintroduce the leak.

apply_guardrail also stopped skipping the call whenever texts was empty,
even when tool_calls/images/structured_messages carried content — that
was the plugin making a selection decision Alice's design says belongs on
the far side. It now only skips when none of the selectable fields have
anything in them.

* fix(alice guardrail): route an undecodable response body through the fallback

`response.json()` raises UnicodeDecodeError when the body carries bytes that
are not valid UTF-8, and that escaped the except clause: UnicodeDecodeError is
a *sibling* of json.JSONDecodeError under ValueError, not a subclass of it, so
naming only JSONDecodeError left it uncaught. Both fallback modes surfaced a
raw decoding error instead of applying unreachable_fallback — which for a
fail_open deployment meant a hard failure where it had asked for an allow.

Named explicitly rather than widening to ValueError, so the clause still says
which three conditions it means. Tested under both policies.
2026-09-01 12:33:39 -07:00
Mateo Wang
4517e5e613
Merge pull request #39148 from BerriAI/litellm_claude_fable_5_1
feat(models): add Claude Fable 5.1 across Anthropic, Bedrock, Vertex AI, and Azure AI
2026-09-01 12:30:37 -07:00
mateo-berri
fa581931f7 test(llm_translation): expect unpaired server tool calls to replay as tool_use 2026-09-01 12:27:18 -07:00
Mateo Wang
695d943745
Merge pull request #39104 from BerriAI/litellm_decrease_anys_opus5_r3
refactor(types): replace Any with precise types across 73 modules
2026-09-01 12:26:52 -07:00
mateo-berri
4875872fe5 fix(cost): treat a non-mapping off_peak_pricing value as never off-peak
A bare string or list under off_peak_pricing in YAML passed the truthy
guard and crashed _is_off_peak with AttributeError, breaking cost
calculation for that deployment. Malformed pieces of the block are
documented to not match rather than error, so guard the block itself the
same way and bill standard rates.
2026-09-01 12:25:39 -07:00
mateo
d6005a1876 merge: resolve conflict with litellm_internal_staging in anthropic transformation tests
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-01 19:23:47 +00:00
yuneng-jiang
0cf236bebb
test(e2e/ui): cover creating, testing and deleting a guardrail (#39053)
* test(e2e/ui): cover creating, testing and deleting a guardrail

The Guardrails page had no browser coverage. The RC checklist covers it by
hand against a live Presidio, which is why it has always been skipped in CI.

These drive the LiteLLM content filter instead, which runs inside the proxy,
so the whole flow is exercised without a third-party moderation service. The
create test does not stop at the table row: it sends a prompt carrying the
keyword it just banned and asserts the gateway refuses it, then sends a clean
prompt through the same guardrail and asserts it is served.

* test(e2e/ui): delete the guardrails these tests create

Review caught the fixtures being left behind. Guardrails are database rows
that show up in the table and in the playground's list, so a run that leaves
them changes what the next run sees.

Also trims the comments that restated what the helpers already say.

* test(e2e/ui): fail the run when guardrail teardown does not delete

Review caught the afterEach discarding the DELETE response, so a failed
cleanup finished quietly and left the guardrail for the next run to trip on.

* test(e2e/ui): wait for a new guardrail to reach the request path

The wizard test drove one chat completion immediately after creating the
guardrail and required a 400. A trace from the deployed stack shows the
record is stored correctly (blocked_words, action BLOCK, block_on_violation)
and the call six seconds later is still served unguarded, so the first
request can land before the proxy picks the guardrail up.

Polls the same call to the same 400 instead, which keeps the assertion and
lets the refresh land. If it never blocks, this stays red, which is what we
want it to say.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-01 12:21:45 -07:00
mateo-berri
8d0e7aee2f test(router): cover _inherit_builtin_base_rates_for_off_peak directly
The router_code_coverage gate only counts calls made from test files with
router in the filename, so the helper needs direct unit tests beside the
other inheritance helpers in test_router_model_cost_isolation.py: fills
missing base rates from the builtin entry, leaves explicit rates alone,
and no-ops without a block or for an unmapped backend model.
2026-09-01 12:20:09 -07:00
Mateo Wang
c50d83ece2
Merge pull request #39070 from BerriAI/litellm_bedrock_invoke_native_structured_output
fix(bedrock): forward native structured outputs on Invoke instead of silently inlining the schema
2026-09-01 12:19:15 -07:00
Mateo Wang
435433fa07
Merge pull request #39149 from BerriAI/litellm_qwencloud_provider_aliases
feat(dashscope): add QwenCloud and Qwen AI Platform provider aliases
2026-09-01 12:18:05 -07:00
yuneng-jiang
33004d2f0c
test(e2e/ui): cover the Budgets page create, edit and delete flows (#39052)
* test(e2e/ui): cover the Budgets page create, edit and delete flows

The Budgets page had no browser coverage at all, so an admin creating or
editing a spend cap through the UI was only exercised by hand at RC time.

Each test reads the budget back from /budget/list, a different route from
the one the table renders, so a row that only exists in the table's cache
does not pass. The edit test pins the rate limits an unrelated spend-cap
edit has no business touching.

* test(e2e/ui): trim comments that restate the test steps

Review flagged the explanatory comments as restating ordinary setup rather
than explaining anything. Keeps the two that carry the regression rationale
for an assertion and drops the rest.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-01 12:17:02 -07:00
mateo-berri
b0751169eb fix(cost): bill off-peak rates for deployments that set only off_peak_pricing
Cost lookup selects the deployment-scoped cost map entry only when custom
pricing is detected and the entry carries a base pricing field. A deployment
whose model_info held nothing but off_peak_pricing failed both conditions, so
its schedule was silently ignored and every request billed at the shared
backend rate.

use_custom_pricing_for_model now also treats deployment-scoped pricing fields
in the metadata model_info as custom pricing, and the router inherits the
backend model's built-in base token rates onto such an entry at registration,
which also lets cache pricing inheritance apply. Regression tests cover the
registration, the detection, and the costed request end to end.
2026-09-01 12:14:46 -07:00
mateo-berri
2adae6b475 fix(prometheus): pass through router-originated labels when no proxy router exists 2026-09-01 12:14:27 -07:00
Mateo Wang
f3dbd253be
Merge pull request #38106 from Timik232/bugfix/streaming-stable-response-id
fix(streaming): keep response id stable across streamed chunks
2026-09-01 12:11:32 -07:00
mateo-berri
98ea5eaab4 fix(responses): correlate streamed tool call events on normalized item ids 2026-09-01 12:08:22 -07:00
mateo
d816b75dd4 feat(bedrock): gate forced tool_choice on supports_forced_tool_use in converse
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-01 19:05:03 +00:00
mateo
6513f5c539 test(utils): allow supports_forced_tool_use in model prices schema test
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-01 19:05:03 +00:00
Mateo Wang
4c3ef9ae0a
Merge pull request #39023 from BerriAI/litellm_add_azure_deepseek_v4_flash_0731
feat: add Azure AI DeepSeek V4 Flash 0731 pricing
2026-09-01 11:54:25 -07:00
yuneng-jiang
75f0a22fc6
Merge pull request #39130 from BerriAI/litellm_dark_mode_skill_detail
fix(ui): render the skill detail page with theme tokens
2026-09-01 11:52:57 -07:00
mateo-berri
0042493bca Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_bedrock_invoke_native_structured_output 2026-09-01 11:50:05 -07:00
mateo
a92ca6cfde chore(models): regenerate model prices schema for supports_forced_tool_use
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-01 18:48:26 +00:00
ryan-crabbe-berri
d9f7f9ea16 feat(ui): add search to Agent Hub tab and admin agents table
Ports the Model Hub search to the AI Hub Agent Hub tab and the admin
/agents toolbar as a client-side filter over agent name and description.
Extracts the hub search matching into utils/searchUtils and fixes the
public Model Hub rendering the whole catalog when a search matches
nothing (LIT-5230)
2026-09-01 11:46:58 -07:00
ryan-crabbe-berri
01de283715
Merge pull request #39142 from BerriAI/litellm_osv_browserslist
build(deps): bump browserslist to 4.28.8 to clear osv-scan
2026-09-01 11:46:26 -07:00
yuneng-jiang
2b1bd20834
Merge pull request #31125 from BerriAI/litellm_/stoic-jones-7de871
feat(proxy): default to the v2 migration resolver, keep v1 as an opt-out
2026-09-01 11:46:13 -07:00