Commit graph

11158 commits

Author SHA1 Message Date
tin-berri
7419a536ad
fix(auto-router): omit Claude Code system text from classifier (#40655)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-09-10 19:18:25 -07:00
devin-ai-integration[bot]
880ccc76a5
fix(streaming): keep admitted mock streams alive with empty stream_options and honor zero prompt counts (#40650)
* fix(streaming): keep usage-only chunks from crashing streams with empty stream_options

The usage-only chunk branch in CustomStreamWrapper.chunk_creator indexed stream_options["include_usage"] directly, so a caller passing stream_options={} hit a KeyError that surfaced as MidStreamFallbackError. Streaming mock_response with an admission input_tokens count (#40637) now always emits such a chunk, which made the crash reachable. Reuse the send_stream_usage policy computed at init instead. Also annotate the #40637 test bindings with Final.

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

* fix(streaming): report admitted zero prompt tokens instead of recounting in mock streams

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

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-10 18:37:39 -07:00
Mateo Wang
4fbe2276a1
fix(logging): finish response metadata before the sync logging thread reads it (#39869)
* fix(logging): finish response metadata before the sync logging thread reads it

The async and sync client wrappers handed the response to the threaded success handler before computing its cost, call id, and api_base, so that thread inserted into the same metadata dict the request coroutine was still iterating and a finished chat completion turned into a 500 (dictionary changed size during iteration). Metadata is now finalized first, and the merge and header copies snapshot their dicts before iterating.

* fix(logging): snapshot metadata with a dict copy and drop redundant comment

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

* fix(logging): copy metadata via dict.copy and dedupe Final import

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

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-10 18:15:24 -07:00
devin-ai-integration[bot]
985ac6b6a5
fix(rate_limiter): skip non-Latin-1 x-litellm-priority header on /v1/messages (#40636)
A team or key priority that is not Latin-1 encodable (for example CJK text) was
attached as a response header by the dynamic rate limiter v3 post-call hook, and
Starlette then raised UnicodeEncodeError while writing headers, turning a
successful /v1/messages call into HTTP 500. The header is now omitted for such
values while x-litellm-rate-limiter-version and the v3 rate limit headers are
still attached.

Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-10 17:48:46 -07:00
devin-ai-integration[bot]
c7a41c35d5
perf(mock): emit admission-time usage chunk on streaming mock_response (#40637)
* perf(mock): emit admission-time usage chunk on streaming mock_response

Streaming mock_response chunks carried no usage, so the chunk builder re-tokenized the whole prompt in Python after the stream ended even when budget reservation had already counted it at admission. The mock streaming generators now yield a final usage-only chunk carrying the admission prompt count (same completion count as the non-streaming path). Without an admission count the old tokenizer fallback stays.

* fix(mock): type the mock stream generators and keep the usage chunk on the content stream id

Review follow-up: the usage-only chunk was built with a fresh id, so CustomStreamWrapper switched response_id for the finish-reason and usage chunks. It now copies the content stream id. The generators also get full parameter and return annotations.

---------

Co-authored-by: yassin <yassin@berri.ai>
2026-09-11 00:48:25 +00:00
ryan-crabbe-berri
8a4fae0e17
Merge pull request #40639 from BerriAI/litellm_enduser_budget_reset_bind_limit
fix(reset_budget_job): reset end users by budget link, not by user id
2026-09-10 17:31:25 -07:00
devin-ai-integration[bot]
960fc4b114
feat(newrelic): export team max and remaining budget gauges to the Metric API (#40542)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-10 17:16:15 -07:00
devin-ai-integration[bot]
ae01882535
feat(proxy): offload spend tracking to a pod-local collector sidecar (#40545)
* feat(proxy): offload spend tracking to a pod-local spend worker sidecar

py-spy on the gateway showed the post-response _PROXY_track_cost_callback,
spend-log and DBSpendUpdateWriter work running on the inference workers'
event loop, so a DB or Redis stall backed up the request path.

When LITELLM_SPEND_WORKER_ENABLED=true, _ProxyDBLogger serializes one compact
typed SpendEvent per success and hands it to a SpendEventProducer that ships
it over a unix socket (default) or loopback-only TCP to a sidecar started as
`python -m gateway.spend_worker`. The sidecar runs the unchanged
_ProxyDBLogger pipeline against the pod's PgBouncer (pooled_database_url).
When the sidecar is unreachable, the buffer is full, or the gateway shuts
down with events still queued or in flight, the producer applies
LITELLM_SPEND_WORKER_ON_UNAVAILABLE (fallback in-process, or drop). The
sidecar half-closes producers on SIGTERM and drains, the producer treats
EOF as unavailable, and the gateway flushes buffered spend counters on
shutdown. The sidecar honors LITELLM_LOG so its writes are visible in its
own process log.

Helm: both charts gain an opt-in spend-worker sidecar container sharing an
emptyDir socket dir, and the componentized chart's HPA uses a
ContainerResource CPU metric scoped to the gateway container so sidecar
CPU does not drive inference scaling.

* feat(terraform): opt-in spend-worker sidecar for the AWS and GCP gateway stacks

Adds spend_worker_* inputs to both modules. On ECS Fargate the sidecar is a second, non-essential container in the gateway task; on Cloud Run it is a second container in the gateway service. Both listen on loopback TCP, share the gateway's DB/Redis/secret env, and set LITELLM_JOB_ROLE=spend_worker. Disabled by default. Plan-only tests cover both, and the terraform CI workflow now runs the gcp module too

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

* test(proxy): retrieve a completed batch in the in-process spend path test

The base now defers cost tracking for batches that are still in flight, so an in_progress batch never reaches update_database

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

* refactor(proxy): rename the spend worker sidecar to collector

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

* fix(proxy): run the collector from the installed litellm package and finish in-flight fallbacks on shutdown

The sidecar command becomes python -m litellm.proxy.collector so the classic image, whose runtime
stage copies only the installed package, can run it. The module now assembles DATABASE_URL and the
pod-local pgbouncer URL itself, replacing gateway/collector.py

The componentized collector sidecar inherits gateway.volumeMounts so custom CA mounts reach it.
SpendEventProducer shields an in-progress fallback from the writer task cancellation so close()
no longer loses an event already handed to the in-process pipeline

Helpers used across modules (address_argument, should_store_prompts_and_responses_in_spend_logs,
flush_spend_counters_on_shutdown) become public so the change adds no reportPrivateUsage errors

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

* ci(terraform): drop the gcp job duplicated by the aws/gcp matrix

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

* fix(collector): keep metrics env off the classic sidecar and reject shared loopback ports

The classic chart no longer hands PROMETHEUS_METRICS_PORT and the billing metrics env to the collector container, and gives it the same /.npm scratch mount as the proxy on a read-only root. AWS and GCP now refuse a plan where the spend collector and the metrics sidecar bind the same loopback port. A regression test drives a sidecar crash mid-stream on asyncio and uvloop and checks no event is billed by both the sidecar and the in-process fallback; the producer docstring spells out why a failed drain() cannot double count

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

* style(proxy): format pooled_database_url after the pgbouncer rebase

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

* fix(proxy): keep the cache-hit preset key and survive dead producers on collector drain

Cache hits updated the logging object after the early return, so the offloaded spend event carried
preset_cache_key=None and the collector re-hashed reconstructed kwargs. Also guard write_eof() against
producer transports uvloop already closed so one dead connection cannot abort the drain

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

* fix(terraform): keep the gcp collector port off the metrics sidecar health port

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

* fix(proxy): collector connects to Postgres directly under IAM or Entra token auth

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

* fix(proxy): mark the collector's DATABASE_URL as pooled when it uses the pod's pgbouncer

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

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-10 17:14:13 -07:00
ryan-crabbe-berri
760043b533 fix(reset_budget_job): reset end users by budget link, not by user id
The cascade zeroed end-user spend with a single update_many whose where
clause enumerated every dependent user id. Prisma compiles that IN-list
into one prepared statement carrying one bind variable per customer, and
PostgreSQL caps a statement at 32,767 of them. Once a shared budget had
more dependents than that the statement could not be parsed at all, so
the atomic cascade rolled back, budget_reset_at never advanced, and the
tier stayed due on every later tick forever. Customers sitting at their
cap were blocked indefinitely with only a recurring log line to show for
it.

End users now match on budget_id like every other gated table, plus a
NULL-budget_id branch for the implicitly created rows that carry no link
and ride the default tier. The statement's bind count now tracks the
number of expiring tiers rather than the customer population, so a reset
costs the same whether a budget has ten dependents or a million.

Fixes #40564

Claude-Session: https://claude.ai/code/session_01Hn5E8Jz1LjGLFyiYxBRcBW
2026-09-10 16:57:53 -07:00
Mateo Wang
ac66754689
Merge pull request #40613 from BerriAI/litellm_gate_organizations_on_enterprise_license
feat(proxy): gate organization endpoints on an enterprise license
2026-09-10 16:54:49 -07:00
ryan-crabbe-berri
7a1178a859 fix(utils): stop model registration from shadowing capability rules
Router writes every configured deployment into litellm.model_cost, and a
deployment that declares no model_info lands there as an empty entry. An exact
entry ends the model-info lookup ladder before the fallback generalizations are
consulted, so that empty entry made the rules inert for the model: configuring
one on a proxy stripped the capabilities the same model resolves to off-proxy.

Seed a new registration from the capability rules its key matches. The caller's
own model_info still wins field by field, so an explicit supports_reasoning:
false on the deployment keeps overriding the rule.

Claude-Session: https://claude.ai/code/session_01A6SkwJdfZUmkzfUkrEkqX8
2026-09-10 16:40:07 -07:00
ryan-crabbe-berri
033f2e5e2a feat(wandb): default unmapped W&B models to reasoning-capable
W&B's serverless catalog grows faster than the registry names it, so a model
they ship today resolves as non-reasoning here until someone edits the cost map,
and the caller's reasoning_effort is dropped or rejected.

Add a wandb-reasoning-baseline capability rule to fallback_generalizations so any
wandb/ id the map has not described defaults to supports_reasoning. Rules lose to
exact entries, so mapped non-reasoning models such as
wandb/meta-llama/Llama-3.1-8B-Instruct are unaffected.

The rule carries no mode and no pricing, so cost stays on the standard unpriced
behavior and the deployment does not read as catalog-mapped to the router's
reasoning-effort resolver.

Claude-Session: https://claude.ai/code/session_01A6SkwJdfZUmkzfUkrEkqX8
2026-09-10 16:40:07 -07:00
Mateo Wang
dce2991085
Merge pull request #40622 from BerriAI/litellm_sanitize_unknown_model_spend_rows
fix(proxy): log rejected unknown-model requests under a placeholder model name
2026-09-10 16:21:10 -07:00
devin-ai-integration[bot]
3b93da414e
feat(proxy): let the in-container pgbouncer follow rotating RDS IAM and Azure Entra tokens (#40623)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-10 16:18:00 -07:00
Mateo Wang
ef1a37795c
Merge pull request #40620 from BerriAI/litellm_redis_breaker_quiet_open
fix(redis): log an open circuit breaker once instead of a traceback per request and count sync timeouts as timeouts
2026-09-10 15:52:09 -07:00
moe-berri
a36b912b45
Merge pull request #40604 from BerriAI/litellm_lit7490_classifier_audit
feat(router): log exact classifier input and masked source request
2026-09-10 15:48:05 -07:00
mateo-berri
3a0dce6bc4 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_sanitize_unknown_model_spend_rows 2026-09-10 15:47:55 -07:00
mateo-berri
a93753e73b fix(proxy): keep the model on failed rows that resolved to a configured model group 2026-09-10 15:43:27 -07:00
mateo-berri
098bfe0bbc fix(proxy): only remap prompt-shaped model strings on failed spend rows 2026-09-10 15:36:32 -07:00
mateo-berri
368de5df68 fix(proxy): log prompt-shaped model strings on pass-through failures under the placeholder model 2026-09-10 15:30:16 -07:00
ryan-crabbe-berri
8c6e085796
Merge pull request #35448 from krth1k/litellm_internal_user_spend_log_detail_route
Some checks failed
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Waiting to run
Unit Tests: Proxy DB Operations / auth-checks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / budgets (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / custom-logging (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / db-and-spend (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / key-generation (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / logging-misc (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-runtime (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-server-core (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-utils (push) Blocked by required conditions
Unit Tests / caching-local (push) Waiting to run
Unit Tests / core-utils (push) Waiting to run
Unit Tests / enterprise-package (push) Waiting to run
Unit Tests / enterprise-routing (push) Waiting to run
Unit Tests / integrations (push) Waiting to run
Unit Tests / All Other Providers (push) Waiting to run
Unit Tests / Vertex AI (push) Waiting to run
Unit Tests / misc (push) Waiting to run
Unit Tests / proxy-auth (push) Waiting to run
Unit Tests / proxy-endpoints (push) Waiting to run
Unit Tests / proxy-extras (push) Waiting to run
Unit Tests / proxy-infra (push) Waiting to run
Unit Tests / proxy-server (push) Waiting to run
Unit Tests / responses-caching-types (push) Waiting to run
GitHub Actions Security Analysis / zizmor (push) Waiting to run
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
fix(proxy): let internal users read request/response for their own spend logs
2026-09-10 15:24:14 -07:00
mateo-berri
07e8a9ba84 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_redis_breaker_quiet_open 2026-09-10 15:21:19 -07:00
devin-ai-integration[bot]
d98522b6f6
feat(proxy): share database connections across workers with an in-container pgbouncer (#39683)
* feat(proxy): share database connections across workers with an in-container pgbouncer

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

* fix(proxy): parse pgbouncer options iteratively to satisfy the recursion gate

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

* fix(proxy): refuse pgbouncer with token db auth and retry failed pooler restarts

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

* fix(proxy): build pgbouncer 1.25.2 from a pinned source archive and verify pooler replacements

The public Wolfi repository only carries pgbouncer 1.24.1-r3, which the image
scan rejects (CVE-2026-6664, CVE-2026-6665, CVE-2026-6666, CVE-2025-12819).
All three images now compile the checksummed 1.25.2 release in a builder stage.

The supervisor now waits for a replacement pooler to listen before treating it
as recovered, ends and retries one that never does, and takes the same lock for
stop() and spawn so no replacement can be started after shutdown began.

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

* fix(proxy): refuse to start pgbouncer on a loopback port another process already owns

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

* fix(proxy): count pgbouncer ready only once its own unix socket answers, not any listener on the port

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

* fix(proxy): refuse pgbouncer older than 1.19, whose unix socket cannot vouch for the tcp port

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

* feat(helm,terraform): expose the in-container pgbouncer pool for the componentized gateway

Add database.connectionPool to helm/litellm and gateway_connection_pool_* to
terraform/litellm/aws so the componentized gateway can receive the
LITELLM_PGBOUNCER_* env the classic image already honours. Both reject the
pool under IAM or Entra token auth at render/plan time: the pooler holds one
static database password for the life of the pod or task.

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

* feat(gateway): launch the componentized gateway image through a pgbouncer-aware supervisor (#40592)

The componentized gateway image started uvicorn directly, so the in-container
PgBouncer never ran for it: every worker opened its own Prisma pool to the
database. It also passed no keep-alive timeout, so behind a load balancer with
a 60s idle timeout uvicorn's 5s default closed idle connections first and the
balancer returned 502s on scale-out

gateway.launch assembles DATABASE_URL, starts PgBouncer once per pod when
LITELLM_PGBOUNCER_ENABLED is set, hands the workers the loopback URL and then
runs uvicorn on gateway.main:app with KEEPALIVE_TIMEOUT as --timeout-keep-alive.
The image builds PgBouncer 1.25.2 from a checksummed tarball, copies the
compiled Rust extension into the /app source tree it imports from (it was only
in site-packages, which PYTHONPATH=/app shadows) and asserts the native bridge
loads. The app user is added to stats_users so operators can read the PgBouncer
console with the application credentials

The supervisor returns the pooled URL instead of writing into the mapping it
was handed, a database user whose name PgBouncer would split into several
stats_users entries is refused before the config is written, and the launcher
tests drive main() with an injected serve callable

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

* docs(terraform): describe the gateway.launch pooler entrypoint in the aws module README

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

* fix(proxy): run pgbouncer exit hooks only in the parent and copy the CA into the runtime dir

Gunicorn workers inherit the parent's atexit table, so a recycled worker (max_requests) stopped the shared pooler and removed its runtime dir, then hung in the inherited Popen lock. The hooks now no-op unless os.getpid() is the process that started PgBouncer

A verified TLS upstream named the operator's CA bundle directly, which is often a 0600 root-owned file that nobody (the user PgBouncer drops to) cannot read, so every server connection failed with "failed to load CA". The bundle is copied into the runtime dir next to the ini and chowned with it

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

* feat(terraform): run the gateway through gateway.launch and add the gcp connection-pool variables

Cloud Run and ECS overrode the image command with uvicorn gateway.main:app, which skips the supervisor that starts the in-container PgBouncer, so LITELLM_PGBOUNCER_ENABLED was inert on both stacks. Both now exec python -m gateway.launch (under ddtrace-run when USE_DDTRACE is set), and the gcp module gains gateway_connection_pool_enabled / gateway_pool_max_db_connections / gateway_pool_max_client_conn wired to the gateway service only

The test_launch password_env fixture now restores DATABASE_URL even when it was unset: monkeypatch.delenv records nothing for an absent var, so main() left postgresql://...@db.internal in the xdist worker's environ and the key-rotation e2e test in the same proxy-infra shard stopped skipping and tried to reach db.internal

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

* fix(pgbouncer): keep channel_binding and gssencmode off the loopback URL

Prisma would demand TLS channel binding from a pooler that only speaks
plain TCP on 127.0.0.1. Also pass the request the marketplace test
started needing after #40518 landed on top of #40496

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

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-10 22:14:37 +00:00
ryan-crabbe-berri
9bdaa54643 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_internal_user_spend_log_detail_route 2026-09-10 15:13:59 -07:00
Mateo Wang
4866be56b0
Merge pull request #40626 from BerriAI/litellm_fix_marketplace_test_request
test(proxy): pass the request to get_marketplace in the archive marketplace test
2026-09-10 15:13:06 -07:00
Kishorekarthik P
6632e8b74f fix(proxy): let internal users read request/response for their own spend logs
The Logs drawer gets messages/response from GET /spend/logs/ui/{request_id};
the list endpoint omits those heavy columns for every caller, admins included.
That detail route was missing from LiteLLMRoutes.spend_tracking_routes, and
check_route_access anchors patterns, so /spend/logs/ui never matched it. Every
internal_user got a 403 before the handler ran and the UI fell back to the
"Request/Response Data Not Available" banner, even on their own requests

Adds the route to spend_tracking_routes so internal_user, internal_user_view_only,
admin_viewer and org_admin all inherit it, and drops the now-redundant explicit
entry from admin_viewer_routes. The handler already authorizes non-admins per row
via _assert_user_can_view_request_id, so no handler-side scoping change is needed

That helper returned silently when no spend-log row existed, which the detail
handler treats as authorized before asking every custom logger for the payload by
raw request_id. With retention pruning the row can be gone while the payload is
still in cold storage, so opening the route would have let a non-admin read
another tenant's prompt out of S3/GCS. A missing row now falls through to the
same 403 as a foreign row, which also removes the exists-but-not-yours oracle

Fixes #34099
2026-09-10 15:04:15 -07:00
ryan-crabbe-berri
7262887ca6
Merge pull request #38413 from eugene-yao-zocdoc/litellm_redis_elasticache_iam_auth
feat(redis): add ElastiCache IAM authentication
2026-09-10 15:03:27 -07:00
Mateo Wang
6b264815ac
Merge pull request #39296 from BerriAI/litellm_fix_v1_models_alias_resolution
fix(proxy): resolve /v1/models limits from the deployment, not the alias
2026-09-10 14:57:19 -07:00
mateo-berri
1987e6e290 test(proxy): pass the request to get_marketplace in the archive marketplace test 2026-09-10 14:54:46 -07:00
moe-berri
989140d065 test(proxy): fix marketplace request fixture after staging merge 2026-09-10 14:48:56 -07:00
ryan-crabbe-berri
33fa195949
Merge pull request #39539 from BerriAI/litellm_fix_health_check_db_storm
fix(proxy): dedup latest health checks in SQL and gate the DB save per window
2026-09-10 14:45:59 -07:00
joshua-berri
4d067b56af
Merge pull request #38724 from BerriAI/litellm_mcp_oauth_identity_binding
fix(mcp): bind per-user OAuth credentials to the authenticated LiteLLM caller
2026-09-10 14:44:11 -07:00
mateo-berri
29145d1396 test(limiter): inject the open-breaker Redis double instead of replacing script attributes 2026-09-10 14:37:39 -07:00
mateo-berri
3e356366ce Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_sanitize_unknown_model_spend_rows 2026-09-10 14:36:07 -07:00
mateo-berri
c7d3a6a1d4 refactor(redis): drop the narrative docstrings on the breaker helper and its tests 2026-09-10 14:31:48 -07:00
Mateo Wang
56b51db451
Merge pull request #35091 from fzowl/feat/voyage-context-4
fix(voyage): accept flat list[str] input for contextual embeddings
2026-09-10 14:29:23 -07:00
mateo-berri
d62493a779 test(batch): prove an open Redis breaker keeps enqueued-token reservations quiet 2026-09-10 14:26:11 -07:00
devin-ai-integration[bot]
6cc13e07a6
feat(proxy): granular key/team access control for Claude Code marketplace plugins (#40518)
Adds object_permission.skills to keys and teams, enforces it on
/claude-code/marketplace.json?key=, /claude-code/plugins and
/claude-code/plugins/{name}, and exposes an Allowed Skills selector in
the key and team create/edit forms of the Admin UI

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-10 14:26:09 -07:00
devin-ai-integration[bot]
03815cf9f6
feat(claude-code): accept https zip archive plugin sources for skills (#40496)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-10 14:25:42 -07:00
devin-ai-integration[bot]
c59fc6dc28
fix(mcp): reject initialize with 403 when the key grants no MCP servers (#40616)
* fix(mcp): reject initialize with 403 when the key grants no MCP servers

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

* test(mcp): e2e expects 403 initialize for a key with no MCP servers

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

* fix(mcp): mention IP filtering in the no-servers initialize denial and keep zero-grant tool coverage

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

---------

Co-authored-by: mateo <mateo@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-10 14:25:30 -07:00
moe-berri
73013124b9 fix(router): merge staging and retain native classifier audits 2026-09-10 14:24:56 -07:00
mateo-berri
fe6f615c7a fix(proxy): log rejected unknown-model requests under a placeholder model name
A request whose model field matched no configured model was rejected with 400 but its failure row still persisted the raw client string as the model, so a client that concatenated its prompt into the model field wrote that prompt into LiteLLM_SpendLogs and the daily spend tables, where /user/daily/activity/aggregated returned it as a breakdown.models key. The spend log payload now records such rejections under the constant unknown-model, keeping the failed request counted without persisting client input as a model name.
2026-09-10 14:18:54 -07:00
mateo-berri
05f459d898 fix(redis): quiet every per-request Redis fallback while the breaker is open 2026-09-10 14:18:53 -07:00
moe-berri
bc77aa05d2
Merge pull request #40608 from BerriAI/moe/lit-7493-zocdocauto-router-encrypted-codex-sub-agent-task-is
fix(router): classify encrypted delegated tasks with native Responses
2026-09-10 14:08:31 -07:00
ryan-crabbe-berri
2f114d44ed
Merge pull request #39190 from WolframRavenwolf/litellm_wandb_reasoning_effort
fix(wandb): preserve reasoning_effort in chat completions
2026-09-10 14:02:49 -07:00
mateo-berri
3b0ffaaa8d test(proxy): send valid organization requests so the licensed gate test proves the handler ran 2026-09-10 14:00:41 -07:00
mateo-berri
ad607516a2 fix(caching): log a refused async_increment as debug while the breaker is open 2026-09-10 13:59:15 -07:00
devin-ai-integration[bot]
46a185d3cd
feat(rust_bridge): count budget-check input tokens in Rust on all LLM routes (#40381)
* feat(rust_bridge): count budget-check input tokens in Rust on all LLM routes

Rust counts input tokens from the raw JSON body with the GIL released inside the existing budget reservation, covering every LLM route the auth dependency guards. It only fires for models on the Anthropic tokenizer when a budget is set, and Python counts whenever Rust is off, missing, or declines a body shape.

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

* perf(rust): count byte-level BPE tokens without the GPT-2 split regex (#40594)

The oniguruma run of the ByteLevel pre-tokenizer regex is about 90% of
encode_fast on a 100k token body (100 ms of the ~110 ms Rust admission
count in the gateway pod). A hand-written scanner that yields the same
pieces, then feeds the model directly, counts the same text in 10 ms.
It only engages for tokenizers with the Anthropic shape (optional NFKC,
ByteLevel without prefix space, no post-processor) and falls back to the
full encoder when the text contains an added token. Parity with
encode_fast is tested on random texts, the pieces are compared with the
real pre-tokenizer, and the \p{L}/\p{N}/\s tables are checked against
oniguruma for every code point.

NFKC runs through unicode-normalization-alignments, the crate and
Unicode tables NormalizedString::nfkc already uses, so the fast path
normalizes exactly what the full encoder would. Using the newer
unicode-normalization crate changed the count for 171 code points that
gained compatibility decompositions after Unicode 9 (U+32FF, U+A7F1..).
The fast normalizer is compared with the tokenizer's for every scalar
value and on random texts.

The scanner is built without mutable state: byte_char and mapped_len replace the const table builders and the reusable mapped buffer, and iter::successors replaces the stateful piece iterator. byte_chars_match_the_byte_level_alphabet checks the byte mapping against ByteLevel for every scalar value.

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(rust_bridge): bound concurrent token-count encodes and share the Anthropic tokenizer predicate

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

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-10 13:56:30 -07:00
mateo-berri
065ab11f0b Merge remote-tracking branch 'origin/litellm_internal_staging' into HEAD 2026-09-10 13:55:11 -07:00
devin-ai-integration[bot]
0e35c8fee9
fix(proxy): recreate the Prisma client when the writer session turns read-only (#40610)
The writer health probe only ran SELECT 1, which a read-only Postgres
session answers fine, so a pooled connection left pointing at a demoted
primary kept failing every write with SQLSTATE 25006 until the pod was
restarted. Probe transaction_read_only instead, treat a 25006 on the
request path as a signal to recreate the client, and back off
exponentially while the database as a whole stays read-only so a replica
or an in-progress failover does not get its engine killed every cycle.

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-10 13:53:42 -07:00