Commit graph

187 commits

Author SHA1 Message Date
mateo-berri
2f397fa128 fix(drop_params): honor string flags in litellm_settings and responses, and fail open on non-flag values 2026-09-07 18:06:29 -07:00
mateo-berri
9041768fb4 feat(cost_map): derive source_revision from the loaded bytes instead of a _metadata stamp
The revision an operator checks is now the git blob id of the exact bytes the process
loaded, the same id git rev-parse <commit>:model_prices_and_context_window.json prints,
so it is always present, never goes stale between bot writes, and needs no stamp in the
JSON that every PR touching the file would have to regenerate. The _metadata block, the
generated_at field, the schema and guard changes, and the bot stamping are dropped
2026-09-07 17:47:51 -07:00
mateo-berri
4cc0180eab fix(router): keep unresolved drop_params strings so DB rows and env refs survive
The drop_params validator collapsed every string it did not recognize to None. A pre-fix DB row holds the flag as ciphertext, so a partial PATCH rebuilt the deployment without it and dropped the key from the stored row, and /model/new turned an os.environ/ reference into nothing before the loader could resolve it. The validator now returns the raw value when it is not a boolean flag, the field admits strings the way timeout already does, and the flag set follows pydantic's lax bool parsing instead of a hand-rolled true/false pair
2026-09-07 17:06:34 -07:00
mateo-berri
0710231acc feat(cost_map): stamp and surface generated_at and source revision provenance
The cost map JSON now carries a top-level `_metadata` block with `generated_at` and `source_revision`, written by the two bot writers only when model data changed. The loader pops it before the map becomes `litellm.model_cost`, records it next to the fetch ETag, and `/reload/model_cost_map`, `/model/cost_map/source`, and the reload schedule status return it. The Price Data Reload card shows the stamp, the ETag, and when the pod loaded the map. The schema and the cost map guard treat `_metadata` as a non-model root key
2026-09-07 16:57:09 -07:00
tin-berri
1ae3216120
fix(router): preserve default heuristic updates (#40007) 2026-09-07 12:05:43 -07:00
mateo-berri
eca59aa90b fix(proxy): make an ambiguous config include loud, not silent
An include entry that matches both a file next to the config that declares it and
one next to the root config now warns naming both, so a config that resolves to a
different file than it used to says so instead of quietly serving other models.

Also from reviewing that change:

- an empty root object in a bucket fails the boot again instead of coming up empty
- a YAML syntax error in a bucket object logs its own line naming the object
- an include already loaded is skipped before it is read rather than after
- reading a config out of GCS builds the plain bucket client, so it needs no
  enterprise license and starts no flush loop that nothing ever cancels
2026-09-06 03:35:07 -07:00
mateo-berri
12204e5230 fix(proxy): resolve disk includes next to the config that declares them
Keep reading an include left beside the root config, with a warning naming where it
was found, so a nested include written against the old rule still boots.

Also build one S3 client per config load rather than one per included object, treat an
empty included object as an empty config instead of failing the boot, and point the
error a dropped bucket include raises at the bucket error logged with it.
2026-09-06 02:47:47 -07:00
mateo-berri
0a763bf00d fix(proxy): read a bucket config's include tree off the event loop
Reading a config from a bucket ran a blocking boto3 GET straight from the
event loop for every object in the include tree, and on GCS it built a new
bucket client per object, each one starting a flush task that never ends.

S3 reads now go through a worker thread, and one bucket client serves the
whole include tree.
2026-09-06 02:05:18 -07:00
mateo-berri
04cc8f855f fix(proxy): resolve config include directives for bucket-hosted configs
A config loaded from a GCS or S3 bucket skipped include processing entirely,
so every model, guardrail, and setting behind an `include` was silently
dropped. Both bucket types shared the same branch in `get_config`, which
never called `_process_includes`, and that helper only ever read from disk.

The merge now lives in one async helper that takes the loader as a
dependency, so disk and bucket configs share the same semantics: list values
extend, everything else overrides, nested includes are followed, and the
`include` key is stripped. Bucket entries resolve as object keys relative to
the config object's prefix, with a leading `/` meaning the bucket root, and
an include that cannot be read now raises instead of being skipped.
2026-09-06 01:03:53 -07:00
tin-berri
9fd60e4f95
feat(router): gate heuristic v1 tuning (#39952) 2026-09-05 19:24:00 -07:00
ryan-crabbe-berri
d885104125 chore: merge litellm_internal_staging into litellm_lit_4738_table_pagination
per_user_usage.tsx conflicted with the server pagination that already landed on
staging (default 50 rows, stale-response guard, tag and page-size resets). Took
the staging version and dropped this PR's now-redundant 25-row test for it

Claude-Session: https://claude.ai/code/session_01HkaXiD6gssHnx3kqu1rR8C
2026-09-05 10:08:11 -07:00
tin-berri
d0d09e5343
feat(router): meter auto-router tier and prompt customization against the auto_router license feature (#39674)
Generalizes the heuristic_v2 ceiling from #39468 into a capability table whose
records own their in-process predicate, SQL spelling and refusal wording. The
existing heuristic_v2 capability keeps its own one-router ceiling. A single
customization capability combines operator-defined tier definitions with every
operator-written part of the classifier prompt. The prompt half only applies to
classifier types that call an LLM. The shipped default prompt, classification
rubric presets, tier-label renames and tier model choices remain ungated.

Scope every enforcement point to actual complexity routers. A model-less PATCH
or legacy update now decrypts the stored model before accepting strategy-router
settings, so a regular model cannot acquire a router config or spend a license
slot. Under the existing advisory lock, the cross-pod candidate query returns
only model scalars and the count decrypts and classifies them in process; old
non-router rows carrying a capability-shaped config no longer block a real
complexity router. The signed auto_router license feature makes both ceilings
unlimited.
2026-09-05 09:51:23 -07:00
mateo-berri
4ffd2ffb25 test(proxy): parametrize the stale end-user counter case so no Final local sits in a loop 2026-09-04 18:00:40 -07:00
mateo-berri
b4fd63f621 chore(proxy): annotate the new spend-counter test locals and correct the floor comments 2026-09-04 17:53:45 -07:00
mateo-berri
89086db282 fix(proxy): floor end-user budget checks on the DB row after a reset
The reset job evicts the cached end-user object only from its own worker's
in-memory cache (plus Redis), so every other uvicorn worker and replica keeps
the pre-reset spend for up to user_api_key_cache_ttl (60s by default). Those
workers pass that stale spend as fallback_spend, and since the authoritative
floor read returned None for spend:end_user: keys, get_current_spend handed
the stale value straight back and the end user kept getting 429 after the
rollover on every worker but the one that ran the reset.

The floor read now consults LiteLLM_EndUserTable.spend for end-user counters,
the same way keys, teams, users, and orgs already read their rows. It runs only
when the shared counter sits below the cached spend (a reset or a Redis
restart) and stays behind the existing 5s in-process marker, so the normal
request path still does no DB read. Cold end-user counters keep seeding from
the cached object rather than the row, so from_db is unchanged for them.
2026-09-04 17:39:46 -07:00
ryan
7783efc3b1 chore: merge litellm_internal_staging into litellm_lit_4738_table_pagination
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-04 05:39:32 +00:00
devin-ai-integration[bot]
4a537e2c19
fix(proxy): emit SSE keepalives on queue, rag, azure passthrough, usage chat and policy enrich streams (#39273)
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-03 18:23:37 -07:00
ryan
9ba6cab889 fix(ui): make Admin UI table pagination honor the selected page size
All Models now pushes the model group, access group and wildcard filters into
/v2/model/info (new optional access_group and wildcard_only params) so the
server total_count matches the rendered rows. Request Logs defaults to 25,
uses the shared page size options and counts rendered rows in the footer.
Deleted Teams gets the shared DataTable server pagination footer instead of a
hard-coded page size of 100. Per-user usage and the remaining unbounded list
tables get paginationMode so the size selector renders.

Resolves LIT-4738

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-03 23:59:58 +00:00
tin-berri
df73c623b2
feat(router): limit heuristic_v2 auto-routers to one without the auto_router license feature (#39468)
Without the auto_router feature in the signed enterprise license a proxy may hold
one complexity router with classifier_type heuristic_v2 across config.yaml and the
DB; with it the limit is lifted. The ceiling is derived once from LicenseCheck and
handed to the Router, which refuses the extra router at registration. config.yaml
over the limit refuses to start, and /model/new, /model/update and
PATCH /model/{id}/update refuse the write with a 403 before touching the DB.
Expiry follows the existing max_users/max_teams pattern: judged when the
license is verified, not on every call, and a verify that rejects the license
(expired or unreadable) leaves no signed payload behind. The rollback after a
failed upsert re-admits state that was already serving, so it is exempt from the
ceiling: an edit that fails, including one refused by a ceiling that has since
tightened, leaves the router serving its previous configuration.
A write that leaves a row on heuristic_v2 under a limited license runs in one
transaction that takes a Postgres advisory lock before counting the DB rows plus
this proxy's config.yaml routers, so concurrent writes on any pod cannot both
claim the sole slot and no surplus row is ever persisted.
Only the row insert runs under that lock: the team model bookkeeping, which
needs a second pool connection, runs after the transaction has committed.
PATCH /model/{id}/update follows the same order as create: the row is written
through the slot first and the team's model list is updated only afterwards, so
a refused write leaves the team as it was.
The slot transaction bypasses the repository's publish-on-write, so it
publishes the config change once after commit, as delete_team_models does.
2026-09-03 13:39:58 -07:00
michelligabriele
f980b94923
fix(proxy): release the health check save window lock on failure or cancel 2026-09-03 12:36:04 +02:00
michelligabriele
eddb29d90e
fix(proxy): dedup latest health checks in SQL and gate the DB save per window 2026-09-03 11:33:44 +02:00
Yassin Kortam
25991fe78a
feat(auth): enforce configurable password policy and SSO-only login (#39381)
Adds a configurable password-strength policy (default: min 12 chars,
upper/lower/number/special, all individually toggleable, floored at 8
so a misconfigured minimum cannot disable the length check, and
unicode-aware so an accented letter cannot satisfy the special-
character requirement) enforced on every path that sets a local
user's password: /user/update, /user/bulk_update, and the invitation
onboarding claim flow.

Adds general_settings.disable_password_login_when_sso_enabled, which
rejects username/password login on /login, /v2/login and /v3/login
(including the UI_USERNAME/UI_PASSWORD admin fallback) once ANY
configured SSO provider is FULLY ready: every companion secret/
endpoint an OAuth provider needs, checked independently per provider
so a stray leftover client id for an unused provider can't mask a
different, fully configured one; and for SAML, the optional
python3-saml runtime being importable, checked without letting a
fully-missing package's ModuleNotFoundError take down password login
itself. SSO becomes the enforced boundary for interactive UI access
without an incomplete, mixed, or half-installed SSO setup locking
every admin out or breaking login outright. Master-key API access is
untouched, and unsetting the setting plus a restart restores password
login as the documented recovery path.
2026-09-02 14:28:13 -07:00
Mateo Wang
a43228ef72
Merge pull request #39249 from BerriAI/litellm_router_settings_reject_unknown_keys
fix: apply optional_pre_call_checks and reject unsupported router settings on /config/update
2026-09-02 11:23:22 -07:00
michelligabriele
d5270890c4
fix(proxy): report the widest window across a model group, not the first deployment's 2026-09-02 12:03:36 +02:00
michelligabriele
c60c60e6fe
fix(proxy): resolve /v1/models limits from the deployment, not the alias 2026-09-02 10:59:18 +02:00
mateo
385957e830 fix: reject constructor-managed router settings
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-02 07:49:25 +00:00
mateo
974b331a4d fix: accept persistable router settings
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-02 06:15:52 +00:00
mateo
cb511f70cc fix: preserve config update authorization order
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-02 02:20:51 +00:00
mateo
0608f0a00f fix: reject unknown runtime router settings
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-02 02:17:02 +00:00
mateo-berri
fcd9052179 feat(proxy): honor model_info.display_name in the Anthropic-shaped /v1/models listing 2026-09-01 17:10:52 -07:00
Mateo Wang
19ca4cd4a9
Merge branch 'litellm_internal_staging' into litellm_fix_supported_openai_params_router_alias 2026-09-01 12:57:36 -07:00
mateo-berri
ec02c9a6d2 fix(router): bare authenticating-provider names declare nothing 2026-08-31 13:34:14 -07:00
mateo-berri
779b3010d4 fix(proxy): never run OAuth device flows when resolving model names
Resolving github_copilot/chatgpt names through get_llm_provider runs the
provider's OAuth device flow synchronously on the event loop. Adopt the
declared provider in PatternMatchRouter.get_pattern, which the auth
layer's zero-cost budget check walks on every request against wildcard
routers, and in /utils/supported_openai_params.
2026-08-31 12:06:35 -07:00
mateo-berri
11a7471902 refactor(proxy): resolve supported_openai_params aliases via Router.resolved_litellm_models 2026-08-31 11:08:31 -07:00
Devin AI
2d01397e4d test: pin llm_router in supported_openai_params tests
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-31 15:03:52 +00:00
Devin AI
1bf3ab5388 fix(proxy): resolve router model aliases in /utils/supported_openai_params
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-31 14:45:31 +00:00
mateo-berri
99a6dd02af fix(proxy): narrow audio_speech response before reading upstream content-type 2026-08-30 12:46:11 -07:00
mateo-berri
2420e3f202 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_gemini_tts_container 2026-08-30 10:02:43 -07:00
mateo-berri
b67b44bdaa fix(proxy): map audio_speech errors to their status codes instead of a blanket 500 2026-08-29 21:10:14 -07:00
ryan-crabbe-berri
e257383cc6
Merge pull request #38249 from BerriAI/litellm_fix_empty_user_models_visibility
fix(proxy): list all non-team models for users with an empty model list
2026-08-29 17:04:28 -07:00
ryan-crabbe-berri
6f8a3b8661 fix(proxy): scope model listing direct access to the calling key's grant
Direct access on /model/info was read from the user record alone, so an
unrestricted user calling with a key limited to a few models saw every non-team
deployment, including ones the key gets a 403 on. Resolve the key's grant the
same way and intersect the two.

Resolving a grant now also expands access groups, which the key path needs and
the user path was missing.

Claude-Session: https://claude.ai/code/session_01XL7LBFEew4wi8gphVCDq6n
2026-08-29 16:54:02 -07:00
mateo-berri
4e2574ce08 fix(proxy): match /v1/audio/speech content-type to the returned audio format 2026-08-29 13:44:43 -07:00
ryan-crabbe-berri
e8994e8ce1 Merge branch 'litellm_window_spend_writer' into litellm_window_spend_reader 2026-08-29 11:49:42 -07:00
yuneng-jiang
fa25ff2a2e
Merge pull request #38626 from BerriAI/litellm_ui_model_links_team_key_info
feat(ui): link team and key model chips to the models page filtered to that group
2026-08-29 11:26:15 -07:00
ryan-crabbe-berri
08118e6246 fix(proxy): let the exact model= filter match team BYOK public names
Team-scoped deployments keep the internal model_name_{team_id}_{uuid} routing key and expose the public name in model_info.team_public_model_name. The dashboard links team model chips with the public name, so the exact filter now matches either name via the existing helper.
2026-08-29 10:52:15 -07:00
mateo-berri
1c6cb6dde2 fix(model_info): map supports_parallel_function_calling into ModelInfo
The registry key was never copied into ModelInfo, so /v1/model/info reported
null for every model, /model_group/info reported false for every group, and
litellm.supports_parallel_function_calling() returned False for provider-prefixed
entries that declare true. Copy it like every other capability flag and pin the
three surfaces with regression tests.

Resolves LIT-6340
2026-08-28 12:09:48 -07:00
mateo-berri
24d226c6c2 chore(token_counter): drop docstrings and test prose that restated the count_tokens branches 2026-08-28 06:28:21 -07:00
mateo-berri
83ab87091b fix(proxy): only attach tools to the count_tokens fallback when counting messages 2026-08-28 06:10:30 -07:00
mateo-berri
70ba0bb973 fix(proxy): count tools, system, and Anthropic document blocks in the count_tokens fallback 2026-08-28 05:41:38 -07:00
tin-berri
ec94a1f82a
fix(router): reject complexity-router settings written outside complexity_router_config (#38570)
A complexity-router setting placed beside complexity_router_config, or inside a
tier entry's litellm_params, is read by nobody: the router loads its settings only
from litellm_params.complexity_router_config. It does not stay inert. The
alias-marker forwarding and the per-tier param spread carry every unrecognized key
onto the outbound request, and all_litellm_params only knows the outer names, so
the key reaches the provider as an unknown body field and every call through that
model group fails with an error naming an internal config key.

Guard the whole set, derived from ComplexityRouterConfig.model_fields so a field
added later is covered, and scoped to complexity-router deployments because the
names only mean this there (embedding_model is a legitimate flat param on an
s3_vectors vector store). Scope is read from the same merged field view the naming
check is judged on, so a router named only by its default model is in scope and a
field added to the required-field table is covered without another edit. The write
endpoints reject with a 400 naming the keys and where they belong, config.yaml
refuses to start for the same reason max_agentic_loops does, and a tier entry is
judged by the config model itself.

An already-stored deployment keeps loading, so an upgrade cannot take a running
gateway down over a row that was written before the gate existed.
2026-08-27 17:04:49 -07:00