Commit graph

45253 commits

Author SHA1 Message Date
wadrzl55333
6b5d287f2a style(proxy): suppress LIT002 for event-type literals in config registration
Passing ["success"]/["failure"] literals constructs a mutable list, which
the type-discipline budget counts; mark them mutable-ok mirroring the
DB-config call site.
2026-08-28 21:42:59 +08:00
wadrzl55333
d9da2db043 refactor(proxy): reuse DB callback helper for config-file registration
The config-file loop reached into litellm._known_custom_logger_compatible_callbacks
(2 new private accesses) which trips the basedpyright per-rule budget.
Route it through the existing _add_callback_from_db_to_in_memory_litellm_callbacks
helper instead: same known-list check, same instance registration, plus the
string fallback now living in _add_custom_logger_callback_to_specific_event
for when the custom-logger class cannot initialize. Net private API surface
change vs base: zero.
2026-08-28 21:34:08 +08:00
wadrzl55333
d02f573e52 style(proxy): remove blank line ruff 0.15.3 isort expects gone
Locked ruff is 0.15.3 (local 0.16.5 had different isort output); its
organize-imports wants no blank line between the import block and the
version try/except.
2026-08-28 21:21:54 +08:00
wadrzl55333
32a3265c85 style(proxy): merge litellm.utils imports to satisfy ruff organize-imports 2026-08-28 21:06:50 +08:00
wadrzl55333
114777be30 refactor(proxy): move config-callback registration to public util wrapper
The helper reached into litellm_logging private APIs; the repo's pyright
config has enableTypeIgnoreComments disabled and a strict per-rule budget,
so each new private import and call site counts against the gate. Move the
instance-registration-with-fallback logic behind a public
register_config_custom_logger_callback in litellm.utils, and let the
existing _add_custom_logger_callback_to_specific_event carry the fallback
(its else branch now registers the string when the custom-logger class
cannot initialize). Net change in private API surface: zero.
2026-08-28 20:54:43 +08:00
wadrzl55333
94ce3b1d50 test(proxy): pin known-callbacks precondition in config-file logger tests
test_add_callbacks_from_db_config clears _known_custom_logger_compatible_callbacks
without restoring it, and the conftest module snapshot skips underscore attrs,
so later tests depend on file order. Assert the precondition via monkeypatch
(which auto-restores) in both custom-logger tests.
2026-08-28 17:29:57 +08:00
wadrzl55333
1592bccf54 test(proxy): cover string fallback when custom-logger init fails
Covers the _register_config_custom_logger_callback fallback branches
(success + failure): when _init_custom_logger_compatible_class returns
None, the callback must land in the string lists so standard-route
logging keeps working.
2026-08-28 17:18:18 +08:00
wadrzl55333
ed99488fd3 fix(proxy): fall back to string callback when custom-logger init fails
The custom-logger class may not initialize without env credentials; keep
the string in litellm.success_callback in that case so standard-route
logging still works. Test sets LANGFUSE env so it exercises the instance
registration path.
2026-08-28 16:57:50 +08:00
wadrzl55333
84afc7e924 fix(proxy): route config-file custom loggers via module helper
Keeps the private-access suppression on one short line so it carries a
reason without exceeding the line budget.
2026-08-28 16:45:18 +08:00
wadrzl55333
82b789a99d style(proxy): keep pyright ignore comment under line limit 2026-08-28 16:39:15 +08:00
wadrzl55333
3a210c1b47 fix(proxy): strip callback string before custom-logger registration
_add_custom_logger_callback_to_specific_event skips both registration and
the string removal inside it when an instance already exists from an
earlier registration, leaving the config string behind in
litellm.success_callback. Remove the string explicitly in the config
path so the duplicate check can never strand it.
2026-08-28 16:24:37 +08:00
wadrzl55333
2bafe2c978 test(proxy): cover non-compatible string and failure-callback branches 2026-08-28 16:06:50 +08:00
wadrzl55333
817b0a9837 test(proxy): avoid module-global mutation in callback regression test 2026-08-28 16:00:47 +08:00
wadrzl55333
4f06fa8a50 fix(proxy): register config-file custom loggers as instances, not strings
Pass-through requests log through the async success path only, so a
config-file success_callback: ["langfuse"] string lands in
litellm.success_callback where pass-through never reads it: the async
list has no langfuse, and _should_run_sync_callbacks_for_async_calls
filters the string because langfuse is in
_known_custom_logger_compatible_callbacks. Result: no langfuse traces
for pass-through endpoints while standard routes log fine.

Route known custom-logger names through
_add_custom_logger_callback_to_specific_event, which registers a
custom-logger instance in both the sync and async callback lists, so
pass-through success/failure logging fires. Mirrors the DB-config path
(_add_callbacks_from_db_config).
2026-08-28 15:53:56 +08:00
tin-berri
ca0b951a43
feat(spend): report prompt caching savings as total and gateway-attributed (#38134)
* feat(spend): report prompt caching savings as total and gateway-attributed

`prompt_caching_savings_spend` credited every cached request, including caching a
client asked for with its own `cache_control` and caching a provider does implicitly,
so the number overstated what the gateway had any hand in.

Gating that column in place would have fixed the overstatement by changing what the
column means, leaving rows written before the change saying "all caching savings" and
rows after saying "gateway-injected only" with nothing to tell them apart, and forcing
a decision about rewriting history. It also breaks the cache-leakage estimate on the
dashboard, whose numerator would be gated while its denominator, the cached token
counts, would not, so the rate it extrapolates from would be quietly diluted.

Report both instead. `prompt_caching_savings_spend` keeps meaning every net dollar
caching saved, which is what a customer means by "what did caching save me", and the
new `gateway_injected_caching_savings_spend` carries the subset litellm caused by
injecting the breakpoints itself. Both are derived from the same marker, so this
changes what is done with it rather than how it is obtained.

The attributed figure is normally the smaller of the two, being a subset of the same
requests, but not always: a request that writes cache it never reads has negative net
savings, and excluding such a request can lift the attributed figure above the total.

Also stops the marker riding into a fallback leg. The fallback rebuild spread the
failed attempt's metadata forward, so a deployment that injected nothing inherited the
marker and was credited anyway, which silently restored the very overstatement this
separates out.

* fix(bedrock): credit gateway caching where the tool cachePoint is placed (#38478)

The savings marker records breakpoints litellm placed, and a tool_config
injection point becomes one only in the converse transform, and only when the
request carries tools. The prompt hook cannot see either condition, so marking
on the point's presence credited request shapes that cached nothing, while
Bedrock tool caching the gateway did cause went uncredited.

Record it at the placement site instead. The marker's reader also resolves its
bucket by value now: litellm_params declares litellm_metadata as None on every
request, so asking the shared name resolver named a bucket that was not there
and the mark was dropped.
2026-08-28 00:19:06 -07:00
yuneng-jiang
2b10dc5a7a
Merge pull request #38304 from BerriAI/litellm_fix_staging_ci_regressions
test: fix staging CI regressions from #38182, #38144, #38265, #37962, and #37969
2026-08-27 23:21:37 -07:00
Yuneng Jiang
74bc1efe65
Merge branch 'litellm_internal_staging' into litellm_fix_staging_ci_regressions
Resolve tests/llm_translation/test_together_ai.py in favor of staging:
bcb6a0a998 already landed the fail-open assertion for models missing from
the registry, so both models now list response_format and tools. This
branch's narrower gating of response_format no longer matches behavior.
2026-08-27 23:12:42 -07:00
tin-berri
02c1c45b53
fix(complexity_router): route client housekeeping calls to the cheapest tier (#38598)
A coding agent names each conversation by quoting the whole session and asking
for a title. The classifier rated the quoted session rather than the request, so
the cheapest call the client makes routed to the most expensive tier: 11 of 17
title generations in one day of real traffic came back COMPLEX.

Recognize those prompts by literal sentinel on the newest ask and route them to
the cheapest configured tier without classifying them, so the call costs nothing
to route. The placement is scoped to the one request that carries the sentinel:
it never displaces an operator's classifier plugin, the bandit cannot reach above
the tier as raised, it never becomes the session pin, and the sentinel that
matched is recorded on the routing decision. Detection reads the newest ask
alone, so a title request quoted into a later turn cannot cheapen the work that
follows it, and a keyword rule, an escalation keyword or the plan-mode floor all
still decide over it.

Regenerates the lazy OpenAPI snapshot, which was already stale on the base for an
unrelated Presidio guardrail field and failed the schema check on every PR.

Resolves LIT-6349
2026-08-27 22:55:09 -07:00
tin-berri
b3322e30a7
fix(anthropic): handle per-level reasoning_effort flags without supports_reasoning (#38618)
Some checks failed
Unit Tests / caching-local (push) Has been cancelled
Unit Tests / core-utils (push) Has been cancelled
Unit Tests / enterprise-package (push) Has been cancelled
Unit Tests / enterprise-routing (push) Has been cancelled
Unit Tests / integrations (push) Has been cancelled
Unit Tests / All Other Providers (push) Has been cancelled
Unit Tests / Vertex AI (push) Has been cancelled
Unit Tests / misc (push) Has been cancelled
Unit Tests / proxy-auth (push) Has been cancelled
Unit Tests / proxy-endpoints (push) Has been cancelled
Unit Tests / proxy-extras (push) Has been cancelled
Unit Tests / proxy-infra (push) Has been cancelled
Unit Tests / proxy-server (push) Has been cancelled
Unit Tests / responses-caching-types (push) Has been cancelled
GitHub Actions Security Analysis / zizmor (push) Has been cancelled
Postgres Tests / schema-migration (push) Has been cancelled
Postgres Tests / proxy-behavior (push) Has been cancelled
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
* fix(anthropic): handle per-level reasoning_effort flags without supports_reasoning

When a model has only per-level flags (e.g. supports_minimal_reasoning_effort: true)
but no explicit supports_reasoning flag, treat it as implicitly reasoning-capable.
This fixes gpt-5-search-api which declares minimal support but was incorrectly
degraded to low/minimal floor due to missing explicit supports_reasoning flag.

Test: verify per-level flag enables resolution path even without supports_reasoning.

Note: This change indirectly causes 20 azure deployments to forward max/xhigh
instead of degrading to high when requested, as these models now correctly
resolve their supported efforts through declared capability flags. This is
intended behavior (avoiding unnecessary degradation) but silent; operators
seeing increased latency/cost should check reasoning effort changes in logs.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(anthropic): explicit supports_reasoning=False wins over per-level flags

Greptile P1: the implicit-True branch bypassed the operator's explicit
supports_reasoning: false escape hatch when per-level flags were present
or inherited through the bare-twin lookup. Return () first on explicit
False, then apply the per-level implication only when the flag is unset.

Also drops a test comment that restated the test name (P2).

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-27 22:19:22 -07:00
tin-berri
39e5b0c2d1
fix(anthropic): drop and self-heal empty thinking blocks on /v1/messages (#38625)
* fix(anthropic): drop and self-heal empty thinking blocks on /v1/messages

* test(anthropic): pin early-signature carry across the blank thinking chunk skip
2026-08-27 21:41:56 -07:00
yuneng-jiang
bd7e9c1997
Merge pull request #38624 from BerriAI/litellm_/circleci-regressions-review-f0a542
fix(exceptions): keep a refused connection an APIConnectionError
2026-08-27 21:16:37 -07:00
ryan-crabbe-berri
546a9aa39e
Merge pull request #36514 from ansh-agrawal/feature/enforce-model-rpm-tpm-on-create
feat(proxy): opt-in flags to require rpm/tpm on model and project create
2026-08-27 21:14:12 -07:00
ryan-crabbe-berri
3beb02e512
Merge pull request #38601 from BerriAI/litellm_ui_navbar_papercuts
fix(ui): one-click theme toggle and matching Docs/Blog styling in the top bar
2026-08-27 21:06:41 -07:00
ryan-crabbe-berri
a96555593e test(proxy): narrow pytest.raises to HTTPException/ProxyException
Satisfies the test-tree ruff gate (PT011, B017)
2026-08-27 21:05:48 -07:00
Yuneng Jiang
a4049b730c
fix(exceptions): keep a refused connection an APIConnectionError
#38318 taught exception_type to map upstream status codes for providers with
no branch of their own. It reads the status code off the exception, but
_handle_error stamps 500 onto every failure that never carried one, so a
refused connection reached the mapper wearing a status code nothing upstream
had sent, and came back as InternalServerError instead of APIConnectionError.

The two are not interchangeable to a caller: a 5xx says the provider answered
and failed, which the router treats as a reason to cool the deployment down,
while a connection error says the request never landed.

BaseLLMException now records whether its status code was received or
synthesized, _handle_error sets that when it invents the 500, and the status
mapper declines to act on a code litellm made up, so those failures fall
through to the APIConnectionError the branch was always meant to produce.

Genuine upstream 5xx responses are untouched, which the second test pins.
The search transformation assertion #38318 had loosened to InternalServerError
goes back to APIConnectionError for the same reason.
2026-08-27 21:00:24 -07:00
ryan-crabbe-berri
b72b9126b5 fix(ui): restore the Blog hover highlight in the top bar
The Blog trigger carried `bg-transparent!`, which emits an important
background-color and so beat the non-important `hover:bg-accent` the shared
product-link class supplies. Docs lit up on hover and Blog stayed flat, the
same Docs/Blog inconsistency this branch is about on a different axis.

Dropping the override lets the shared hover through. `border-0!` stays, since
it keeps the trigger's box identical to the plain Docs anchor. Verified in the
browser: both now paint lab(96.1596 -0.0823438 -1.13575) on hover at 36px tall.
2026-08-27 20:58:16 -07:00
ryan-crabbe-berri
929946bdc1 fix(ui): give the shared product-link class a focus ring
Docs went from a ghost Button to a plain anchor, which dropped the focus
treatment the Button was supplying, so tabbing to Docs showed nothing while
tabbing to Blog showed a ring. The ring now lives on the shared class both
sides use, matching the Button primitive's values.

Kept Docs as a real anchor rather than routing it back through Button:
nativeButton={false} stamps role="button" onto the element, so the old
DashboardHeader markup announced Docs as a button and lost its link
semantics. Tests pin both the ring and the link role.
2026-08-27 20:50:41 -07:00
ryan-crabbe-berri
bc127a0b82 feat(proxy): reject wildcard project models under enforce_project_model_quota
Project auth expands all-proxy-models, * patterns, and access-group names
to many concrete models, but the rate limiter looks quotas up by the exact
requested model name, so a quota keyed on one of those entries is never
applied. Fail loudly with a 400 instead of storing an unenforceable quota
2026-08-27 20:49:22 -07:00
ryan-crabbe-berri
2e06762fc4 Merge branch 'litellm_internal_staging' into feature/enforce-model-rpm-tpm-on-create 2026-08-27 20:49:11 -07:00
tin-berri
3300fc3a96
fix(moonshot, together_ai): send the reasoning effort Kimi K3 accepts (#38611)
* fix(moonshot, together_ai): send the reasoning effort Kimi K3 accepts

Moonshot documents reasoning_effort as a top-level chat completions field for its reasoning
models, and defaults it to max, but MoonshotChatConfig builds its supported params by
subtracting from the OpenAI base list, which never carried that param. An explicit level
raised UnsupportedParamsError before the request left the proxy, so low and high were
unreachable and every call ran at the provider default

Together accepts low, high and max on Kimi K3. The per-model clamp added for the gpt-oss
family folds max down to high for every model except deepseek-ai/DeepSeek-V4-Pro, so a caller
asking for max silently got roughly half the reasoning budget they paid for

Moonshot now offers reasoning_effort whenever the registry says the model reasons. Together
sends a level the map entry declares unchanged, and keeps its existing table for every level
an entry does not name, so the only value that moves is Kimi K3 at max

* fix(moonshot): unwrap the bridges' effort object to the level string
2026-08-27 20:46:46 -07:00
tin-berri
3002994c0e
feat(ui): the model and wire layer for operator-defined auto-router tier sets (#38602)
* feat(ui): the model and wire layer for operator-defined auto-router tier sets

The data half of the custom tier set editor, with no visible UI change: the
editor lands separately on top of it.

One reader, activeTierRows, mints built-in rows with the canonical tier key as
their id, so the fallback pointer, the plan-mode floor and the per-model params
are row ids in both modes and nothing downstream branches on the mode. One
restrictions table carries each forbidden setting beside the reason shown for
it, so the greyed control and the omitted payload key cannot disagree. The
tier-set writes live in applyTierSetAction, where the fallback re-point and the
floor turn-off happen in one commit, unit-tested without a render.

buildComplexityRouterConfig emits tiers, tier_definitions and fallback_tier from
the rows, forces the LLM classifier, and strips what the backend rejects beside
tier_definitions. A payload built without a custom tier set is byte-identical to
what the form sends today.

* fix(ui): resolve frontend-lint failures on the tier-set model layer

* test(ui): drop a redundant explanatory comment per repo convention

* fix(ui): keyword rules follow their tier row through every tier-set action
2026-08-27 20:11:05 -07:00
Mateo Wang
5337c68dd3
Merge pull request #38257 from BerriAI/litellm_together_registry_sync
feat(models): add daily Together AI model registry sync script and workflow
2026-08-27 19:46:37 -07:00
Mateo Wang
98c52339d4
Merge pull request #38606 from BerriAI/litellm_bedrock_messages_midstream_fallback
fix(router): fall over on raised mid-stream errors in /v1/messages streams
2026-08-27 19:13:18 -07:00
tin-berri
49e6081978
fix(anthropic): resolve /v1/messages effort tiers through the capability owner (#38492)
* fix(anthropic): resolve /v1/messages effort tiers through the capability owner

The bridge normalizer read three supports_*_reasoning_effort booleans of its own, so it
answered "which levels does this deployment take" independently of the resolver behind
/model_group/info. The two disagreed: a proxy advertising kimi-k3 max forwarded high.

Degrade against resolve_supported_reasoning_efforts instead, with the chains as a declared
table. When no step of a chain is accepted, the fallback is read off that same resolved set
rather than assumed, since an entry naming its levels outright can exclude the tiers the
per-level flags treat as unconditional. none is never chosen as that fallback, being an off
switch rather than a tier, and a deployment accepting no tier at all keeps the floor every
deployment degraded to before.

* test(anthropic): pin the normalized effort at the /v1/messages request boundary

The existing coverage stopped at normalize_reasoning_effort_value, so nothing failed if the
handler dropped or overwrote the normalized tier on its way into completion_kwargs. Drive
_prepare_completion_kwargs instead and assert on the kwargs handed to acompletion, in both the
string and the dict effort shapes, including the provider-prefixed model name the handler is
actually called with.

Against the pre-fix normalizer the fallback case fails, and against the baseline before a map
entry could declare its levels 7 of the 12 fail, so the boundary is pinned rather than restated.
2026-08-27 19:13:13 -07:00
tin-berri
2306816d40
fix(shadow_eval): refuse a judge model that also serves one of the arms it grades (#38589)
A shadow eval whose judge_model is one of the router's tier models, the router's
default model, or a reverse job's baseline_model was accepted with no warning. An
LLM judge scores its own output higher than a rival's, so that tier's win rate
measures the judge instead of the models, and the job's whole budget buys a result
that has to be thrown away.

start_shadow_eval now rejects it with a 400 naming the colliding arm.

`judge_target` is the single answer to "where does a call to this name go for this
caller, and what answers it", and the resolvability gate, the collision gate and
the judge dispatch all read it. It has three outcomes and no others: the router
serves the name, the SDK serves it, or nothing does. Splitting that question is
what every bug here came from, so `router_resolves_model` and `answering_models`
are gone rather than joined by a third.

Two spellings of one model are one identity. A name is compared by what would
answer it, resolved through every channel `get_model_list` composes and then put
in the provider-qualified form litellm itself uses, so a judge given as `gpt-4o`
collides with a tier deployment serving `openai/gpt-4o`, and a judge given as
`openai/gpt-4o` collides with a deployment configured as bare `gpt-4o`. Both ends
are normalised because an admin writes them at different times.

Answering is also per-caller. The shadow and judge calls carry the shadowed key's
`user_api_key_team_id`, which is what the router selects deployments with, so the
endpoint derives the job's teams once from the keys it already looks up and every
check runs under them, and the judge dispatch picks its arm under the same team.
A team's public model name resolves to nothing for everyone else and a team's own
deployment resolves for nobody else, so a check that omits the team answers for a
caller who does not exist. A collision under any one team fails the job, because
every key's verdicts land in the same win rates.

Three sites were separately re-deriving "the provider models this name resolves
to", with unexplained divergence in whether they fell back to the literal name.
`Router.resolved_litellm_models` is now the one owner; the routing-plugin
candidate list and the stream-options check both delegate to it, and
`_deployment_litellm_model` is gone.

The router's arms come from `strategy_router_dependencies`, the same enumeration
the health check reads. Only the roles that serve are arms: a classifier or
embedding model picks the tier and never produces a response anyone judges. A
semantic auto-router keeps its routes in an opaque config blob, so only its
default model is enumerable and the guard is incomplete there by design, able to
miss a collision but never to invent one

The two regenerated artifacts carry `presidio_analyze_chunk_size_bytes` from
alters the spec; the sync gate runs on any PR touching litellm/proxy, so this one
has to carry the base's drift to go green
2026-08-27 18:44:44 -07:00
mateo-berri
e6a568d99b test(router): cover the raised-stream fallback helpers by name and trim their docstrings 2026-08-27 18:42:59 -07:00
tin-berri
09b23742e7
feat(proxy): dry-run a real request body on /auto_router/test_routing (#38590)
The endpoint built messages=[{"role": "user", "content": prompt}], so a dry run
could not carry prior turns, the caller's system prompt, or the tool definitions
a request advertises. A real agentic turn reduced to its last sentence classified
as trivial, which is why a config sweep reported savings for every configuration.

Accept messages, system and tools, and forward them to the same pre-routing hook
untranslated, with the raw-body snapshot built by the serving path's own owner,
refresh_proxy_server_request_body_snapshot. Loose types are deliberate: the hook
reads whatever dialect the surface produced, so validating against one surface's
schema would reject the others.

prompt stays as the single-ask shorthand, normalized into one user turn inside the
request model so the handler carries no mode branch.
2026-08-28 01:35:10 +00:00
Mateo Wang
10cd9259a3
Merge pull request #38100 from FelipeRodriguesGare/bugfix/tencent-thinking-extra-body
fix(tencent): route thinking through extra_body in chat completions
2026-08-27 18:23:11 -07:00
ryan-crabbe-berri
e5dcc6873e fix(ui): make the theme toggle switch on one click and stop Docs looking dimmer than Blog
The top bar's theme control needed a click on the sun/moon, then a menu, then
a choice, to do something every other product does in one click. It is now a
plain button that flips between light and dark, with the beta marker moved into
the label of the click that turns dark on. An explicit "system" choice is gone,
but next-themes still follows the OS for anyone who has it stored and has not
clicked yet.

Docs and Blog also drifted apart in the gateway header: Blog rendered through
the shared product-link class while Docs was a muted ghost button one size
down, so Docs read as dimmer and sat 4px shorter. Both now go through a shared
DocsLink component, which is also what the legacy navbar uses, so the pair
cannot drift again.
2026-08-27 18:05:04 -07:00
yucheng-berri
bb72815e70
fix(langfuse): warn and drop invalid LANGFUSE_TRACING_ENVIRONMENT instead of failing requests (#38582)
* fix(langfuse): warn and drop invalid LANGFUSE_TRACING_ENVIRONMENT instead of failing requests

* fix(langfuse): treat a dynamic environment equal to the raw deployment value as redundant
2026-08-27 18:03:42 -07:00
ryan-crabbe-berri
32b8edb4d5
Merge pull request #38572 from BerriAI/litellm_fallback_access_group_check
feat(proxy): opt-in enforce_fallback_model_access authorizes router fallbacks against the calling key
2026-08-27 18:03:34 -07:00
yucheng-berri
272458be0c
fix(router): copy instead of mutating caller metadata when scrubbing fallback stamp keys (#38586) 2026-08-27 18:03:25 -07:00
yucheng-berri
74050e03c5
fix(guardrails): add fail-open mode to CrowdStrike AIDR guardrail (#38568)
* fix(guardrails): add fail-open mode to CrowdStrike AIDR guardrail

Add a fail_on_error param (default True, preserving existing behaviour) to
the CrowdStrike AIDR guardrail, mirroring model_armor and generic_guardrail_api.

When fail_on_error=False the guard fails open only on server errors (5xx) and
connectivity failures, so the request proceeds unmodified. Caller-controlled
4xx responses and result.blocked policy blocks always fail closed. The
applied-guardrails header is recorded even on the fail-open path.

* fix(guardrails): fail open AIDR 4xx

* refactor(guardrails): isolate AIDR fail-open

* style(guardrails): format AIDR fail-open

* ci: satisfy unit workflow timeout invariant

* refactor(guardrails): accept AIDR mappings

* test(guardrails): inject AIDR HTTP client

* fix(guardrails): harden AIDR fail-open against delivered verdicts and record fail-open status

Reads the blocked verdict from the raw body before guard_output validation so schema drift or a changed verdict type cannot fail open past a delivered block. A transformed response that cannot be parsed fails closed so delivered redactions are never dropped. Fail-open runs record guardrail_status guardrail_failed_to_respond with timings instead of success. Restores the fail-open behavior tests dropped mid-PR and reverts the payload Mapping widening

* test(guardrails): cover fail_on_error wiring and fail-closed default for CrowdStrike AIDR

* chore(guardrails): annotate the transformed-drift detail payload for the LIT002 budget

---------

Co-authored-by: abrekhov <abrekhov@users.noreply.github.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-08-27 17:57:35 -07:00
tin-berri
77bbf4b5b7
feat(ui): dry-run an auto-router config against the backend before saving it (#38595)
* feat(ui): dry-run an auto-router config against the backend before saving it

Both auto-router forms built a payload and posted it, so anything the write gate
refused came back as a raw 400 with the backend's message buried in it. They now
POST the exact payload to /auto_router/validate_complexity_router_config first
and surface its verdict inline.

One dryRunRejection owns the gate, and it reads valid alone. The verdict's two
fields arrive independently, so gating on the error message would let a rejection
that carried none through to the write. A transport failure fails open as valid,
leaving the write gate authoritative rather than blocking a save on a flaky
network.

Applies to every auto-router, built-in tiers included.

* fix(ui): hold the auto-router create closed for the full dry-run and create sequence

A second submit while the dry-run round-trip was pending started another
create against the non-idempotent /model/new. The submit handler now
refuses re-entry and the button disables for the whole sequence, matching
the edit modal's loading guard. Also drops the explanatory comments this
PR had added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 17:53:11 -07:00
yucheng-berri
22a349ee70
fix(logging): stop stream-based log collectors classifying INFO logs as errors (#38476)
Route records below WARNING to stdout (WARNING and above stay on stderr),
emit ANSI color codes only when both streams are a TTY (honoring NO_COLOR),
and parse JSON_LOGS strictly so JSON_LOGS=false no longer enables JSON logs.
2026-08-27 17:42:00 -07:00
yucheng-berri
239ec955dc
fix(presidio): chunk oversized text before /analyze so large content blocks do not fail (#38483)
* fix(presidio): chunk oversized text before /analyze so large content blocks do not fail

The Presidio PII guardrail sent each content block to the analyzer as a
single /analyze call with no size check. Analyzer deployments commonly cap
the request body (the reporting deployment rejects bodies over 1,000,000
bytes with HTTP 413), so large blocks failed closed, and analyzer latency
grew linearly with payload size.

analyze_text now splits texts larger than presidio_analyze_chunk_size_bytes
(default 500,000 UTF-8 bytes, configurable per guardrail) into overlapping
chunks, analyzes them concurrently, remaps each detection's start/end onto
the original text, and deduplicates detections from the overlap regions.
Anonymization, blocked-entity checks, score filtering, numbered-token
unmasking, telemetry, and the dashboard entity positions all consume the
remapped global offsets unchanged.

Resolves LIT-4785

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(presidio): review-round hardening for chunked analyze

- measure the chunk budget on the JSON-serialized text (non-ASCII escapes
  expand beyond raw UTF-8, so a raw-byte budget could still exceed the
  analyzer body limit)
- share the chunk fan-out semaphore per event loop and instance instead of
  per call, so many oversized blocks cannot multiply concurrent analyzer
  calls
- apply configured score thresholds and deny list per chunk BEFORE overlap
  resolution, so a below-threshold span cannot displace a detection the
  thresholds keep

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 17:41:55 -07:00
tin-berri
1ab6fd89d2
fix(anthropic): carry the effort tier only where the target declares reasoning_effort (#38592)
The /v1/messages bridge decided a Claude target could take `reasoning_effort` from
the model name, which says nothing about the params the provider in front of it
accepts. Snowflake serves Claude over the Anthropic dialect and declares `thinking`
alone, so `get_optional_params` raised `UnsupportedParamsError` before the request
reached the wire: every adaptive request carrying an effort tier turned a 200 into
a 400 for all seven of its Claude entries.

The tier is now offered only where the target declares the param, reading the same
`get_supported_openai_params` the sibling `_supports_prompt_cache_key` reads twelve
lines up. A target declaring neither carrier keeps its bare `thinking` block, which
is what this bridge sent before it carried a tier at all.

Without a resolved provider the tier stays behind rather than being offered blind.
Resolving one from the model's prefix instead would run an OAuth device flow for
github_copilot and chatgpt, blocking for minutes, and one of the two callers in that
position is a logging callback. The copilot case is pinned by a test.
2026-08-28 00:41:46 +00:00
ryan-crabbe-berri
e6c4580a31
Merge pull request #38596 from BerriAI/litellm_fix_componentized_ui_virtual_keys_link
fix(ui): link Virtual Keys hint through the migrated /ui route
2026-08-27 17:30:20 -07:00
ryan-crabbe-berri
76e7bd41f4 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fallback_access_group_check 2026-08-27 17:29:08 -07:00
yassin
3ec3933c1f fix(ui): link Virtual Keys hint through the migrated /ui route
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-28 00:19:29 +00:00