Commit graph

4637 commits

Author SHA1 Message Date
Yuneng Jiang
e37d1b0cb6
[Fix] Deflake spend tracking tests
Two independent deflakes:

1. test_ui_view_spend_logs_unauthorized (unit) was returning 400 instead
   of 401/403 when earlier tests in the file left proxy-auth globals
   (prisma_client, master_key, user_custom_auth, general_settings,
   user_api_key_cache) in a state that let invalid tokens pass auth and
   fall through to the endpoint's own start_date/end_date validation.
   Add an autouse fixture that pins those globals to their import-time
   defaults for every test in the file. Harden the assertion to include
   response body so future flakes are diagnosable.

2. test_basic_spend_accuracy (CI job proxy_spend_accuracy_tests) depends
   on the Redis transaction buffer flushing spend to Postgres. The buffer
   uses a single global pod-lock key (cronjob_lock:db_spend_update_job)
   and a single global buffer list key. Pointing the proxy at the shared
   remote Redis means concurrent CI pipelines contend for the same lock
   and can drain each other's buffer into the wrong database. Add a
   start_redis reusable command that boots a per-job redis:7-alpine
   container (digest-pinned), and switch proxy_spend_accuracy_tests to
   REDIS_HOST=host.docker.internal:6379 so lock and buffer state are
   isolated per CI run.
2026-04-23 14:13:55 -07:00
Yuneng Jiang
c41567eaa0
fix(budget_reset): use raw SQL for IS NOT NULL filter on Json? columns
The periodic budget-window reset job filtered keys/teams with
`where={"budget_limits": {"not": None}}`. The prisma-client-python
library does not support null-filtering on `Json?` columns (no
DbNull/JsonNull sentinel — upstream issue #714). The client drops the
`None` value during serialization and the engine rejects the query with
`MissingRequiredValueError: where.budget_limits.not: A value is
required but not set`, so neither the key nor team reset path runs.

Switch those two `find_many` calls to `query_raw` with
`WHERE budget_limits IS NOT NULL`, selecting only the PK and the
`budget_limits` column. Writes still go through the ORM. Add unit tests
covering the expired/unexpired paths for keys and teams, string-encoded
JSON payloads, empty payloads, error isolation between the two paths,
and a regression guard asserting the query still uses `IS NOT NULL`.
2026-04-23 12:26:25 -07:00
Ryan Crabbe
85f9c5e83f
feat(mcp): resolve team/key MCP permissions by server name or alias
team.object_permission.mcp_servers (and the per-key equivalent) previously
only accepted server_id strings. For config-loaded MCP servers, the id is
derived from a hash that includes the server URL, so the same logical
server in two regions ends up with two different ids in a shared database.
Permission lists had to enumerate every region's id.

Add a single MCPServerManager.expand_permission_list() helper that resolves
each entry against the current region's config + DB registry union: entries
that match a server_id pass through, entries that match an alias/server_name/
name expand to every matching id, and unresolved entries drop with a debug
log so stale or typo entries are diagnosable. Wire it into the four
_get_allowed_mcp_servers_for_* helpers so direct server entries and
mcp_tool_permissions dict keys are both expanded before the intersection.

Access-check outcomes are unchanged for existing id-based permissions;
name-based entries now resolve instead of being silently denied.
2026-04-23 11:13:30 -07:00
Sameer Kankute
d5449f5b1a
Merge pull request #26300 from BerriAI/litellm_oss_staging_04_22_2026
Litellm oss staging 04 22 2026
2026-04-23 18:53:58 +05:30
user
f5b4564466
fix(mcp): OAuth 2.1 spec compliance + cookie exp + loopback edge cases
Address codex review P1 + P2 findings:

- BYOK /token now accepts OAuth 2.1 clients that omit redirect_uri
  (draft-15 §4.1.3 dropped the requirement). When the client does
  submit a value, equality is still enforced vs the /authorize record.
  PKCE + client_id binding cover the security role redirect_uri
  played under RFC 6749.
- _user_id_from_session_cookie requires the ``exp`` claim on the UI
  session JWT (PyJWT options={"require": ["exp"]}) so leaked cookies
  have a bounded lifetime.
- validate_loopback_redirect_uri rejects URIs with a fragment
  (RFC 6749 §3.1.2) and catches malformed-URI ValueError so
  unparseable input surfaces as 400 invalid_request instead of 500.
2026-04-23 03:39:41 +00:00
user
b5c78d7db4
fix(auth): guard _team_obj_from_token against team-less tokens
The HTTPException arm in _run_centralized_common_checks assumed the
exception came from the team-object fetch, but asyncio.gather raises
the first exception from any of the five gathered coroutines. If
get_user_object / get_project_object / get_end_user_object raises
HTTPException on a token with team_id=None, the assert inside
_team_obj_from_token fires and the outer auth-exception handler
mishandles it.

Guard on team_id before calling _team_obj_from_token and default
team_object to None otherwise. (Greptile P1.)
2026-04-23 03:17:49 +00:00
user
200a38c3af
fix(mcp): revalidate loopback at /callback + no-store on /token
Address codex review P0 + P1 findings on the discoverable OAuth proxy:

- /callback now re-validates that the decoded base_url is loopback before
  302-redirecting to it. State is encrypted but pre-existing states minted
  before the /authorize validation was added have no expiry and remain
  valid; validating at the sink closes the open-redirect + code-theft
  primitive for those stale states too. (VERIA-57 root cause B, P0.)
- /token responses now set Cache-Control: no-store + Pragma: no-cache
  per RFC 6749 §5.1 (P1).
- Move TOKEN_NO_CACHE_HEADERS constant from byok_oauth_endpoints into
  the shared oauth_utils module so both endpoints use the same value.
2026-04-23 03:16:37 +00:00
user
ef108e79a1
fix(mcp): validate loopback redirect_uri in discoverable OAuth proxy
Extract the BYOK loopback redirect_uri check into a shared
oauth_utils.validate_loopback_redirect_uri helper. Call it in
discoverable_endpoints.authorize_with_server before the client-supplied
redirect_uri is encrypted into the OAuth state.

Without this check, a non-loopback redirect_uri was encoded into the
state parameter and decoded on /callback to 302 the user back to the
attacker's URL with the authorization code attached — an open-redirect
+ code-theft primitive (VERIA-57 root cause B). The /callback handler
is already safe because state is HMAC-signed via encrypt_value_helper,
so validating at /authorize before encoding is sufficient.

Also updates existing tests to use loopback client redirect_uris and
adds regression tests for non-loopback rejection, IPv4 127.0.0.0/8
range acceptance, and full-form IPv6 loopback acceptance.
2026-04-23 03:01:18 +00:00
user
fb55e2f1b9
fix(auth): propagate BudgetExceededError through _safe_fetch
get_end_user_object raises litellm.BudgetExceededError internally
when the end-user is over budget. The previous _safe_fetch in the
centralized gate swallowed it and returned None, which caused
common_checks to see end_user_object=None and skip the budget check
entirely — silently bypassing end-user budget enforcement.

Add BudgetExceededError to the re-raise list alongside HTTPException
and ProxyException (reported by Veria AI).
2026-04-23 02:48:03 +00:00
sakenuGOD
034f4fdef2
fix(mcp_semantic_tool_filter): match tools with client-side namespace prefix (#26078) (#26117)
* fix(mcp_semantic_tool_filter): match canonical tools that arrive with
a client-side namespace prefix.

`SemanticMCPToolFilter._get_tools_by_names` matched by exact equality
between the canonical name stored in the router
(`<server><MCP_TOOL_PREFIX_SEPARATOR><tool>`) and the name in the
incoming `tools[]` list. MCP clients such as opencode wrap every tool
name with their own additive alias prefix
(`<client_alias>_<canonical>`), so the two never matched, the filter
dropped every tool to zero, and the proxy forwarded `tools: []` with
`tool_choice: auto` — which strict upstream providers reject with a 400.

The fix adds anchored suffix matching with a separator check: the
canonical must form the complete tail of the incoming name and be
preceded by `_` or `-`. Exact matches still win over suffix matches,
incoming tools are returned at most once, and the original tool object
is passed through unchanged so the client-facing name survives for
tool-call round-trips.

Seven unit tests in a new TestGetToolsByNames class cover exact
match, underscore- and dash-prefixed variants, non-separator-anchored
suffixes (which must not match), exact-wins-over-prefixed precedence,
deduplication when two canonicals suffix-match the same incoming tool,
and ordering-follows-router-output.

Fixes #26078

* review: strengthen the suffix-fallback tie-breaker and the
deduplication regression test (Greptile comments on #26117)

- test_same_tool_not_returned_twice now passes two distinct canonicals
  ("read_file" and "file") that both suffix-match the same incoming
  tool, rather than the same canonical twice, so the assertion
  actually exercises the used_ids dedup path instead of the
  duplicate-input-list path.

- The suffix fallback in _get_tools_by_names now prefers the shortest
  incoming name that still qualifies under the separator-anchored
  match. In the one-prefix-per-client opencode scenario this is a
  no-op, but in multi-namespace configurations the shortest qualifying
  name is the least-wrapped one and is the most defensible deterministic
  choice, replacing the dict-insertion-order fallback.

- Adds test_suffix_fallback_prefers_shortest_candidate covering the
  new tie-breaker directly.

Still 15 tests passing locally (was 14).

* review(#26117): gate suffix-matching on canonical containing MCP_TOOL_PREFIX_SEPARATOR

@krrish-berri-2 flagged a possible collision in the suffix fallback:
a local user function whose name happens to end in a bare canonical
substring (e.g. my_firecrawl_scrape vs canonical firecrawl_scrape)
would be spuriously selected.

Server-registered MCP tools are always emitted as
<server_name><MCP_TOOL_PREFIX_SEPARATOR><tool_name> via
add_server_prefix_to_name, so a canonical without the separator is
not a namespaced MCP tool and does not warrant suffix matching.
Added that guard to _name_matches_canonical with a regression test
(test_does_not_collide_with_local_function_on_unprefixed_canonical)
that reproduces the collision before the fix and is pinned after.

Pre-existing TestGetToolsByNames fixtures that relied on bare
canonicals (get_weather, search, read_file, write/delete/read) were
switched to realistic server-prefixed ones so they continue to
exercise the suffix-fallback path under the new guard. The opencode
scenario (client prefix on already-server-prefixed canonical) is
unchanged.

---------

Co-authored-by: sakenuGOD <sakenuGOD@users.noreply.github.com>
Co-authored-by: Krrish Dholakia <krrish+github@berri.ai>
2026-04-22 19:06:34 -07:00
ryan-crabbe-berri
c4c1861389
Merge pull request #26195 from BerriAI/litellm_team_member_total_spend
Track per-member total spend on team memberships
2026-04-22 18:20:16 -07:00
user
e0a9c193f8
fix(auth): address CI regressions from centralization
- Narrow _team_obj_from_token to require non-None team_id so mypy
  passes.
- Preserve the no-auth dev-mode contract for deployments with
  master_key unset AND no JWT/OAuth2 configured — the gate
  short-circuits only in that specific combination. JWT or OAuth2
  deployments without master_key still run the centralized authz.
- is_database_connection_error now enumerates data-layer PrismaError
  subclasses (DataError, UniqueViolationError, ForeignKeyViolationError,
  MissingRequiredValueError, RawQueryError, TableNotFoundError,
  RecordNotFoundError) as False, and maps everything else (bare
  PrismaError, connectivity subclasses, DB_CONNECTION_ERROR_TYPES) to
  True. Known-safe-to-propagate errors don't trigger HA fallback;
  unknown / generic PrismaError still falls back to preserve legacy
  503 behavior.
- Update test_handle_authentication_error_db_unavailable_connectivity
  to include PrismaError in the fallback list.
2026-04-23 00:13:17 +00:00
user
3737d6a1f3
fix(auth): centralize common_checks to close authorization bypass
Multiple paths through _user_api_key_auth_builder returned a
UserAPIKeyAuth without running common_checks(): OAuth2 token validation,
OAuth2 proxy header hook, JWT admin shortcut, master_key path,
pass-through custom headers, the /user/auth route, and the
allow_requests_on_db_unavailable fallback. An operator-configured key
model-access list, max_budget, team_blocked flag, or team model scope
was therefore silently skipped on those paths. The HA-fallback token
was worse: it was a full proxy-admin synthetic, so a DB outage granted
full admin to every caller.

Fix three root causes (VERIA-18):

1. Centralize common_checks in the user_api_key_auth wrapper. The
   builder paths no longer call it; the wrapper runs it once after the
   builder returns, for every path. Introduces _run_centralized_common_checks
   which gathers team/user/project/end_user/global_spend context in
   parallel via asyncio.gather. Preserves the existing
   custom_auth_run_common_checks opt-out for custom-auth deployments.

2. Narrow is_database_connection_error — drop the blanket PrismaError
   catch that routed data-layer errors (UniqueViolationError, etc.)
   into the HA fallback. Only real connectivity failures plus the
   no_db_connection marker now qualify.

3. DB-unavailable fallback issues an INTERNAL_USER token with user_id
   DB_UNAVAILABLE_FALLBACK_USER_ID instead of proxy-admin. An outage
   can no longer escalate an anonymous caller.

JWT admin / master_key tokens still grant admin via a synthesized
admin user_object (so non_proxy_admin_allowed_routes_check in
common_checks recognizes them); other common_checks branches
(team_blocked, team_model_access) now apply uniformly.
2026-04-23 00:04:42 +00:00
shin-berri
b6fdd46636
Merge pull request #26270 from BerriAI/litellm_/lucid-kowalevski-de832f
[Fix] Stabilize flaky spend accuracy tests + patch Redis buffer data-loss path
2026-04-22 15:02:24 -07:00
user
862bad363e
fix(mcp): validate preconditions before consuming auth code
Address two Greptile P2 findings on the BYOK OAuth endpoints:

- GET /v1/mcp/oauth/authorize now runs _validate_redirect_uri up front
  so a non-loopback redirect_uri is rejected before the HTML form is
  rendered. Previously the user typed an API key, submitted, and got a
  400 with no form state.
- POST /v1/mcp/oauth/token moves the master_key guard ahead of the
  code-consumption and credential-store steps. Without this, a proxy
  with master_key unset would burn the code and persist the credential
  but return an error — leaving the user with no way to retrieve a
  session token without restarting the whole flow.
2026-04-22 21:26:27 +00:00
Yuneng Jiang
288d403529
[Fix] Preserve in-memory spend updates when Redis rpush fails
store_in_memory_spend_updates_in_redis drained the in-memory queues
into local variables before the rpush pipeline. If rpush raised (cloud
Redis hiccup, timeout, connection blip), those already-drained
transactions were garbage-collected with the scheduler job, silently
losing all spend aggregated during that tick.

Wrap the rpush in try/except. On failure, re-enqueue the aggregated
transactions into their respective in-memory queues so the next
scheduler tick retries.

Add a unit test that seeds real queues, simulates an rpush failure,
and asserts the transactions land back in-memory.
2026-04-22 14:10:40 -07:00
user
bd1c3ea94d
fix(mcp): harden BYOK OAuth authorize/token endpoints
- POST /v1/mcp/oauth/authorize now requires an authenticated UI session
  cookie. The authenticated user_id — not the OAuth client_id form
  field — is stamped onto the authorization code record (RFC 6749 §2.2:
  client_id identifies the client, not the user).
- redirect_uri is restricted to loopback per RFC 8252 §7.3 (localhost
  plus any ipaddress.is_loopback IP, covering 127.0.0.0/8 and IPv6
  loopback forms).
- POST /v1/mcp/oauth/token enforces exact-match of the redirect_uri and
  client_id submitted at /authorize (RFC 6749 §4.1.3).
- /token error responses use the RFC 6749 §5.2 format ({"error":
  "<code>"}), and all /token responses set Cache-Control: no-store +
  Pragma: no-cache (RFC 6749 §5.1).
2026-04-22 20:44:01 +00:00
Milan
ec735074a2
fix(proxy): reapply Bedrock guardrail spend logging (#25854)
Restore guardrail spend/UI event_type wiring, request_data on streaming
OUTPUT paths, and centralized match redaction after the upstream revert.

Made-with: Cursor
2026-04-22 23:00:45 +03:00
Michael Riad Zaky
0bd49ecb8b Fix bug that bypasses per-team member budget limit
Some checks failed
Unit Tests: Proxy DB Operations / proxy-db (auth-checks, tests/proxy_unit_tests/test_auth_checks.py tests/proxy_unit_tests/test_user_api_key_auth.py, 20, 8) (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-db (key-generation, tests/proxy_unit_tests/test_key_generate_prisma.py, 30, 0) (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-db (proxy-utils, tests/proxy_unit_tests/test_proxy_utils.py, 20, 8) (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-db (remaining, tests/proxy_unit_tests --ignore=tests/proxy_unit_tests/test_key_generate_prisma.py --ignore=tests/proxy_unit_tests/test_auth_checks.py --ignore=tests/proxy_unit_tests/test_user_api_key_auth.py --ignore=tests/proxy_unit_tests/test_p… (push) Has been cancelled
Unit Tests: Security / security (push) Has been cancelled
2026-04-22 10:41:13 -07:00
Sameer Kankute
b9675109cb
fix(proxy): preserve anthropic_messages call type for /v1/messages logging
Keep anthropic_messages as the logged call_type for non-Anthropic /v1/messages adapter paths and add a regression test to prevent fallback to completion/acompletion.

Made-with: Cursor
2026-04-22 18:32:33 +05:30
Yuneng Jiang
fbcdacc446
[Fix] Proxy: reconnect Prisma DB without blocking the event loop
When the DB becomes unreachable the reconnect path calls
`prisma.disconnect()`, which ultimately invokes prisma-client-py's
synchronous `subprocess.Popen.wait()` on the query engine subprocess.
That call does not yield to asyncio, so the event loop freezes for
however long the Rust engine takes to shut down (30-120+ seconds in
production when the engine is stuck on TCP close). During the freeze
`/health/liveliness` becomes unresponsive, and in Kubernetes the
liveness probe fails and the pod is SIGKILL'd.

Replace `disconnect()` in the reconnect paths with a direct, non-blocking
kill of the engine subprocess (SIGTERM -> 0.5s asyncio-yielding sleep ->
SIGKILL) followed by a fresh Prisma client and a new `connect()`. Both
`recreate_prisma_client` and the formerly-separate "direct reconnect"
path go through the same kill-then-recreate flow.

Also validate `_get_engine_pid` returns an int (defensive; prevents a
MagicMock leak under unit-test mocking).

Tests that encoded the old blocking behavior are updated or removed;
the deleted `test_lightweight_reconnect_skips_kill_on_successful_disconnect`
invariant ("don't kill on successful disconnect") was part of the bug.
2026-04-21 23:29:17 -07:00
yuneng-jiang
eebb80fbef
Merge pull request #26208 from BerriAI/litellm_individual-team-member-budgets
Some checks are pending
Unit Tests: Proxy DB Operations / proxy-db (auth-checks, tests/proxy_unit_tests/test_auth_checks.py tests/proxy_unit_tests/test_user_api_key_auth.py, 20, 8) (push) Waiting to run
Unit Tests: Proxy DB Operations / proxy-db (key-generation, tests/proxy_unit_tests/test_key_generate_prisma.py, 30, 0) (push) Waiting to run
Unit Tests: Proxy DB Operations / proxy-db (proxy-utils, tests/proxy_unit_tests/test_proxy_utils.py, 20, 8) (push) Waiting to run
Unit Tests: Proxy DB Operations / proxy-db (remaining, tests/proxy_unit_tests --ignore=tests/proxy_unit_tests/test_key_generate_prisma.py --ignore=tests/proxy_unit_tests/test_auth_checks.py --ignore=tests/proxy_unit_tests/test_user_api_key_auth.py --ignore=tests/proxy_unit_tests/test_p… (push) Waiting to run
Unit Tests: Security / security (push) Waiting to run
Litellm individual team member budgets
2026-04-21 18:38:00 -07:00
shivam
5770af0068
Merge branch 'litellm_internal_staging' into litellm_individual-team-member-budgets 2026-04-21 17:59:29 -07:00
shivam
27a105bcf9
fix: give each team member an independent budget instead of sharing the team default
Previously, members added to a team without an explicit per-member budget were
all linked to the same `litellm_budgettable` row referenced by the team's
`metadata.team_member_budget_id`. Updating one member's budget via
`/team/member_update` mutated the shared row and silently changed every other
member's budget too.

Now both write paths produce a private, per-member budget:

- `add_new_member` clones the team's default budget into a fresh row when a
  member is added without `max_budget_in_team`/`allowed_models`. If no team
  default exists, the membership is created with no budget.
- `_upsert_budget_and_membership` detects when an existing membership still
  points at the team's default budget id and clones-on-write, relinking the
  membership to the new private budget before applying the update.
- `team_member_update` reads `team_member_budget_id` from team metadata and
  passes it through so the helper can make this distinction.

Adds unit tests for clone-on-write, in-place update of a private budget, and
the no-default-no-budget add path.

Made-with: Cursor
2026-04-21 17:58:50 -07:00
yuneng-jiang
d9494b6990
Merge pull request #26144 from BerriAI/litellm_post_call_non_streaming
fix(bedrock_guardrails): use Bedrock OUTPUT source for apply_guardrail when scanning model responses
2026-04-21 17:50:16 -07:00
yuneng-jiang
5dc2926a1e
Merge pull request #26194 from BerriAI/litellm_fix_migration_thrashing
[Feature] Proxy: opt-in v2 migration resolver
2026-04-21 16:55:54 -07:00
harish-berri
984287daaa Implement CacheCodec for DualCache serialization and deserialization. Add attach_redis_cache method to DualCache for lazy Redis integration. Update RedisCache to handle None keys and improve logging. Enhance user_api_key_auth caching logic and introduce tests for CacheCodec functionality. 2026-04-21 22:53:54 +00:00
Yuneng Jiang
88b1823f51
[Test] Fix setup_database call-signature assertions for v2 flag
Existing tests pinned exact kwargs on `PrismaManager.setup_database`,
but the opt-in v2 resolver added `use_v2_resolver=False` to every call.
Update the three assertions to reflect the new signature.

Fixes:
- TestHealthAppFactory::test_use_prisma_db_push_flag_behavior
- TestHealthAppFactory::test_startup_fails_when_db_setup_fails
2026-04-21 14:45:29 -07:00
Ryan Crabbe
e5f3e15969
Track per-member total spend on team memberships
Adds total_spend column to LiteLLM_TeamMembership that accumulates
continuously and is not zeroed by the budget cycle reset job. This
enables UI surfaces to distinguish current-cycle spend (the existing
spend column, which resets) from lifetime spend per team member.

Also exposes budget_reset_at on LiteLLM_BudgetTable so /team/info
callers can see when a member's budget window next resets. The field
was already stored in the DB but stripped by the response Pydantic
model.

Includes regression tests that:
- Guard the reset job against ever writing total_spend: 0
- Verify the spend writer increments both spend and total_spend in
  one UPDATE statement.
2026-04-21 13:56:44 -07:00
shivam
62c2c553d7
Merge remote-tracking branch 'upstream/litellm_internal_staging' into litellm_post_call_non_streaming 2026-04-21 11:45:26 -07:00
shivam
6beba97d20
test(bedrock_guardrails): assert apply_guardrail maps response to OUTPUT source
Add regression tests that mock make_bedrock_api_request and verify
input_type=request uses source=INPUT with user messages, and
input_type=response uses source=OUTPUT with synthetic ModelResponse.

Made-with: Cursor
2026-04-20 19:53:49 -07:00
Yuneng Jiang
9deefc0f76
fix: align MCP broker endpoint access controls with existing auth patterns 2026-04-20 16:52:59 -07:00
ishaan-berri
9aee0da7d8
fix: /health/readiness 503 loop when DB is unreachable (#26134)
* fix: /health/readiness returns 503 when DB is unreachable due to handle_db_exception re-raising

handle_db_exception() re-raises the Prisma exception inside _db_health_readiness_check's
except block, which propagates out to health_readiness() and gets wrapped in a 503.
The health endpoint never reached the reconnect path and the service never recovered.

Fix:
- Remove handle_db_exception() call from _db_health_readiness_check — that helper is
  for API request handlers (allow_requests_on_db_unavailable flag), not health checks
- Replace raw disconnect()+connect() with attempt_db_reconnect(), which uses the proper
  lock, cooldown, escalation, and heavy-reconnect (recreate_prisma_client) machinery

* test: update health readiness tests for handle_db_exception removal

- Remove tests that expected handle_db_exception to re-raise (old buggy behaviour)
- Remove tests asserting disconnect()/connect() calls (replaced by attempt_db_reconnect)
- Add regression tests covering the 503 loop fix:
  - transport errors never raise (ClientNotConnectedError, httpx.ConnectError, etc.)
  - reconnect success path returns 'connected'
  - reconnect failure path returns 'disconnected' without raising
  - non-transport errors return 'disconnected', skip reconnect

---------

Co-authored-by: yuneng-jiang <yuneng@berri.ai>
2026-04-20 15:29:43 -07:00
Krrish Dholakia
386f334fee
Prompt Compression - add it to the proxy (#25729)
* refactor: new agentic loop event hook

simplifies how to create logic for tool based multi llm calls

* fix: compress - make it work on anthropic input as well

* fix(compress.py): working prompt compression for claude code

ensures claude code messages can run through proxy easily

* docs: add agentic loop hook guide

* docs: add agentic_loop_hook to sidebar

* fix: fix multiple arguments error

* fix: fix tool call loop for compression on streaming /v1/messages

* fix: fix linting errors

* fix: fix ci/cd errors

* feat(litellm_pre_call_utils.py): use claude code session for litellm session id

allows claude code logs to be stitched together, making it easy to know they were all part of the same conversation

* fix: suppress incorrect mypy warning rE: module

* revert: drop PR's changes to litellm/proxy/_experimental/out/

Restores the 34 HTML files under _experimental/out/ to their pre-PR
paths (X/index.html -> X.html). All renames are R100 (content
unchanged); no other files are touched.

* fix: address greptile review comments on PR #25729

- Skip ``kwargs["tools"] = []`` injection when compression is a no-op —
  Anthropic Messages rejects empty tool arrays on requests that did not
  originally declare tools.
- Move agentic-loop safety guards (fingerprint cycle / max depth) out of
  the per-callback try/except so they propagate instead of being swallowed
  by the generic exception handler. Extracted _check_agentic_loop_safety.
- Gate generic ``x-<vendor>-session-id`` capture behind the
  LITELLM_CAPTURE_VENDOR_SESSION_HEADERS env var (off by default) to
  preserve backwards compatibility; explicit x-litellm-* headers are
  unaffected.
- Fix monkeypatch target in pre-call-hook test to patch the actual
  module-level binding
  (litellm.integrations.compression_interception.handler.compress).
- Add regression tests for empty-tools skip and opt-in session capture.

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

* revert: drop LITELLM_CAPTURE_VENDOR_SESSION_HEADERS flag

Generic x-<vendor>-session-id header capture is a new feature and only
runs *after* the explicit x-litellm-trace-id / x-litellm-session-id
checks, so it does not change behavior for any existing caller that was
already using the LiteLLM headers — no backwards-incompatibility to gate.

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

* refactor(compress): replace input_type with CallTypes call_type

Drop the bespoke ``CompressionInputType`` literal and use the existing
``litellm.types.utils.CallTypes`` enum instead.  ``litellm.compress()``
now takes ``call_type: Union[CallTypes, str]`` (default
``CallTypes.completion``) — no new concept to learn, and the enum is
already the way the rest of the codebase talks about request shapes.

Supported values: ``completion`` / ``acompletion`` (OpenAI chat-completions
shape) and ``anthropic_messages`` (Anthropic structured content blocks).

Updated: compress(), the compression_interception handler, tests, docs,
and the two eval scripts.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 15:08:00 -07:00
mubashir1osmani
499f5d6d6b
prevent post call guardrail called twice 2026-04-20 09:13:22 -04:00
Ryan Crabbe
c8b7c1bafa
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_feat-multi_threshold_budget_alerts 2026-04-18 15:26:02 -07:00
Ryan Crabbe
eb6fd98611
Merge remote-tracking branch 'origin/main' into litellm_feat-multi_threshold_budget_alerts 2026-04-18 14:54:26 -07:00
ishaan-berri
ecff06df65
Merge pull request #26032 from BerriAI/litellm_mcp_pkce_fix_v2
fix(mcp): restore PKCE-triggering 401 when no stored per-user token exists
2026-04-18 14:52:31 -07:00
yuneng-jiang
e69051916e
Merge pull request #25983 from BerriAI/litellm_yj_apr17
[Infra] Merge dev branch
2026-04-18 14:43:04 -07:00
Ishaan Jaffer
b7813aad41
fix(mcp): restore PKCE-triggering 401 when no stored per-user token exists
Per-user OAuth MCP requests now only skip pre-emptive 401 when a stored token is available, preserving token-reuse behavior while restoring fast PKCE kickoff for first-time or missing-token users.
2026-04-18 14:04:22 -07:00
shivam
dbf4f9637f
Merge remote-tracking branch 'upstream/litellm_internal_staging' into litellm_project_rate_limiting 2026-04-18 13:26:24 -07:00
Yuneng Jiang
f483f1e800
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_yj_apr17 2026-04-18 13:19:16 -07:00
Ryan Crabbe
208d583a33
[Fix] handle metadata=null on service-account keys
Addresses Greptile P1: `metadata: null` on a key with a reserved field
was crashing inside prepare_metadata_fields because cast(dict, None) is
a runtime no-op, so the loop hit `reserved_field in None` and returned
500 instead of 400.

Extend the rejection condition to cover `casted_metadata is None`, so
attempts to clear an immutable field via `metadata: null` return 400
consistently with the overwrite path. Non-service-account keys still
fall through to the existing "clear all metadata" behavior.
2026-04-18 11:52:54 -07:00
Shivam Rawat
f870095f3f
Merge pull request #25991 from BerriAI/litellm_persist_default_router_end_budget
Litellm persist default router end budget.
2026-04-18 11:52:20 -07:00
ishaan-berri
d03c301c79
Merge pull request #25936 from BerriAI/litellm_health-check-reasoning-tokens
fix(proxy): prioritize reasoning health-check max token precedence
2026-04-18 11:35:04 -07:00
Ryan Crabbe
01acbb8d3d
[Fix] reject explicit null when clearing reserved metadata field
Addresses Greptile review feedback:
- Clarify LiteLLM_Reserved_Metadata_Fields comment to describe both
  preserve-on-omit and reject-on-change behaviors.
- Treat explicit null as a change attempt so callers trying to clear
  service_account_id get a 400 instead of a silent no-op.
2026-04-18 11:06:22 -07:00
Yuneng Jiang
9c0b73e5f4
[Fix] should_create_missing_views returns False for reltuples=0 (falsy zero bug)
`should_create_missing_views()` had `and result[0]["reltuples"]` which is
falsy when reltuples=0. On a fresh empty PostgreSQL table, CREATE INDEX sets
reltuples=0, causing the guard to return False and skip view creation entirely.
Views like MonthlyGlobalSpendPerKey are never created, and the
/global/spend/logs endpoint returns 500.

Fix: change to `and result[0]["reltuples"] is not None` so reltuples=0
(empty table) and reltuples=-1 (unanalyzed table) both correctly return True.

Also harden test_vertex_ai.py to return None instead of crashing with
JSONDecodeError when the spend-logs endpoint returns a non-JSON 500 response,
and add unit tests covering all three reltuples branches (0, -1, positive).
2026-04-18 11:00:09 -07:00
Ryan Crabbe
48fb19b4fd
fix: align test assertion with additive merge semantics
test_virtual_key_max_budget_alert_check_per_key_overrides_global asserted
override semantics but the implementation does additive merge. Renamed test
and updated assertion to match: per-key and global thresholds are unioned,
not replaced.
2026-04-17 23:46:40 -07:00
Ryan Crabbe
80d48a41e4
[Fix] preserve service_account_id in metadata on /key/update
/key/update and /user/update wholesale-replaced the metadata JSON column
whenever a caller passed a `metadata` field, silently dropping
service_account_id. The pre-call check in litellm_pre_call_utils.py then
stopped treating the key as a service account and bypassed
service_account_settings.enforced_params.

Add LiteLLM_Reserved_Metadata_Fields and have prepare_metadata_fields
preserve these keys from the existing row when the caller omits them.
Reject attempts to change an already-set value (400) since rebinding
service_account_id would break spend attribution.
2026-04-17 23:21:20 -07:00
Yuneng Jiang
e004876950
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/wonderful-bouman
# Conflicts:
#	tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py
2026-04-17 21:32:09 -07:00