Commit graph

18968 commits

Author SHA1 Message Date
ryan-crabbe-berri
ac47c95db9 fix(proxy): re-read UI settings on every config reload
The persisted UI settings were read once at startup, so a proxy admin
flipping a runtime flag through PATCH /update/ui_settings only changed the
pod that served the request. Every other pod kept serving the old value
until it restarted.

add_deployment, the reload the scheduler runs every 30s, now re-reads the
row and applies the runtime flags before it takes the model reconcile lock,
so a change made through one pod reaches the rest within one reload
interval. The startup hook and the two settings endpoints share that helper
instead of each repeating the flag copy.

Claude-Session: https://claude.ai/code/session_018PUCupsaarVLJy4iDFx256
2026-09-16 15:38:06 -07:00
ryan-crabbe-berri
9b77b5c2cb feat(proxy): let proxy admins choose which team fields team admins may edit
Team admins could never reach POST /team/update: the route gate answered
401 before the handler's team-admin branch ran. This moves /team/update
into the self-managed routes, resolves proxy admin, org admin or team
admin inside the handler, and filters team admins through a new
proxy-wide UI setting, team_admin_editable_team_fields. The setting is
an allow-list of team fields. Empty means team admins cannot edit team
settings and get a 403 pointing at the proxy admin, and changing a field
outside the list fails 403 naming that field. Only values that differ
from what is stored count, since the dashboard resends the whole form.

The registry of fields the setting accepts ships empty on purpose. Each
field lands in its own follow-up PR with its value diff and dashboard
wiring. The Admin UI gains a "Team admin editable fields" section under
Settings > UI and a toast on the team page while editing is disabled.

Refs LIT-5722

Claude-Session: https://claude.ai/code/session_01A6SkwJdfZUmkzfUkrEkqX8
2026-09-16 15:38:06 -07:00
yucheng
62b69294c1 test(utils): build the rewritten hook response without in-place mutation
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-16 22:34:52 +00:00
yucheng
f3e05d1d13 fix(otel): budget indexed messages from the tracer's own span limits and skip already-mapped error keys
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-16 22:34:02 +00:00
Yuneng Jiang
99545b5f26
Merge remote-tracking branch 'origin/main' into litellm_/buildkite-litellm-e2e-setup-ff714d 2026-09-16 15:15:22 -07:00
yucheng
934baa7441 fix(utils): swap converted stream iterator in place instead of rewrapping
Keeps the original CustomStreamWrapper so response headers and the
correlation-context cleanup in __del__ are untouched when a deployment
hook rewrites the converted response. Covers the early-return branches
for real provider streams and unmapped call types

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-16 22:14:58 +00:00
Yassin Kortam
a6e87526f8
Merge pull request #40843 from elifozdamar/litellm_fix_nonstream_parallel_slot_release
fix(proxy): release completed max-parallel slots promptly
2026-09-16 15:13:59 -07:00
mateo-berri
ea5887d4bb test: cover proxy_admin_viewer sessions in the model_group/info admin regression test 2026-09-16 15:10:48 -07:00
mateo-berri
167edf2769 fix(proxy): read the database user row only in the credential mint
The token exchange mint keeps reading the user row from the database, since JWT auth caches the user it creates before adding it to the JWT's team and a mint off that cached row refused the first exchange for a new user. Introspection and the refresh revalidation go back to the cache read, so a resource server calling /introspect per request pays no database read.
2026-09-16 15:10:03 -07:00
mateo-berri
d5570d04d9 fix(logging): keep a log extra only after inspecting every value it holds
The filter kept an extra as its original object whenever the plain and
scrubbed safe_dumps renderings matched, but safe_dumps skips what it cannot
render (non-string dict keys, anything past its depth limit, fields a repr
hides), so a secret in those places rode the untouched object past the
litellm_redacted stamp. The filter now walks JSON-native shapes itself
(strings, scalars, string-keyed dicts, lists and tuples) and keeps the
original only when every value it holds comes back unchanged from the
redactor; anything else is handed to safe_dumps and the record carries the
scrubbed JSON shape the formatter would have rendered
2026-09-16 15:07:22 -07:00
Yujong Lee
a84f68b6e3 refactor(rust_bridge): give chat completions, messages and responses the ocr dispatch shape
Each route now has litellm/rust_bridge/<route>/{entrypoints,callbacks}.py and a
public dispatch module (litellm/chat_completions/dispatch.py,
litellm/responses/dispatch.py, litellm/messages/dispatch.py) that binds the
public call to the legacy Python signature, builds a frozen request, and asks
the runtime to pick Rust or Python from the catalog. The legacy implementations
stay in litellm/main.py, litellm/responses/main.py and the anthropic messages
handler, and litellm/__init__.py re-exports the dispatch names over them the
same way it already does for ocr

The per-handler shims in rust_bridge/chat_completions/native.py and
rust_bridge/messages/native.py are removed along with their call sites in the
anthropic and bedrock chat handlers and the http handler. The exception
mapping that every callbacks module repeated moves to rust_bridge/failures.py
and the signature binding helpers to rust_bridge/public_call.py
2026-09-16 15:02:12 -07:00
Yuneng Jiang
76c0f8db1d
chore(e2e): report the key components behind a mount that never converges
Builds 232 and 233 held the Bedrock hit rate at 9% with the Claude Code
driver already sending byte-identical requests and headers, so something
between the proxy's ingress and the upstream still moves per build and
the flat key cannot say what.

Emit a digest per key component next to the counters: the test id, the
method, the URL, each keyed header, the whole body, and one digest per
top-level JSON body field. Values are digested, so no payload or
credential reaches the artifact. Diffing two builds' artifacts names the
field that moved.

Diagnostic, to be removed once it has answered.
2026-09-16 15:01:08 -07:00
yucheng
a5f85c2bdb fix(otel): fit per-index OpenInference messages to the span's remaining attribute budget
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-16 21:57:05 +00:00
yucheng
325a42ea75 fix(utils): run post-call deployment hook on converted chat streams
A pre-call deployment hook can turn a requested stream into a non-streaming provider call and the result is wrapped back into a fake stream. The async client wrapper treated that wrapper like a caller-requested stream and returned before async_post_call_success_deployment_hook, so SDK callers lost post-call deployment processing (including CustomGuardrail post_call enforcement) on converted streams. Run the hook on the complete ModelResponse behind the wrapper and rewrap a modified response so it reaches the emitted chunks.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-16 21:49:47 +00:00
Yassin Kortam
f62f140f75
Merge pull request #41403 from BerriAI/litellm_key_total_spend
feat(proxy): expose lifetime total_spend on virtual keys
2026-09-16 14:49:02 -07:00
Yassin Kortam
2bfa10b6d9
Merge pull request #41311 from BerriAI/litellm_key_status_filter_deleted_lookup
feat(keys): filter /key/list by active, expired, revoked or deleted status and serve deleted keys from /key/info
2026-09-16 14:48:34 -07:00
Yassin Kortam
95abc9fb0b
Merge pull request #41330 from BerriAI/litellm_team_model_max_budget_v2
feat(team): team-level model_max_budget with key-level overrides
2026-09-16 14:48:29 -07:00
Yassin Kortam
314c0d71a5
Merge pull request #41327 from BerriAI/litellm_s3_log_prompts_only
feat(s3): add s3_log_prompts_only option to log prompts without responses
2026-09-16 14:48:18 -07:00
kerry
4b60682600 ci: auto-merge provider-info-sync PRs when CI, Greptile and Bugbot are clean
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-16 21:48:08 +00:00
shivam
8a41e10332 fix(anthropic-bridge): convert mid-conversation system turns to user turns on /v1/messages to chat completions
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-16 21:47:46 +00:00
yujonglee
cc99ad00dd
Merge pull request #41489 from BerriAI/litellm_ocr_fileio_core
refactor(ocr): move file preparation from the python bridge into litellm-core
2026-09-16 14:45:09 -07:00
mateo-berri
eaeeb3a9bc Merge remote-tracking branch 'origin/main' into litellm_dashscope_reasoning_effort 2026-09-16 14:41:08 -07:00
mateo-berri
da844e3dd2 Merge remote-tracking branch 'origin/main' into litellm_model_group_info_proxy_admin_all_models 2026-09-16 14:40:10 -07:00
ryan-crabbe-berri
4439631e05
Merge pull request #41379 from runjivu/fix/fallback-budget-check
fix!: re-check budget on router fallback targets
2026-09-16 14:39:56 -07:00
Yujong Lee
62c862796a cleanup 2026-09-16 14:38:59 -07:00
Yassin Kortam
cd08c65002
Merge pull request #41425 from BerriAI/litellm_streaming_buffer_release_on_scan
feat(guardrails): release buffered stream chunks after each passing scan
2026-09-16 14:37:56 -07:00
Yassin Kortam
16bbff6643
Merge pull request #41474 from BerriAI/litellm_router_tpm_rpm_count_before_headers_v2
fix(router): count TPM/RPM usage before building rate-limit headers
2026-09-16 14:37:04 -07:00
Yuneng Jiang
0de187e76c style(test): annotate the new locals as Final 2026-09-16 14:34:13 -07:00
Yuneng Jiang
515bf8c9d5 refactor(test): validate cost-map entries into a typed model
The selector read raw cost-map dicts as `Mapping[str, Any]`. It now validates
each together_ai entry into a frozen Pydantic model and takes the two
capabilities as keyword booleans, so nothing in the helper is coarsely typed
or stringly addressed.
2026-09-16 14:32:08 -07:00
Mateo Wang
6edb549dbd
Merge pull request #41112 from BerriAI/litellm_registry_audit_2026_09_14
fix(models): rolling registry audit: Gemini latest aliases, Nova cache pricing, OpenRouter/Together sync, Mistral GLM 5.3, Azure snapshots, Grok caching
2026-09-16 14:31:50 -07:00
Yuneng Jiang
ba6c9fa61d fix(test): drop the redundant sys.path.insert
CI runs these lanes as `python -m pytest` from the repo root, so the root is
already on sys.path and `tests._live_test_helpers` imports without help. The
insert only tripped the TQ003 test-quality budget.
2026-09-16 14:26:16 -07:00
mateo-berri
09f3a5160a test(azure_ai): assert the bare deployment name reaches the native Responses endpoint 2026-09-16 14:21:14 -07:00
Yuneng Jiang
b478131701 test(together_ai): select the live model from the cost map
`together_ai/openai/gpt-oss-20b` was hardcoded in two live tests and is no
longer served, so both failed on a vendor catalog change rather than on
anything litellm did.

Both call sites now resolve the cheapest non-deprecated together_ai chat
entry at runtime, filtered on the capabilities the tests actually exercise,
mirroring what tests/e2e/llm_translation/test_together_ai_e2e.py already
does. The selector lives in tests/_live_test_helpers.py so both lanes share
one implementation.
2026-09-16 14:20:49 -07:00
mateo-berri
04eae31769 fix(logging): keep secret-free log extras as their original objects
A non-string extra was scrubbed by a safe_dumps round trip, which handed
every user-attached handler a JSON-shaped copy even when nothing in it
was redacted. The record now keeps the original object whenever the plain
and the scrubbed renderings compare equal, so only an extra that carried
a secret comes back as its scrubbed shape
2026-09-16 14:17:48 -07:00
ryan-crabbe-berri
17844cf14b Merge remote-tracking branch 'origin/main' into pr41379 2026-09-16 14:17:23 -07:00
Yujong Lee
64cd6538a6 cleanup 2026-09-16 14:16:15 -07:00
kerry
35a5fc92bc Merge remote-tracking branch 'origin/main' into litellm_e2e_cost_calculation_scripted_provider 2026-09-16 21:15:34 +00:00
ryan-crabbe-berri
cfe65f7b55 fix(proxy)!: enforce fallback budget by default
A budget bypass that ships off by default stays open for every deployment
that does not know to look for the flag, so `enforce_fallback_budget` now
defaults to true and `general_settings.enforce_fallback_budget: false` is
the opt-out for anyone who wants the old unguarded behaviour back.

BREAKING CHANGE: a paid fallback target is now refused for callers who are
over their key or user `max_budget`. Deployments relying on fallbacks to
keep serving over-budget callers must set enforce_fallback_budget: false.
2026-09-16 14:15:27 -07:00
kerry-berri
930ec9643a
Merge pull request #41446 from BerriAI/litellm_fix_anthropic_stream_served_model
fix(anthropic): carry the served model from message_start onto stream chunks
2026-09-16 14:14:54 -07:00
mateo-berri
b2e0611c3f Merge remote-tracking branch 'origin/main' into HEAD 2026-09-16 14:13:00 -07:00
mateo-berri
3a8ac47e99 fix(proxy): mint the exchange credential off the database user row, not the cache
JWT auth caches the user it creates before it adds that user to the JWT's
team, and adding a team member never evicts the cached user row, so the mint
read a row with no teams and refused the very first token exchange for a
never-seen user as not a member. The loader now reads the row from the
database and leaves the fresh row in the cache for the requests the
credential makes next
2026-09-16 14:12:15 -07:00
Yujong Lee
c621435ef7 refactor(ocr): move file preparation from the python bridge into litellm-core
Delete litellm/ocr/input.py and the native _ocr_file_document, _ocr_upload_document
and _ocr_mime_type helpers. File documents now project to a typed OcrDocumentInput
and the core lifecycle reads local paths, encodes bytes and asks the host to read
file-like objects through a ReadDocument operation before the provider request

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-16 21:02:30 +00:00
Yuneng Jiang
f10d95fb95
test(together_ai): drop the prefix-strip assertion, e2e covers it live
tests/e2e/llm_translation/test_together_ai_e2e.py registers its model with the
full registry key, so a slashed together_ai/<vendor>/<model> goes through the
prefix strip on every e2e run and an over-strip would fail against the real
API. The unit assertion was a second copy of that. The roles check stays, since
nothing in e2e exercises it.
2026-09-16 13:58:38 -07:00
Mateo Wang
b3898dfd85
Merge pull request #36815 from BerriAI/litellm_otel_gen_ai_system_none
fix(otel): drop None metric and event attributes before OTLP export
2026-09-16 13:56:05 -07:00
yucheng
b96a804004 fix(proxy): seed litellm_call_id into request data before parsing can fail
The failure hook received data without the resolved id when body parsing or
add_litellm_data_to_request raised, so proxy-only spend logging minted a fresh id
that did not match the error log or the x-litellm-call-id header. The id is now
part of the request data from the start and merged over the parsed body, which
also removes the post-hoc in-place assignment

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-16 20:55:32 +00:00
Yuneng Jiang
baca62df13
test(logging): pick this test's own records out of the shared log batch
The generic API logger batches whatever is queued when it flushes, so records
from tests in other files in the same job land in the same request. Two tests
assumed otherwise: one read actual_request[0], the other counted NDJSON lines,
and both broke whenever another file logged first. Select by the messages each
test sent instead, which keeps the format assertions and stops the order from
deciding the outcome.
2026-09-16 13:53:28 -07:00
mateo-berri
7cb01cf47f fix(proxy): exchange role default, logged rejections, gated grant listing
The exchange refused the very user JWT auth upserts (its row has no
user_role) as "no longer active". The credential now carries the role the
proxy already enforces for that user on every request, internal_user when
the row has none, the same rule _get_user_role applies on the data plane.

A rejected subject_token no longer echoes JWT auth's wording on the public
/token endpoint: the response is a fixed invalid_request and the reason
goes to the proxy log, since that wording can name the JWKS URL or relay
the IdP's reply.

The exchange grant is listed on /register, /.well-known/litellm-cli-auth,
and the aggregate authorization-server metadata only when JWT auth is on,
backed by a database, and licensed, so a client never selects a grant the
gateway would then refuse.
2026-09-16 13:53:10 -07:00
Yuneng Jiang
9421b26bf6
fix(e2e): stage the seeded device id per thread, not per process
Build 232 took two compat cells red with a FileNotFoundError renaming
`.claude.json.197` onto `.claude.json`. `run_claude_models_parallel`
drives several models from one process, so a pid-suffixed staged name is
shared between threads: one thread renamed the file the other was still
writing, and the loser died on a path that no longer existed.

mkstemp in the same directory gives a name that is unique per thread as
well as per process, and the rename stays atomic.
2026-09-16 13:47:07 -07:00
Yassin Kortam
4bf04b22b0
Merge pull request #41472 from BerriAI/litellm_customer_budget_prometheus_metrics
feat(prometheus): add customer (end_user) budget gauges
2026-09-16 13:46:57 -07:00
Yassin Kortam
8cdb275ec0
Merge pull request #41460 from BerriAI/litellm_pg10_migration_default_guard
ci(migrations): flag defaulted ADD COLUMN on request-log tables
2026-09-16 13:45:48 -07:00