Commit graph

46529 commits

Author SHA1 Message Date
Yassin Kortam
bf59b7e23d
feat(rust): route /chat/completions through the Rust core for anthropic and bedrock (#37241)
Adds a chat_completions route module to litellm-core, mirroring the messages
route, plus Anthropic Messages and Bedrock Converse provider configs. The
per-model `rust: true` opt-in now covers /chat/completions for both providers.

The core accepts an allowlisted subset (text conversations, non-streaming) and
returns CoreError::Unsupported for anything else, so tool calls, multimodal
content and streaming fall back to the Python path transparently.

Resolves LIT-5698
2026-08-20 16:15:24 -07:00
Mateo Wang
8f68bc6579
Merge pull request #37607 from BerriAI/litellm_lit_5869_cost_e2e_pins
test(e2e): pin prompt-cache, service-tier, and cost-header billing as permanent regressions
2026-08-20 16:15:08 -07:00
mateo-berri
14faec9bc4 fix(redis): keep a coroutine redis_connect_func on async clients
redis-py awaits a redis_connect_func that is a coroutine function, so
dropping every connect func the async paths cannot convert took away an
auth path that worked.
2026-08-20 16:10:53 -07:00
devin-ai-integration[bot]
33bafd0402
fix(router): make prompt caching affinity aware of auto-injected cache_control (#37689)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 16:10:27 -07:00
mateo-berri
47731303b5 fix(caching): bound the semantic cache embedding lookup
A semantic cache lookup embeds the prompt before the request reaches the LLM,
and that embedding call carried no deadline of its own. It inherited the 6000s
request timeout and the Router's num_retries, so an embedding endpoint that is
down or unroutable parked every proxied request for minutes and gave back
nothing but x-litellm-semantic-similarity 0.0 once it finally gave up.

The lookup now runs on its own short deadline, 5s by default, with retries off
so failures cannot stack. Redis, Valkey and qdrant all pick it up, and the
deadline is settable per cache with semantic_cache_embedding_timeout or
globally with SEMANTIC_CACHE_EMBEDDING_TIMEOUT_SECONDS.
2026-08-20 16:09:30 -07:00
devin-ai-integration[bot]
8c42d8b97b
fix(token_counter): stop large token counts from blocking the proxy event loop (#37697)
tiktoken's BPE merge loop is quadratic in the length of a single regex piece, so a long
run of one repeated character turns a multi-MB payload into minutes of CPU. Encoding in
bounded chunks makes that linear, at a drift of at most ~1 token per chunk boundary.

Chunking alone only makes the stall shorter, so the async paths now count in a worker
thread: tiktoken releases the GIL for its Rust encode, so the loop keeps serving other
requests while a count is in flight. The /utils/token_counter endpoint awaits the new
atoken_counter, and the router's async deployment selection counts off-loop and hands
the result to _pre_call_checks instead of making it count inline.

The chunk size knob is bounded to [1, 4096]: a non-positive value used to raise or
silently report zero tokens, and an arbitrarily large one restored the quadratic cost
this exists to remove. Out-of-range and unparseable values warn and fall back to 1024.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-08-20 16:09:07 -07:00
devin-ai-integration[bot]
3d3946059d
perf(prometheus): render /metrics off the event loop and coalesce concurrent scrapes (#37702)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 16:08:22 -07:00
devin-ai-integration[bot]
ebdbb317b3
perf(budget_reservation): tokenize each request once, off the event loop for large prompts (#37683)
Budget reservation tokenized every request twice, once for the max-cost
estimate and once for the input-cost estimate, and again per pricing
candidate. Tokenizing is O(prompt) and ran inline, so admitting one large
request stalled every other request the worker was serving.

Count the input tokens once per request and reuse the counts for both
estimates. Prompts above 30K characters of input text are counted in a
worker thread so the event loop stays free. The size heuristic renders the
body rather than walking its values, so tool-schema property names count
toward the threshold, and it sizes every field the counter tokenizes,
tool_choice included.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-08-20 16:07:38 -07:00
devin-ai-integration[bot]
a9744645ee
fix(logging): bound oversized error payloads written to stdout (#37684)
Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-08-20 16:07:21 -07:00
devin-ai-integration[bot]
035a3227ac
fix(proxy): capture requester IP in 401 and auth-time 429 failure logs (#37707)
Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-08-20 16:05:59 -07:00
devin-ai-integration[bot]
7bcdc6c707
fix(logging): bound the shared logging executor backlog (#37694)
The shared logging ThreadPoolExecutor uses an unbounded work queue, so
sync callbacks that fall behind request arrival pin every queued payload
in memory until the task restarts. Cap queued-plus-running work with a
semaphore, shed submissions past the cap, and warn at most once every 30
seconds naming the knob that raises it. No caller of the shared executor
reads the returned future, so shedding is safe.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-08-20 16:04:43 -07:00
Devin AI
618d907d5a fix(fal_ai): price gpt-image-2 unprefixed alias and edit endpoint
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 23:04:37 +00:00
devin-ai-integration[bot]
18242aec9a
fix(router): isolate deployment model info (#37687)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 16:04:08 -07:00
devin-ai-integration[bot]
cacfc95eed
fix(datadog): normalize alias and request tag values before submission (#37682)
Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-08-20 16:03:47 -07:00
devin-ai-integration[bot]
a07b2c30b0
feat(helm): compose DATABASE_URL_READ_REPLICA from a reader host secret key (#37109)
* feat(helm): compose DATABASE_URL_READ_REPLICA from a reader host secret key

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

* test(helm): cover reader host composition and readReplicaUrlKey precedence

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

* fix(helm): suppress unused reader host env when readReplicaUrlKey is set

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

* fix(helm): emit reader host only when readReplicaUrl composition is active

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

---------

Co-authored-by: milan <milan@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Yassin Kortam <yassin@berri.ai>
2026-08-20 16:03:32 -07:00
ryan-crabbe-berri
bc52dd5c8b
fix(proxy): split agent inference and management routes so admin nodes can create agents (#37730)
Agent registry CRUD (/v1/agents*) sat in agent_routes, which feeds
llm_api_routes, so DISABLE_LLM_API_ENDPOINTS returned "LLM API routes are
disabled for this instance." for every Admin UI Agents tab call. Split the
group the same way MCP is split: agent_inference_routes stays on the data
plane, agent_management_routes joins management_routes, and agent_routes
remains their union for keys configured with allowed_routes=["agent_routes"].

Non-admin callers reached agent CRUD through llm_api_routes before, so the
management paths also join self_managed_routes and the llm_api_routes virtual
key carve-out; the handlers already scope reads by role and 403 non-admin
writes.

Both new groups are tuples, so check_route_access now takes a Sequence and
matches wildcards through a generator instead of materializing an
intermediate list on every call.
2026-08-20 16:03:15 -07:00
devin-ai-integration[bot]
d8a57a1a2b
fix(reset_budget_job): reconnect and retry on transient DB transport errors (#37705)
A dropped connection anywhere in the budget reset tick used to abort the whole
phase, so every due key, user, team and budget tier stayed unreset until the
next tick ten minutes later. Route the job's DB calls through
call_with_db_reconnect_retry so a transport blip costs one reconnect instead.

Reads replay on any transport error, since re-running a SELECT has nothing to
double-apply. Writes are non-idempotent, a reset assigns spend = 0
unconditionally, so they narrow to DB_RETRY_SAFE_ERROR_TYPES: only a
ConnectError proves the statements never reached the database. A post-send
error like ReadError or ReadTimeout leaves the commit outcome unknown, and
replaying one that already landed would erase whatever was spent since, so
those keep the pre-existing behaviour of failing the tick.

Resolves LIT-5372

Co-authored-by: Yassin Kortam <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 16:02:51 -07:00
devin-ai-integration[bot]
22e8b45c68
feat(proxy): add maximum_health_check_retention_period to bound the health-check table (#37681)
* feat(proxy): add health check retention cleanup

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

* test(proxy): drop redundant health-check assertion

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

* fix(proxy): share cleanup budget across retention groups

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

* refactor(proxy): clarify cleanup group deadlines

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

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 16:01:16 -07:00
devin-ai-integration[bot]
3ea1c16b0d
fix(auth): cache team member default budget as a typed model (#37695)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 16:00:57 -07:00
devin-ai-integration[bot]
a030b33188
fix(scim): fail group sync when a member add or user creation fails (LIT-5105) (#37688)
* fix(scim): fail group sync when a member add or user creation fails

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

* style(scim): apply ruff format

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

---------

Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 16:00:37 -07:00
devin-ai-integration[bot]
387a948263
fix(scim): keep the matched user_id on POST /Users email match (#37701)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 16:00:08 -07:00
mateo
df00c334d1 fix(proxy): reload the unpriced-model toggle regardless of supported_db_objects
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 22:53:34 +00:00
mateo-berri
307ca1bcc7 fix(redis): say when an async client drops an unusable connect func
A caller-supplied redis_connect_func has no way to run on an async
connection, so log it instead of dropping it in silence.
2026-08-20 15:53:00 -07:00
mateo-berri
3c44f8d926 fix(ui): surface toggle failures on the block-unpriced-models setting
The hook swallowed errors into the console, so an admin flipping the switch without
STORE_MODEL_IN_DB saw nothing happen and got no reason why. Adds the missing hook tests.
2026-08-20 15:47:13 -07:00
mateo-berri
bfe54eb013 docs(redis): say why async paths cannot reuse redis_connect_func
The AUTH exchange it runs is the blocking client API, so on an async
connection send_command and read_response hand back coroutines nobody
awaits and the connect fails outright.
2026-08-20 15:46:03 -07:00
mateo-berri
cba4fa403d fix(redis): keep Azure AD and GCP IAM auth on URL and pool clients
REDIS_URL-based async clients and every async connection pool dropped the
managed-identity credential the caller configured, so they connected
unauthenticated against an auth-enforcing Redis. The conversion from
redis_connect_func to a CredentialProvider now happens once, before any
branch, and covers the url, sentinel, cluster, and pool paths alike.

Also adds credential_provider to the cluster kwargs allowlist, which
silently filtered it out.
2026-08-20 15:42:48 -07:00
Bisma Nawaz
909ab23b89 test: annotate parametrized traffic-type test inputs 2026-08-21 03:42:34 +05:00
mateo-berri
a3b6762788 fix(streaming): price partial-stream spend rows at the real model and keep prompt and cache fields
A streaming chat completion that ends early (client disconnect, or the proxy
cutting the stream at LITELLM_MAX_STREAMING_DURATION_SECONDS) wrote a spend log
row with spend 0.0, prompt_tokens 0 on the proxy-cut path, and no cache fields
in usage_object. The proxy restamps chunk.model in place to the client-facing
alias, so the partial response rebuilt from those chunks priced the unmapped
alias and came out at 0. The failure path also rebuilt usage without the
request messages, so prompt tokens counted to 0, and a cut stream never sees
the final usage event that normally zero-fills the cache fields.

Restamp the rebuilt partial response with the wrapper's real model before cost
calculation on both the disconnect and the failure paths, pass the request
messages when rebuilding usage on the failure path, and zero-fill missing
cache usage fields the way completed streams already do.
2026-08-20 15:39:08 -07:00
mateo-berri
3672fa9fb5 fix(proxy): log block_requests_for_models_without_pricing updates lazily
The eager f-strings tripped tests/test_litellm/test_logging.py::test_logging_calls_do_not_build_their_message_eagerly.
2026-08-20 15:38:27 -07:00
mateo-berri
2b2d6d7aad fix(proxy): apply DB-persisted safe litellm settings on every worker's config reload
Peer workers previously kept their startup value for block_requests_for_models_without_pricing
until a restart, so a toggle from the UI only took effect on the worker that served the request.
2026-08-20 15:35:20 -07:00
Mateo Wang
1d7f675e52
Merge pull request #37663 from BerriAI/litellm_azure_postgres_entra_auth
feat(proxy): authenticate to Azure Postgres with Microsoft Entra ID tokens
2026-08-20 15:35:05 -07:00
Devin AI
f3896c0527 test(fal_ai): use monkeypatch for the gpt-image-2 cost map fixture
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 22:27:01 +00:00
Devin AI
60706d5f89 feat(fal_ai): add gpt-image-2 image generation support
Route fal.ai's openai/gpt-image-2 endpoints through a dedicated transformation that maps OpenAI image params (n, size, quality, output_format) into fal's schema, and register the model in the cost map.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 22:18:34 +00:00
mateo-berri
5c89490124 Merge litellm_internal_staging into litellm_fix_responses_bridge_incomplete_500 2026-08-20 15:16:14 -07:00
tin-berri
cb4eb82249
feat(ui): per-model reasoning effort in the complexity tier editor (#37673)
* feat(ui): per-model reasoning effort in the complexity tier editor

* feat(ui): gate the effort control on model group reasoning support
2026-08-20 15:10:35 -07:00
Bisma Nawaz
d317c5621f fix: map Gemini ON_DEMAND_FLEX traffic type to flex service tier 2026-08-21 02:56:23 +05:00
mateo
eb8d402187 test(proxy): cover a registry model priced only via tiered_pricing
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 21:55:43 +00:00
tin-berri
2dcd453860
feat(shadow_eval)!: gate the per-key budget on dollar spend instead of turns (#37555) 2026-08-20 14:55:21 -07:00
tin-berri
60e03bedcf
fix(ui): surface the paginated fallback on Cost Optimization (#37659)
* fix(ui): surface the paginated fallback on Cost Optimization

The page streamed its fallback silently: useDailyActivityRange dropped
the hook's progress and cancel fields and CacheLeakageCard only showed
a loading state while empty. Extract the Usage page's fetch banner into
a shared PaginationStatusAlerts component, render it above the tabs,
and note on the cache leakage tables when pages are still arriving.

* fix(ui): gate the cache leakage streaming note on isFetchingMore only

loading also covers a fresh aggregated request over the previous
range's rows, where pagination copy mislabels stale data. Drop the
redundant component comment flagged against the repo comment policy.
2026-08-20 14:55:00 -07:00
mateo-berri
ab79b8dcb6 fix: count tiered_pricing as a cost mapping when blocking unpriced models 2026-08-20 14:52:37 -07:00
Mateo Wang
d556fac56b
Merge pull request #37112 from mubashir1osmani/litellm_add_perplexity_agent_api_models
feat(perplexity): add Agent API third-party models
2026-08-20 14:49:12 -07:00
yuneng-jiang
9432f40145
bump: litellm-enterprise 0.1.57 -> 0.1.58, litellm-proxy-extras 0.4.87 -> 0.4.88 (#37717) 2026-08-20 14:45:40 -07:00
mateo
6bb677d30f fix(model-costs): correct gpt-5.6 input token cap
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 21:44:32 +00:00
yucheng-berri
abdde94ad5
fix(ptu): refuse an incomplete config.yaml reservation the way the endpoints do (#37703)
* fix(ptu): refuse an incomplete config.yaml reservation the way the endpoints do

POST /model/new answers 400 when PTU fields are set without a team_id, a
ptu_effective_from, or the count and rate together. config.yaml ran none of
those checks, so the same deployment loaded and served, billing per token
while accruing no flat cost, with nothing logged.

The rule moves into litellm_core_utils.ptu_pricing so both paths state it
once. Registration refuses such a deployment and names it, and the proxy's
ignore_invalid_deployments keeps that to the one entry. Only enforced while
PTU cost attribution is enabled, so a proxy that never opted in is unchanged.

* refactor(ptu): build the refusal message in the module that owns the rule

router.py raised a message it composed itself, which put proxy-facing
wording on the shared SDK surface. ptu_config_error now takes the
deployment name and returns the whole sentence; the endpoints still ask
without a name and their 400 bodies are unchanged.
2026-08-20 14:43:06 -07:00
mateo-berri
5301872093 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_block_unpriced_models 2026-08-20 14:42:03 -07:00
Yassin Kortam
996693f1eb
fix(a2a): accept the whole JSON-RPC id union the spec defines (#37704)
JSON-RPC 2.0 types `id` as string, integer or null, but
LiteLLMSendMessageResponse annotated it as a bare required `str`. Pydantic v2
dropped v1's int-to-str coercion, so an upstream agent echoing an integer id was
rejected outright, and a null id, which section 5 requires for an error that
cannot be correlated to a request, was rejected too. Both surfaced as -32603 with
a pydantic ValidationError in the message: five distinct 500s on
/a2a/{agent_id}, across message/send and tasks/get.

Everything around the model already handled the full union: the endpoint reads
the id off the body as Any, its helpers are typed `str | int | None`, the error
builder takes `object`, and the streaming path passes the id through untouched.
The response model was the only narrowing left.

Backfilling an id the agent omitted keeps the caller's type too, since JSON-RPC
requires the response id to equal the request id and a caller that sent 7 cannot
correlate a response carrying "7".

`bool` is excluded from the integer half even though it subclasses `int`, so a
boolean id is stringified rather than relayed as 1 or 0, where it would collide
with a real integer id another in-flight request may be using.
2026-08-20 14:41:22 -07:00
yuneng-jiang
4af66657f9
feat(ci): freeze the conftest save/restore inventory so it can only shrink (#37621)
* feat(ci): freeze the conftest save/restore inventory so it can only shrink

* fix(ci): resolve the named constant a conftest save loop iterates

* fix(ci): match the snapshot shape instead of a list of blessed dict names

* feat(ci): fail a branch that clears TQ violations without lowering the ceiling

A limit that only ever falls is not the same as one that falls when it can.
Clearing violations and leaving the ceiling above the new count let the same
violations return later under a limit nobody moved, so the gate now fails on
that and names `make lint-budget-update` as the fix. It needs both head below
base and head below limit, so headroom already in the base is never blamed on
the branch that happens to run next.

Drops the seeded-rule exemption from the ratchet along with it. Its stated
reason was that the base tree predates a rule introduced on this branch, but
base counts are measured with the current checker, so such a rule is counted at
the base too and its grandfathered total was never at risk of reading as fixed.
Removing the exemption is what lets a newly seeded rule ratchet like the six
that came before it.

The base scan is skipped when the branch touches neither the test tree nor the
checker, since neither count can have moved.
2026-08-20 21:39:59 +00:00
mateo-berri
7f539e388c Merge branch 'litellm_block_unpriced_models' of https://github.com/BerriAI/litellm into litellm_block_unpriced_models
# Conflicts:
#	litellm/proxy/auth/auth_checks.py
#	tests/test_litellm/proxy/auth/test_auth_checks.py
2026-08-20 14:36:58 -07:00
yuneng-jiang
648c6e7dc5
feat(ci): assert .github/workflows holds only workflows, correctly named (#37616)
* feat(ci): assert .github/workflows holds only workflows, correctly named

* style(tests): annotate the hygiene test module's names with Final

* fix(ci): report a .yaml workflow as a naming finding, not a stray

GitHub reads .yml and .yaml alike, so WF001 telling you to move a valid
.yaml workflow to .github/scripts/ was wrong advice. WF001 now covers only
files that are not workflows at all, and the .yml spelling this directory
keeps moves to WF004, which says to rename rather than relocate.

WF001 also never looked into subdirectories, since GitHub does not read
them either; the message now says so. The directory is injected rather
than read off a module constant, so the cases are testable without
monkeypatching.
2026-08-20 21:36:26 +00:00
mateo-berri
2996a18fa9 fix(model_prices): document 262k input limit on fireworks qwen3p8-max 2026-08-20 14:33:04 -07:00