Commit graph

12640 commits

Author SHA1 Message Date
mateo-berri
3c73a39877 fix(cli): verify every credential store transition before reporting it done
A keyring backend can accept a write and keep nothing. That is exactly what
`keyring --disable` and PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring
select, and it raises nothing to distinguish itself, so `lite login` was
handing the credential to a black hole, scrubbing its own copy from
token.json, and printing a success message over a login that no longer
worked. Reading the value back is the only way to tell that backend apart
from a keychain that really stored the secret.

The same rule closes the rest of the gaps. A credential the token file will
not record is taken back out of the keychain instead of being left live on a
machine with no record of it, and is reported rather than raised. The
migration stages its scrubbed file before the keychain is handed anything,
so a directory that will not accept the rewrite stops the move rather than
leaving the secret in two places. Logout no longer reads a key in the file
as proof that the keychain is clear, which was never sound across two
separate runs, and only draws that conclusion when the `keyring` package is
missing outright, where nothing could have reached a keychain at all.
2026-08-19 21:10:59 -07:00
mateo-berri
424e74ba9f fix(cli): roll the keychain write back when the plaintext copy cannot be removed
Removing the file when it could not be rewritten covered a full disk, but not a
~/.litellm that permits neither the rewrite nor the delete, which is what a
`sudo lite login` leaves behind. There the secret was copied into the keychain and
kept in cleartext on disk, so migration widened exposure instead of narrowing it

Migration now only keeps the vault copy if the file's copy is gone. When it is not,
the write is rolled back and the user is left exactly as they were, logged in with
one copy of the credential
2026-08-19 20:31:43 -07:00
mateo-berri
1750893a69 fix(cli): never report success while a credential is still readable
Migration moved the secret into the keychain and then suppressed any OSError from
rewriting token.json, so a file that could not be rewritten kept the credential in
cleartext while every command reported success. That file is now removed instead:
signing in again costs one command, a stranded live credential costs the credential

`lite logout` also reported a clean logout whenever the keyring package was missing,
on the reasoning that an install without it could never have stored anything. The
entry belongs to the OS, so a keychain-backed login survives a logout run from a venv
without the cli extra. erase() now reports which keychain state applies, and logout
warns with the advice that fixes each one, staying quiet for file-backed logins whose
token file still carries its own secret

Also pins the migration path's tightening of a world-readable legacy token.json, and
moves the logout tests off patch() onto the injected vault
2026-08-19 20:26:41 -07:00
mateo-berri
65d834f01d Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_cli_refresh_tokens
# Conflicts:
#	basedpyright-code-budget.json
2026-08-19 19:50:26 -07:00
mateo-berri
01add58298 fix(cli): name why a login fell back to the token file
lite ships with every install of litellm, but the keyring package it needs
for keychain storage only ships with the cli extra. Such a user on a Mac was
told 'No OS keychain available' about a machine that plainly has one, with
nothing pointing at the missing package.

The vault now reports which of the three unusable states it is in, so login
can point at the install, name the kill switch, or report a genuinely absent
keychain.
2026-08-19 19:45:38 -07:00
yucheng-berri
c2b3c4b1e4
feat(ptu): accrue flat cost for PTU deployments declared in config.yaml (#37556)
* feat(ptu): accrue flat cost for PTU deployments declared in config.yaml

The flat-cost rollup reads deployments from LiteLLM_ProxyModelTable, and config.yaml
models never reach that table by design, so a PTU deployment declared there accrued no
flat cost at all while still billing its traffic per token. The provider bills the
reservation whichever file declared it.

The rollup now also reads the deployments the router holds that no database row owns,
identified by db_model, skipping the per-request credential clones that carry
original_model_id and reuse their source's PTU config under a fresh id. Registering such
a deployment zeroes its pricing, since reserved capacity already pays for the traffic it
serves, and leaving a rate unset falls back to the public cost map, which makes the double
charge the default rather than an opt-in.

The rules both halves apply now live in one module. The rollup's test for what it will
charge and the router's test for what to zero have to agree, or a deployment one accepts
and the other declines serves its traffic for free. That module also owns the fields the
write endpoints already zero, so the two paths cannot drift: tiered_pricing is emptied
rather than zeroed because its tiers outrank the rates beside them, the search context
table is written zeroed because an absent one means the provider default, and any further
rate the deployment itself declares is zeroed alongside the standing set.

The prune is bounded to the deployments a run scanned, but only for a run that priced a
config-declared deployment. Deciding a row is garbage on staleness alone stays correct
while every run derives its charges from the same table, so a database-only run sweeps
exactly as it did before; once one host's charges come from a file the others cannot read,
a row it never considered is not evidence of anything.

Behaviour change worth calling out: a zeroed deployment sorts ahead of an unpriced sibling
in QualityRouter's cost tiebreak, where an unset rate previously sorted last. Reserved
capacity really is the cheaper choice, but the ordering moves.

* refactor(ptu): drop a Final rebind and two redundant isinstance guards

The basedpyright budget rejected reassigning a Final in the datetime coercion and
two isinstance calls the router entry's own type already guarantees. Filtering the
built records rather than the raw entries removes both guards and leaves
_router_deployment as the single validator.
2026-08-19 19:08:14 -07:00
mateo-berri
0de829d3e4 feat(cli): store the lite login credential in the OS keychain
lite login used to write the minted cli-session key in cleartext to
~/.litellm/token.json. The secret material (key plus any JWT) now goes
to the OS keychain through the optional keyring package, with the 0600
file kept for non-secret metadata and as the fallback on headless boxes.
Legacy plaintext files keep authenticating and are migrated into the
keychain, then scrubbed, on first read. A secret still on disk always
outranks the keychain entry, so a failed keychain write can never
resurrect a stale key. LITELLM_PROXY_API_KEY and --api-key precedence
is unchanged, lite logout clears both stores and warns when the
keychain will not release the entry, and ~/.litellm is created 0700
(tightened from 0755 where an older CLI left it broader).
LITELLM_CLI_DISABLE_KEYRING=1 forces the file fallback.
2026-08-19 18:57:35 -07:00
devin-ai-integration[bot]
3a04860122
feat(proxy)!: default audit logs on for enterprise licenses (#37518)
* feat(proxy): enable audit logs by premium license

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

* test(proxy): support premium audit logging mocks

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

* test(proxy): disable audit logging for key rotation mocks

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

---------

Co-authored-by: yucheng <yucheng@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-19 18:49:21 -07:00
Mateo Wang
8922aaab95
fix(anthropic): log partial stream spend when a /v1/messages client disconnects mid-stream (#37558) 2026-08-19 18:43:46 -07:00
devin-ai-integration[bot]
f5cfa84220
feat(router): allow per-tier litellm_params in complexity autorouter config (#37064)
* feat(router): support complexity tier request params

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

* fix(router): make complexity tier params immutable

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

* refactor(router): simplify complexity tier overlays

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

* fix(router): preserve plain tier config round trips

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

* fix(router): mask tier params in routing decisions

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

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-20 01:39:02 +00:00
yuneng-jiang
7b6e16cfd3
perf(ci): gate the lint, MCP and dashboard jobs on the pull request's file list (#37559)
PR #37550 taught the backend unit-test shards to read the pull request's own
file list, but four required jobs were never wired to that gate and ran in full
on every pull request regardless of what it touched. A UI-only pull request
still paid roughly 17 runner-minutes of Python work it could not have affected,
and a backend-only one still installed and built the dashboard.

Lint and the MCP suite now take the existing backend decision. The dashboard
build and unit tests take a new ui decision, which tracks ui/ rather than
reusing client: client deliberately runs whenever the backend changes, because
it gates CircleCI's end-to-end jobs that drive a real proxy, while the build and
the unit tests cannot see the backend at all. CI config counts as ui-relevant
too, so a pull request that rewrites the dashboard workflows still exercises
them instead of shipping unvalidated.

The gate stays inside the job rather than moving to on.paths or to a job-level
condition on the shard callers. A workflow filtered out by on.paths never starts
and never reports, so a required check waits forever, and a skipped caller job
publishes its own name instead of the nested "<shard> / Run tests" the ruleset
requires. Both were measured before settling on this shape.

Three setup steps in the shard base and in the documentation job also leaked
past the gate, so a skipped shard still spent about twelve seconds installing uv
and restoring its cache. They now carry the same condition, and the documentation
job stops cloning litellm-docs when it has nothing to validate.
2026-08-19 18:32:21 -07:00
Mateo Wang
26841dae43
Merge pull request #37520 from BerriAI/litellm_lit_5795_failed_request_deployment_details
fix(proxy): populate deployment attribution on failed-request spend logs
2026-08-19 18:14:01 -07:00
Mateo Wang
a6163e0146
Merge pull request #37543 from BerriAI/litellm_lit_5785_vertex_regional_pricing
fix(vertex_ai): apply regional endpoint uplift to cost tracking
2026-08-19 17:56:34 -07:00
mateo-berri
1140366bee fix(vertex_ai): resolve passthrough serving location in the logging cost recompute 2026-08-19 17:24:44 -07:00
Yassin Kortam
f22eeb2ce0
fix(proxy): initialize the secret manager before resolving os.environ config references (#37544)
`ProxyConfig.get_config()` walked the parsed config and replaced every
`os.environ/<KEY>` string with `get_secret(value)` before anything initialized
the secret manager, so a key held only by the manager resolved to `None` and
that `None` was written back into the config. The later fallback in
`load_config` could not recover it, because the key now existed with a `None`
value.

Hoist the initialization into `get_config()`, ahead of the resolution pass, so
every entrypoint gets it: the CLI already did this itself, but the microservice
entrypoints (`gateway/main.py`, `backend/main.py`) uvicorn the app directly and
bypass the CLI. `load_config`'s own call is now redundant and is dropped, so
startup builds the manager once instead of building one and discarding it.

`get_config()` also runs on management-endpoint request paths, so this returns
early once a manager exists rather than rebuilding the client per request.

Also warn when a reference the manager would have been asked for resolves to
`None`. The reporter had no log line at all to work from. `get_secret` only
reaches the manager when reads are enabled and the name is in `hosted_keys`, so
`secret_manager_would_be_consulted` mirrors that gate and keeps the warning off
env-only references, which are expected rather than an error.
2026-08-19 17:00:26 -07:00
mateo-berri
919bf1a097 fix(proxy): strip client standard_logging_object before the failure logging handler 2026-08-19 16:53:02 -07:00
mateo-berri
c549cddada fix(vertex_ai): price passthrough calls on the URL's serving location 2026-08-19 16:44:52 -07:00
yuneng-jiang
eecb226762
fix(ci): gate backend unit tests on the pull request's own file list (#37550)
detect-backend-changes diffed the event payload's base.sha against the
checked-out ref. Those are two different points in time: actions/checkout
resolves refs/pull/N/merge, and GitHub recomputes that ref whenever the base
branch advances, so the diff picked up whatever landed on staging between the
event firing and the job starting. On a recent UI-only pull request three
backend commits from staging were attributed to the branch, and every backend
shard ran in full

Ask the API which files the pull request touches instead. That is the same set
the Files changed tab shows, and it is immune to either endpoint moving. The
shell body moves into .github/scripts/detect_backend_changes.sh so it can be
exercised directly, and the fail-open paths now also cover an API failure, a
file list past the API's 3000-entry listing ceiling, and a classifier that
prints something unexpected
2026-08-19 16:41:56 -07:00
yucheng-berri
b7181a8914
perf(otel): build the credential-scoped tracer Resource once per logger (#37542)
Every dynamic tracer-provider build called Resource.create, which scans the entry
points of every installed distribution, roughly 3ms and 200 file opens. The dynamic
providers reach it from the async logging path, which runs on the event loop serving
requests, so past the provider cache bound every request paid it and delayed the
requests in flight alongside it

The value derives only from the logger's config and process environment, so it is
built once per logger and reused. This logger's own init-time providers share it,
which also removes redundant startup builds. ArizeLogger overrides _init_tracing and
still builds its own, so it keeps one extra build

Refs LIT-5437
2026-08-19 16:40:40 -07:00
mateo-berri
7b6f537855 fix(anthropic_messages): price native /v1/messages vertex calls on the deployment location
The proxy pre-creates the logging object before the router picks a deployment,
and the native /v1/messages handler never copied the deployment's
vertex_location into the logging params it updates, so cost resolution fell
back to the environment or the default region and priced every call on this
surface with the regional endpoint uplift. Copy the explicitly configured
location from the request's litellm params, the same source dispatch builds
the request URL from, and register the new regional_endpoint_uplift_multiplier
field in the cost map schema test.
2026-08-19 16:26:34 -07:00
Mateo Wang
0a3504c8a3
Merge pull request #37527 from BerriAI/litellm_batch_file_upload_validation
feat(proxy): fast-fail validation for batch input files at /v1/files
2026-08-19 16:24:39 -07:00
mateo-berri
dce207add4 fix(proxy): strip client standard_logging_object and zero-fill unknown recovered cost on the failure path
Auth and pass-through failures reach post_call_failure_hook with the raw request body unstripped, so a client-supplied standard_logging_object could feed the new attribution fallback when the logging object carries none. Pop the key before the lift so only the logging object may supply it. Also coalesce a None recovered cost to 0.0 so the lift always overwrites any client-supplied response_cost, matching the merge base's clobber semantics.
2026-08-19 16:19:26 -07:00
Mateo Wang
634e699555
Merge pull request #36331 from BerriAI/devin_ai_agentcore_search
feat(search): add Amazon Bedrock AgentCore web search provider
2026-08-19 15:59:17 -07:00
mateo-berri
8494a4deee fix(vertex_ai): read the served location from optional_params when pricing proxy calls 2026-08-19 15:58:12 -07:00
Mateo Wang
da9d406e8d
Merge pull request #34887 from RayJueWang/litellm_fix_spend_deadlock_retry
fix(proxy): retry spend updates on Postgres deadlock instead of dropping them
2026-08-19 15:53:45 -07:00
Mateo Wang
449bf68498
Merge pull request #36987 from BerriAI/litellm_infer_single_worker_redis_banner
feat(proxy): auto-suppress the no-Redis banner for confirmed single-worker deployments
2026-08-19 15:52:58 -07:00
tin-berri
dfeb12649b
feat(complexity-router): make the reasoning override floor configurable (#37537)
The reasoning override's floor was pinned to tier_boundaries.simple_medium,
so an operator could not restore the unconditional promotion nor raise the bar
independently of the SIMPLE/MEDIUM cut. Setting reasoning_override_min_score
was accepted and echoed back by /model/info, because the config model allows
extra keys, while routing ignored it.

Resolve the floor through one accessor that falls back to simple_medium when
the field is unset, so moving that boundary still moves the floor with it, and
an explicit 0 is a real floor rather than an absent one. Record the resolved
value on the routing decision so a logged row states the floor that applied,
which is also what lets the Admin UI stop hardcoding the copy PR #37500 added.
2026-08-19 15:45:39 -07:00
mateo-berri
7e27e211a1 Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_lit_5785_vertex_regional_pricing
# Conflicts:
#	type-discipline-budget.json
2026-08-19 15:44:05 -07:00
mateo-berri
9bb5483991 fix(proxy): apply db-backed max_batch_file_size_mb on config reload 2026-08-19 15:37:44 -07:00
mateo-berri
b477d0967a fix(proxy): lift standard_logging_object onto request_data before the logging object is popped 2026-08-19 15:34:38 -07:00
ryan-crabbe-berri
74b279bc44
fix(auth): resolve bare model names against wildcard deployments in model access groups (#37492)
* fix(auth): resolve bare model names against wildcard deployments in model access groups

* test(e2e): cover model access group permission checks on keys and teams
2026-08-19 15:33:29 -07:00
Yassin Kortam
6ca48efc8b
feat(cli): add lite login --config-claude to wire Claude Code at login (#37507)
`lite up` already patches ~/.claude/settings.json, but only for as long as it
runs in the foreground, and it restores the original file on exit. Users
proxying Claude Code through LiteLLM therefore have to re-wire it by hand after
every login.

--config-claude makes that write persistent. It reuses the settings shape
`lite up` writes (env.ANTHROPIC_BASE_URL plus an apiKeyHelper invocation),
preserves every unrelated key, creates the file when missing, and writes it
atomically with owner-only permissions. Plain `lite login` is unchanged.

Reaching the credential through apiKeyHelper rather than copying it into the
file means a later login refreshes it with no further action, and keeps the
short-lived CLI token out of settings.json entirely.

The shared parts of the settings-file handling move from up.py into a new
claude_settings.py, since up.py imports auth.py and so auth.py cannot import
up.py back. That module now also owns the registry of commands that can be
temporarily managing the file, so the persistent write refuses while either
`lite up` or `lite autoroute up` holds a backup it would later restore over
this write.

Because this write has no backup and no `lite down`, it is stricter than
`lite up` about the user's file: it writes through a symlinked settings.json
rather than replacing the link with a regular file, and it refuses rather than
silently discarding a non-object `env` value.

Also fixes the apiKeyHelper command itself: --base-url belongs to the
top-level `lite` group, so `lite auth print-token --base-url X` is rejected by
click with "No such option". Every settings file `lite up` has written carries
that malformed command, which makes the helper return nothing and every Claude
Code request lose its token. The existing tests only string-matched the
generated command, so the new tests parse it through the real CLI instead.
2026-08-19 15:32:14 -07:00
mateo-berri
a870d45a8a Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_lit_5785_vertex_regional_pricing
# Conflicts:
#	litellm/model_prices_and_context_window_backup.json
#	model_prices_and_context_window.json
2026-08-19 15:31:42 -07:00
Mateo Wang
7e247c1477
Merge pull request #37525 from BerriAI/litellm_lit_5741_replay_match_keys
feat(e2e): canonical content-based match keys for record-and-replay
2026-08-19 15:27:16 -07:00
Mateo Wang
d192ceec73
Merge pull request #37457 from BerriAI/litellm_batch_empty_line_cost
fix(batches): stop one bad output line from zeroing an entire batch's spend
2026-08-19 15:26:37 -07:00
mateo-berri
0f4c2d71fb test(files): hoist shared batch line fixture into one constant 2026-08-19 15:22:43 -07:00
mateo-berri
b39a339b7d fix(vertex_ai): apply regional endpoint uplift to cost tracking 2026-08-19 15:21:06 -07:00
mateo-berri
708ff0b910 fix(proxy): retry end-user spend updates on Postgres deadlock instead of dropping them 2026-08-19 15:20:59 -07:00
Mateo Wang
f80b5e3cbb
Merge pull request #35998 from BerriAI/litellm_fix_bedrock_adaptive_thinking_token_accounting
fix(anthropic,bedrock): report provider thinking tokens instead of classifying them as text
2026-08-19 15:14:49 -07:00
mateo-berri
31090d122e Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_infer_single_worker_redis_banner
# Conflicts:
#	litellm/proxy/proxy_server.py
2026-08-19 15:12:57 -07:00
Mateo Wang
d58b1c8558
Merge pull request #37516 from BerriAI/litellm_gemini_prompt_cache_min_tokens_4096
fix(model_prices): set prompt_cache_min_tokens=4096 for Gemini 3.5/3.6/3.7 Flash and 3.1 Pro Preview
2026-08-19 15:11:00 -07:00
mateo-berri
680e4a5736 fix(files): treat nonpositive max_batch_file_size_mb as no cap 2026-08-19 15:05:17 -07:00
mateo-berri
710ef81a80 fix(usage): keep responses usage SDK-parseable and complete streamed reasoning splits
An unknown reasoning split now falls back to reasoning_tokens=0 in the
chat-to-responses usage translation, since the OpenAI SDK requires
output_tokens_details with an int reasoning_tokens, and the streaming
chunk builder caps the tokenized reasoning estimate at completion_tokens
and fills text_tokens with the remainder
2026-08-19 14:57:19 -07:00
mateo-berri
c5194a8507 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_spend_deadlock_retry 2026-08-19 14:55:58 -07:00
mateo-berri
975e79dcef fix(e2e): make concurrent replay consumption race-free
Greptile flagged that lazy per-slug pool initialization could double-build
under concurrent replay calls, splitting consumption across a discarded
pool. Pools are now built once at ReplaySource construction and per-key
consumption is a single atomic deque pop, with a barrier-synchronized
regression test that fails 10/10 under the lazy-init mutant
2026-08-19 14:55:53 -07:00
mateo-berri
45884b9bd3 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_gemini_prompt_cache_min_tokens_4096
# Conflicts:
#	litellm/model_prices_and_context_window_backup.json
#	model_prices_and_context_window.json
2026-08-19 14:55:44 -07:00
mateo-berri
70a4f9a73a fix(search): refuse AgentCore credentials over plaintext HTTP
A trusted hostname over plain http would expose the bearer token or a
replayable SigV4 signature to network observers. Credentials now only ride
https, with localhost exempt so local MCP stubs keep working.
2026-08-19 14:50:58 -07:00
Mateo Wang
b8d5139701
Merge pull request #37473 from BerriAI/litellm_model_registry_audit_20260819
fix(model_prices): correct gemini 3.1 flash image and deepseek v4 pricing, add openai deprecation dates
2026-08-19 14:50:49 -07:00
mateo-berri
2a4598219d feat(proxy): fast-fail validation for batch input files at /v1/files 2026-08-19 14:43:22 -07:00
yassin
8ef522a2a0 fix(search): read AgentCore structuredContent results
Web-search connector 1.1.0 and later return the machine-readable results in result.structuredContent and may leave the text block as prose, which the parser dropped.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-19 21:41:57 +00:00