Commit graph

42200 commits

Author SHA1 Message Date
Devin AI
42b97bc0a5 Merge branch 'litellm_internal_staging' into litellm_generic_guardrail_api_ui_fields
Some checks failed
LiteLLM Rust / rustfmt, clippy, test (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
2026-08-05 19:37:08 +00:00
Mateo Wang
332ec6c17a
Merge pull request #35926 from BerriAI/litellm_remove_types_ruff_exclusion
chore(lint): remove litellm/types from the ruff lint exclusion
2026-08-05 12:35:02 -07:00
Yassin Kortam
2a9843e649
fix(proxy): keep the connected DB client when a startup health check fails (#35837)
`_setup_prisma_client` ran `connect()`, then a `SELECT 1` health check, then
armed the DB health watchdog. Any failure fell into one handler that, with
`allow_requests_on_db_unavailable` set, swallowed the error and returned None,
which the caller assigns to the module-level `prisma_client`. A single
transient timeout on that health check therefore discarded a client that had
already connected, for the life of the process, and skipped the watchdog that
exists to reconnect it.

The watchdog now starts before the health check, and a swallowed post-connect
failure returns the connected client instead of None. A client whose
`connect()` failed is still discarded, and startup still hard-fails when
`allow_requests_on_db_unavailable` is not set.

The same check also misreported its own failure. `health_check()` labelled its
error `disconnect()`, a copy-paste from the real `disconnect()` below it, so
grepping the logs for the health check turned up nothing and read as "the check
never ran". Both it and the sibling `connect()` failure reported through
`print_verbose`, which reaches `verbose_proxy_logger.debug` and otherwise prints
only under the deprecated `litellm.set_verbose`, leaving a startup-blocking
database fault invisible at the verbosity operators actually run. Both now log
at warning under their own names. The proxy logger's handler carries the secret
redaction filter, so a connection string in the exception text is redacted
exactly as it was on the old print path.
2026-08-05 12:27:49 -07:00
Mateo Wang
54f83b2614
Merge pull request #35870 from BerriAI/litellm_reland_evicted_client_closer
fix(caching): re-land evicted LLM client closing (#35492) atop self-healing handlers
2026-08-05 12:22:12 -07:00
mateo-berri
6c76f5f9c6 chore(lint): clear grandfathered over-limit lint drift and ratchet budgets down
Every ruff-strict rule that sat above its budget limit (FURB188, RUF022,
SIM118, UP007, UP032, UP037) is now at zero, LIT001 and LIT006 are back
under their ceilings, and the freed headroom is ratcheted out of
ruff-strict-budget.json, type-discipline-budget.json, and
basedpyright-code-budget.json so the gates take the fast path again
2026-08-05 12:18:13 -07:00
Yassin Kortam
0b8c58735d
fix(ci): make the env-key doc gate see get_secret_bool reads (#35833)
The gate only matched os.getenv(, litellm.get_secret( and
litellm.get_secret_str(, so a bare get_secret_bool("X") matched nothing and
the key bypassed the documentation requirement entirely. Add a fourth pattern
for get_secret_bool, with or without the litellm. prefix, and a negative
lookbehind so an unrelated receiver's .get_secret*( call is not mistaken for
an env var read.

Extraction and table parsing move into functions behind a __main__ guard so
the patterns can be unit tested; the script is still invoked exactly the same
way by CI.

This surfaces 13 keys the gate never checked, 8 of which have no reference
row yet.
2026-08-05 12:03:15 -07:00
tin-berri
d3d30353aa
refactor(ui): remove the three dashboard lint-budget violations added by #35893 (#35960)
PR #35929 zeroed the eslint budget headroom while #35893 added UI code in parallel, so staging went over budget by one complexity violation and two no-large-inline-object-arg violations, failing frontend-lint on every UI-touching PR until #35964 reverted the ratchet. This removes the three violations at the source so the budgets can ratchet back down: the submit-blocked-reason chain in add_auto_router_tab moves to a module-level helper, taking the component arrow from complexity 21 to 18, and the two four-property object literals in build_complexity_router_config.test.ts move into named variables. No behavior change; the touched suites pass (101 tests)
2026-08-05 11:51:24 -07:00
ryan-crabbe-berri
2792887e47
fix(proxy): give proxy_admin_viewer read parity with proxy_admin (#35851)
* fix(proxy): give proxy_admin_viewer read parity with proxy_admin

Route-level checks already default-allow management GETs for the viewer
role, but ~15 handlers compared user_role to PROXY_ADMIN only, dropping
viewers into regular-user scoping (/key/list, /user/info, /model/info,
guardrails, prompts, agents, memory, workflows, MCP catalog, coordination
redis settings, credential migration check, enterprise projects). Swap
those read paths to user_api_key_has_admin_view; write gates unchanged.

The dashboard now presents the viewer session as Admin for all gating
(effectiveSessionRole) so every page fetches with admin visibility, with
userRoleLabel/isViewOnly preserving the account-menu label and the
playground cost guard. The server remains the write authority.

* refactor(agents): remove side-effectful health_check param from GET /v1/agents

Addresses a security review finding on the admin viewer read parity change:
listing agents with health_check=true made the proxy issue a server-side GET
to every agent URL, so a read-scoped caller could trigger request fan-out
beyond their object permissions. The list endpoint is now a pure read for
every role.

Removes the query param, the URL probing helper and its timeouts, the
AgentHealthCheck httpx provider tag, and the dashboard's Health Check
toggle. Requests still passing health_check=true get the full list back
with the param ignored.

* fix(proxy): keep credential encryption check proxy_admin only

The residual scan behind GET /credentials/migrate-encryption/check loads
every model, credential, MCP, team, and verification-token row and runs a
decryption attempt on each stored value. Extending it to proxy_admin_viewer
let a read-only account repeatedly trigger deployment-wide scans, so the
route keeps its original full-admin gate.

* fix(agents): restore health_check, keep list fast path proxy_admin only

Restores the agent health_check feature exactly as before this PR: the
query param, the URL probing helper, the httpx provider tag, and the
dashboard toggle all return, so existing callers keep the filtering
contract. The viewer expansion is instead reverted at its source: the
GET /v1/agents admin fast path stays PROXY_ADMIN only, so a
proxy_admin_viewer goes through the object-permission scoped branch as
before and cannot fan out health checks beyond their allowlist. The
viewer read of a single agent stays viewer-inclusive since it has no
side effects.
2026-08-05 18:33:55 +00:00
mateo-berri
d59135b5ea Merge branch 'litellm_internal_staging' into litellm_remove_types_ruff_exclusion
Import-block conflicts against #35962's unused-import removals resolve
to this branch's side: its modernization already removed every typing
import staging trimmed, plus the usages
2026-08-05 11:10:34 -07:00
mateo-berri
ebc31f7e06 fix(caching): clear strict-lint budget breaches in re-landed closer code 2026-08-05 11:08:19 -07:00
Mateo Wang
c19896029a
Merge pull request #35964 from BerriAI/revert-35929-litellm_zero_dashboard_eslint_headroom
chore(ui): don't zero out stale headroom except no-console
2026-08-05 11:06:16 -07:00
mateo-berri
1a693a3014 fix(types): resolve the four type-discipline additions surfaced by the staging merge 2026-08-05 11:05:53 -07:00
mateo-berri
51c54b56ac Merge branch 'litellm_internal_staging' into litellm_remove_types_ruff_exclusion
Resolve litellm/types/google_genai/main.py and litellm/types/utils.py by
keeping this branch's modernized annotations on top of staging's removal
of inert type: ignore comments. Rebuild ruff-strict-budget.json from
measured merged-tree counts where de-excluding litellm/types adds
violations, keeping the stricter of the two sides' limits everywhere
else so no rule gains headroom. Fix the four type-discipline additions
the merge surfaced: freeze GEMINI_1_5_ACCEPTED_FILE_TYPES, drop a
callback_args parameter rebind in guardrails, and give the two remaining
mutations reasoned suppressions
2026-08-05 11:05:33 -07:00
Mateo Wang
45ed654603
Merge pull request #35962 from BerriAI/litellm_fix_basedpyright_over_limits
fix(lint): bring basedpyright rule counts back under their budget limits
2026-08-05 11:03:53 -07:00
Mateo Wang
85aad29885
chore: make no-console max 12 2026-08-05 10:45:56 -07:00
Mateo Wang
64f83a23e1
Revert "chore(ui): zero stale headroom on local dashboard eslint budgets" 2026-08-05 10:44:02 -07:00
mateo-berri
469d5126f6 fix(lint): bring basedpyright rule counts back under their budget limits 2026-08-05 10:23:02 -07:00
mateo-berri
824608c42b Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_reland_evicted_client_closer
# Conflicts:
#	litellm/llms/azure/common_utils.py
2026-08-05 10:16:37 -07:00
Yassin Kortam
0659738b3e
fix(migrations): recover from an interrupted Prisma toolchain install (#35832)
The Prisma CLI is a Node program that installs a private Node runtime on its
first invocation. That one-time install shared the 60s budget that bounds each
migration command, so on a slow or cold machine it was killed before it could
finish. Prisma then decides whether to reinstall by testing the cache
directory for existence alone, and a killed install leaves that directory
behind, so every later attempt skipped the install and failed on a node binary
that was never written. The existing four-attempt retry loop could not help:
each attempt hit the same missing binary, which turned a slow start into a
container that never migrated again.

Migrations now prepare the toolchain as its own step under its own budget, and
a cache directory that exists without a node binary is deleted first so an
interrupted install reinstalls instead of persisting. Both budgets are
overridable, LITELLM_PRISMA_BOOTSTRAP_TIMEOUT for the install and
LITELLM_PRISMA_COMMAND_TIMEOUT for each Prisma command, and every previously
hardcoded timeout now goes through one helper rather than thirteen literals.
The per-command default stays at 60s.

An override is only honoured when it parses as a finite positive number.
Infinity and NaN parse as floats and survive a plain positivity check, and
subprocess treats either as no deadline at all, so a value like `inf` or a
fat-fingered `1e400` would have silently disabled the timeout it was meant to
configure.
2026-08-05 10:14:46 -07:00
Yassin Kortam
54fb717de1
fix(router): redact fallback tracebacks at the call site and cover the sync deferred stream (#35843)
Three follow-ups surfaced while merging current staging into this branch.

`exc_info=True` at both fallback-failure log sites handed a live exception to
the logging machinery. SecretRedactionFilter rewrites `record.exc_text`, but
`record.exc_info` stays an exception object no filter can reach, so a handler
that renders it itself (Datadog and OTel log bridges do) received the
unredacted provider key. Both sites now pass `redact_string(traceback.format_exc())`
as a `%s` arg, keeping staging's lazy-logging form. The existing test only
asserted on `exc_text`, so it passed under the bug; it now renders `exc_info`
the way a bridge handler would and covers every record the call emits.

The eager deferred-stream fetch existed only on the async path. Vertex and
Bedrock build the same `completion_stream=None` plus `make_call` wrapper on
their sync branches, so `Router.completion(stream=True)` still surfaced the
provider error on first iteration, outside `_completion`'s except block, and
never reached the fallback chain. `_completion` now calls `fetch_sync_stream()`
under the same guard `_acompletion` uses.

The first of the three header-strip passes in the proxy error path was dead:
only the custom-header update and the response-headers hook run before the
second pass re-filters everything. Collapsed to one `safe_headers` binding.
2026-08-05 10:06:52 -07:00
Mateo Wang
8fe9809a4b
Merge pull request #35365 from rimysore/fix-managed-files-null-object
fix(managed-files): skip rows without file objects
2026-08-05 10:01:42 -07:00
Mateo Wang
520e38e232
Merge pull request #35929 from BerriAI/litellm_zero_dashboard_eslint_headroom
chore(ui): zero stale headroom on local dashboard eslint budgets
2026-08-05 09:47:16 -07:00
Mateo Wang
0b8c7f29ed
Merge pull request #35927 from BerriAI/litellm_zero_more_local_basedpyright_rules
chore(lint): zero out seven more purely local basedpyright rules
2026-08-05 09:46:21 -07:00
Mateo Wang
551f395dbf
Merge pull request #35928 from BerriAI/litellm_zero_ruff_lit_headroom
chore(lint): zero stale ruff and LIT headroom and strip inert type: ignore comments
2026-08-05 09:46:05 -07:00
Abhimanyu Kapur
cc1c7d6101
feat(complexity_router): let operators rename the four complexity tiers (#35893)
* feat(complexity_router): let operators rename the four complexity tiers

Adds an optional tier_labels map to complexity_router_config so a deployment can
put its own vocabulary on the four tiers, e.g. Cheap / Standard / Premium / Deep,
instead of reading SIMPLE / MEDIUM / COMPLEX / REASONING in its dashboard, its
spend logs, and the rubric the LLM classifier reasons with.

Labels are display-only. Every config key stays canonical, so tiers,
keyword_tier_rules[].tier, and tier_boundaries are written exactly as they are
without labels, and partial maps are fine with unlisted tiers keeping their
default name. A validator rejects blank labels, two tiers sharing a label, and a
label that is another tier's canonical name, since any of those would make a log
row or a rubric line ambiguous. That validator runs on the /model/new and
/model/update write path already, so an ambiguous config gets a 400 rather than
being stored for the router to refuse later.

Under the default heuristic scorer the names are cosmetic: the scorer maps a
weighted score to a rung and never reads a tier name, verified by running the
eval corpus with and without a rename and getting identical tier and identical
score on all 29 cases. Under classifier_type: llm the labels are the names in the
rubric and the values the classifier must return, so the response format's enum
is now built from the configured labels and a reply is resolved back to its tier
against labels first, then canonical names, case-insensitively. An unresolvable
reply degrades to the heuristic on the existing fallback path. A test pins the
generated schema for an unrenamed deployment as equal to the shipped
TierClassification schema, so the wire shape can't drift.

Spend logs keep routing_decision.tier canonical so rows from before and after a
rename stay comparable, and gain routing_decision.tier_label on the tiers that
were renamed.

* refactor(complexity_router): drop added comments and the Counter construction

Review feedback: the repository guide bans new comments, so the explanatory
comments and the appended docstring paragraphs this branch added come back out.
One-line docstrings stay in complexity_router.py, matching that file's own
convention.

The duplicate-label check no longer builds a Counter, which the mutable-collection
budget counts, and the error text drops its list() reprs for joined strings. The
labels are stripped in tier_label() now rather than by rewriting the field in the
validator, so the stored config keeps exactly what the operator wrote.

schema.d.ts is regenerated: ComplexityRouterConfig is exposed in the OpenAPI spec,
so tier_labels surfaces there.

* fix(ui): carry tier_labels through the auto-router preset prefill

buildPresetPrefill maps every payload key onto form state, but the tier_labels
key added by this branch had no line, so a preset shipping labels would apply
its tiers and silently drop its names.
2026-08-05 09:42:57 -07:00
Mateo Wang
7900e1fc79
Merge pull request #35251 from BerriAI/litellm_fix_codecov_oidc_flake 2026-08-05 09:26:43 -07:00
mateo-berri
f270b53144 Merge branch 'litellm_internal_staging' into fix-managed-files-null-object 2026-08-05 03:05:32 -07:00
mateo-berri
20d297a151 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_zero_ruff_lit_headroom
# Conflicts:
#	litellm/llms/anthropic/experimental_pass_through/responses_adapters/streaming_iterator.py
2026-08-05 02:53:06 -07:00
Mateo Wang
b735578822
Merge pull request #35367 from rimysore/fix-responses-batch-usage
fix(batches): account for Responses API usage
2026-08-05 02:51:35 -07:00
mateo-berri
66373eb25f fix(types): log AdapterCompletionStreamWrapper errors lazily 2026-08-05 02:49:35 -07:00
mateo-berri
83008a916d Merge remote-tracking branch 'origin/litellm_internal_staging' into fix-responses-batch-usage
# Conflicts:
#	litellm/batches/batch_utils.py
2026-08-05 02:38:02 -07:00
mateo-berri
338e411103 chore(lint): strip inert type: ignore comments and zero LIT009, LIT010, LIT011 headroom 2026-08-05 02:37:24 -07:00
Mateo Wang
64f4bedde1
Merge pull request #34957 from BerriAI/litellm_gpt56_cache_token_pricing
fix(cost): bill gpt-5.6 prompt cache reads at the cache read rate
2026-08-05 02:30:41 -07:00
Mateo Wang
71f7fad16a
Merge pull request #34812 from BerriAI/litellm_fix_openai_cache_token_details_loss
fix(cost_tracking): keep OpenAI prompt cache token details through usage reassembly
2026-08-05 02:21:49 -07:00
mateo-berri
0c50661307 chore(lint): re-tighten reportPrivateUsage and reportDeprecated to post-merge counts 2026-08-05 02:15:42 -07:00
mateo-berri
d4611a1253 Merge origin/litellm_internal_staging 2026-08-05 02:12:38 -07:00
mateo-berri
69a8043667 chore: keep base field ordering for service tier cache write costs 2026-08-05 02:11:05 -07:00
mateo-berri
a9902fcdb5 fix(streaming): carry Anthropic cache-creation TTL split through fallback usage reassembly 2026-08-05 02:06:52 -07:00
mateo-berri
e8a80b9883 docs(anthropic): state why usage-shape detection requires a cache key, pin Responses-shape rejection 2026-08-05 02:01:53 -07:00
mateo-berri
bd04520d98 Merge branch 'litellm_internal_staging' into litellm_gpt56_cache_token_pricing 2026-08-05 01:51:07 -07:00
mateo-berri
b4b5c8f0a1 chore(ui): zero stale headroom on local dashboard eslint budgets 2026-08-05 01:45:02 -07:00
mateo-berri
cdfefd7f41 chore(lint): zero stale headroom on purely local ruff and LIT rules 2026-08-05 01:40:30 -07:00
mateo-berri
021b52527b chore(lint): zero out seven more purely local basedpyright rules 2026-08-05 01:31:09 -07:00
mateo-berri
74f1b934be chore(ui): format CacheLeakageCard with prettier 2026-08-05 01:23:54 -07:00
mateo-berri
7288247682 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_openai_cache_token_details_loss
# Conflicts:
#	litellm/litellm_core_utils/llm_cost_calc/utils.py
#	litellm/litellm_core_utils/streaming_chunk_builder_utils.py
#	litellm/litellm_core_utils/streaming_handler.py
#	tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py
2026-08-05 01:23:54 -07:00
mateo-berri
4e32a8bf6a chore(lint): remove litellm/types from the ruff lint exclusion
ruff.toml has excluded litellm/types/* since 2024, so no lint rule ever ran
on the types tree. Remove the exclusion, apply ruff --fix and ruff format
across litellm/types, and hand-fix what autofix cannot reach so the
pyupgrade budgets stay at zero: implicit type aliases converted to PEP 604
unions, RootModel[Union[...]] bases, duplicate imports, and a stray print.

Load-bearing import X as X re-exports deleted by preview-mode F401 are
restored, and the six star-imported hub modules keep their re-export
surface via per-file F401 ignores. Star-import consumers that silently
relied on typing names leaking from those hubs are modernized to builtin
generics and PEP 604 unions.

Runtime annotation introspection that only recognized typing.Union is
taught types.UnionType (guardrail UI field schemas, volcengine response
fill), with regression tests for both. Strict budget limits for the rules
the types tree now trips are raised to exact measured totals, so any
net-new violation still fails the gate
2026-08-05 01:10:15 -07:00
Mateo Wang
732bba00df
Merge pull request #35862 from BerriAI/litellm_self_heal_evicted_httpx_clients
fix(http_handler): self-heal handler clients closed after cache eviction
2026-08-04 23:55:53 -07:00
mateo-berri
c11ea9694a Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_self_heal_evicted_httpx_clients 2026-08-04 22:52:06 -07:00
mateo-berri
34f87a1a91 fix(async_client_cleanup): stop cleanup from resurrecting healed clients 2026-08-04 22:43:40 -07:00
tin-berri
4fcaf7d736
feat(spend): derive a default auto-router savings baseline from the hardest tier (#35907)
* feat(spend): derive a default auto-router savings baseline from the hardest tier

The savings driver shipped off by default: unless an operator names
litellm_settings.autorouter_savings_baseline_model, every auto-routed request
records $0.00 and the dashboard card never populates. Nobody discovers a knob
whose feature they have never seen work, so the default has to come from
somewhere the proxy already knows.

The router's own tier ladder is that place. Without a router a deployment runs
one model that can carry the hardest request it will see, so the derived
baseline is the priciest model in the hardest configured tier, REASONING when
present, otherwise the most severe tier the router actually defines. A cheap
tier is a choice the router made, not a ceiling it was bounded by.

An earlier draft of #35521 derived this per request and was deleted for it:
ranking candidates against the request that ran meant reading the request, and
every input shape it could take produced its own review finding. This
derivation is ranked against one fixed reference request instead, a cache-heavy
shape matching real auto-routed traffic, so it never reads the request at all.
Candidates still resolve through the router's deployments, so Azure base_model
and per-deployment pricing overrides rank correctly.

The deciding router records the result on its routing_decision, because one
model name can carry several tag-scoped routers with different tier ladders and
only the deciding instance knows which of them routed the request. The spend
writer's precedence is: configured baseline, then the recorded one, then off.
When the setting is present the router skips deriving entirely rather than
pricing candidates per decision only to be ignored.

Resolution never raises; an unresolvable baseline zeroes the driver instead of
failing a live request. Rows queued by a pod on the previous release carry no
recorded baseline and fall back to the configured setting, exactly as today.

The schema.d.ts regeneration also picks up the reminder_markers field that
UI-19232 (#35874) added without regenerating, so one hunk there is inherited
staleness rather than part of this change.

* fix(spend): cache the derived baseline, price it by deployment, keep it out of the routing preview

Three review findings on the derived baseline, addressed together because they
all sit on the same value's path from derivation to consumer.

Derivation walked and priced the hardest tier's whole pool inside a property
read on every routing decision, unbounded by pool size. The router now caches
the result per instance with a 30 second TTL, None results included, so the
hot path is a clock compare and a deployment edit still lands within a window
no operator watches closer than.

Ranking used each deployment's effective pricing but recorded only the model
name, so the spend writer priced the winning baseline at its public rate: a
hardest tier whose deployment carries a negotiated rate produced materially
wrong savings. The decision now also records savings_baseline_deployment_id
and the writer resolves it through Router.get_deployment_model_info, exactly
as the selected arm already does. The id is ignored whenever the configured
setting overrides the recorded baseline, since the setting names a model, not
a deployment.

/auto_router/test_routing returns the routing decision verbatim to team admins
while only authorizing the classifier and embedding models, so a derived
baseline would resolve another team's model-group alias into its backend
provider/model mapping and hand it to a caller never authorized for it. The
preview's throwaway router is built with derive_savings_baseline=False; its
decisions are never spend-tracked, so nothing is lost, and a source-pinning
test keeps the flag on the endpoint.

Also strips the explanatory comments this PR had added.

* refactor(spend): pin the derived baseline per router instance instead of a TTL

Creating or editing a router already rebuilds its ComplexityRouter instance,
through unregister and re-add on upsert and through the registry reset on a
full model_list load, so a value derived once per instance refreshes on
exactly the flows that can change it. That makes the TTL a solution to a
problem the rebuild lifecycle already solves, and it goes.

Derivation stays deferred to first use rather than running in __init__: during
a config load this router can be constructed before the deployments its tiers
name, and a baseline pinned at that moment would be empty for the process
lifetime.

The one behavior the TTL had that the pin does not: editing a tier deployment
without touching the router itself refreshed the baseline within a window.
That edit path rebuilds only the edited deployment's own strategies, so the
pin holds the old answer until the router is next saved or the config next
loads. A stale deployment id degrades to public-rate pricing rather than
failing, which is where every other unresolvable baseline already lands.
2026-08-04 22:36:45 -07:00