Commit graph

47404 commits

Author SHA1 Message Date
Cursor Agent
bbf4d1dc30
fix(spend): catch PrismaError instead of bare Exception in key recovery
The Usage recovery path was adding four BLE001 hits and failing the
strict-rule budget. Soft-fail only on PrismaError so a down token table
still falls through to SpendLogs.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-09-03 14:44:02 +00:00
Cursor Agent
fd520e53ab
style(spend): satisfy ruff format on key metadata recovery
Lint CI failed because ruff format splits the Set alias import and collapses a couple of long lines.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-09-03 14:34:44 +00:00
Cursor Agent
6f0f2fcc8d
fix(spend): restore user_email for double-hashed keys and persist it in spend logs
Recovery now resolves the key owner's email from UserTable via the
recovered token user_id, and SpendLogsMetadata keeps user_api_key_user_email
so new batch/export consumers see email without a separate user join.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-09-03 14:27:12 +00:00
Cursor Agent
9dae07175c
fix(spend): share double-hash key alias recovery with CloudZero and Focus
Extract the Usage reverse-hash / SpendLogs alias recovery into a shared
helper and apply it when CloudZero and Focus export DailyUserSpend rows,
so BI pulls get api_key_alias back for historical v1.99 double-hashed keys
instead of null.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-09-03 14:24:08 +00:00
Cursor Agent
e07d58a4ef
fix(usage): recover aliases for v1.99 double-hashed spend keys
Callback log replay also omitted user_api_key_hash, so it could double-hash
spend rows the same way batch costing did. On the read path, Usage key
metadata now reverse-hashes orphaned DailyUserSpend.api_key values against
VerificationToken and falls back to SpendLogs metadata so historical dirty
rows show their api_key_alias again instead of key-hash-...

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-09-03 14:21:25 +00:00
Cursor Agent
63579f1e35
fix(spend-tracking): keep batch spend keys joinable after v1.99 provenance gate
Batch cost attribution and the legacy queue endpoint already store the
VerificationToken hash in user_api_key, but omitted user_api_key_hash.
Since v1.99 the spend-log writer re-hashes any key without that provenance
flag, so DailyUserSpend.api_key no longer joins VerificationToken and Usage
shows key-hash-... rows with null api_key_alias / user_email.

Co-authored-by: Mateo Wang <mateo-berri@users.noreply.github.com>
2026-09-03 14:12:57 +00:00
mateo
840173e778 feat(registry): add azure_ai/mistral-ocr-4-0 page and annotation prices from Azure Retail Prices
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-03 13:48:25 +00:00
mateo
f26407aa8c feat(registry): add azure_ai/MAI-Thinking-1 from Azure Retail Prices and Foundry docs
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-03 13:43:48 +00:00
mateo
55a5f142e6 fix(model_prices): add azure_ai Codestral-2501 and FW-Nemotron-Lightning-3.5, sync Azure and Vertex deprecation dates, fix novita gpt-oss vision flags
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-03 13:23:02 +00:00
Mateo Wang
11a02b9581
Merge pull request #36260 from eeshsaxena/fix-credential-endpoints-raise-not-return
fix(proxy): 404 a credential delete that matched nothing, and raise instead of return
2026-09-03 06:17:56 -07:00
mateo
1a39275cb3 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_registry_audit_2026_09_02 2026-09-03 13:05:42 +00:00
mateo-berri
f83b6029e3 Merge branch 'litellm_internal_staging' into fix-credential-endpoints-raise-not-return 2026-09-03 05:36:50 -07:00
mateo-berri
418d0e79ba fix(proxy): answer 404 when deleting a credential that was never stored
prisma's `delete` returns None when the `where` clause matched no row
instead of raising, and the handler never looked at the return value. It
went straight on to filter an in-memory list that never held the name and
answered 200 "Credential deleted successfully", so an operator scripting
credential cleanup could not tell a real deletion from a typo.

Look at what the repository returned and answer 404 with the name, the
same rejection PATCH /credentials/{credential_name} already gives. A
credential that only exists in the config yaml is never written to the
table, so it now answers 404 too, which is honest: reporting success for
it is the same lie, since it comes back on the next proxy boot.

Adds regression tests for the delete 404, the still-working delete, the
config-yaml-only credential, and for the raise-not-return fix on both
DELETE /credentials/{credential_name} and GET /credentials, which
serialized a rejection as the 200 response body.
2026-09-03 05:30:28 -07:00
mateo-berri
15e956db33 test(agents): make the make_public regression tests fail without the fix
The config stub shared one list object between save_config and get_config, so the
DB overlay handed the endpoint back the very list it had just appended to and both
tests passed with the product fix reverted. Store the settings as JSON the way the
litellm_config row does, and check the duplicate guard against a list that only
ever existed in the DB.
2026-09-03 05:21:42 -07:00
mateo-berri
b1695e9090 test(agents): type the public-agent regression tests fully 2026-09-03 04:33:55 -07:00
mateo-berri
9d862a6583 style: drop explanatory comments from the agent publish fix 2026-09-03 04:27:43 -07:00
mateo-berri
ccbd3e495c fix(agents): keep the published agent in public_agent_groups
`POST /v1/agents/{id}/make_public` appended the agent id to
`litellm.public_agent_groups` and only then called `get_config()`, which
re-applies the DB's `litellm_settings` over the module globals and threw the
append away. The config it saved was therefore a no-op: the endpoint answered
200 with an empty `public_agent_groups`, the agent never reached
`GET /public/agent_hub`, and re-publishing never hit the "already public" 400.
Read the config first, derive the new list from the refreshed globals, save it,
then update the global

Also fixes the e2e model hub spec, which is flaky for a second reason: the
"Make Models Public" modal preselects the groups that are already public, so a
blind click on "Select All" cleared them and left "Next" disabled for the full
15s action timeout. Check the box instead of toggling it, and wait for "Next"
to be enabled before clicking
2026-09-03 04:24:09 -07:00
mateo-berri
e33f6911e3 test(guardrails): assert the handler map is read live on every hook call
Covers the reintroduction of a second module-level cache for the guardrail
translation mappings: remapping the loader between two pre-call hooks must
change which handler runs, and the module must expose no assignable map of
its own.
2026-09-03 04:04:24 -07:00
mateo-berri
fc4c961f98 fix(anthropic): key the chat-completions bridge spend row on the streamed msg_ id
Streaming /v1/messages against a model served through the chat-completions
bridge (every non-Anthropic provider other than OpenAI) minted its msg_ id
inside the stream wrapper, so the spend row landed under the provider's own
completion id and the caller could not find the call by the only id it saw.

The wrapper now mints the id once in its constructor and hands it to the
logging object, the same way the Responses-API bridge does.
2026-09-03 04:03:31 -07:00
mateo
753bea360e test: deflake guardrail mapping leak, tag routing randomness, and liveliness timing
TestStreamingScanDedup restored the reduced module-level translation
mapping on teardown via monkeypatch, so under --dist=loadscope the
worker that ran only that class carried the reduced mapping into the
streaming block test modules. Tag routing tests now assert the eligible
deployment set directly instead of sampling ten random picks. The
liveliness latency check measures steady-state polls after a warm-up
request rather than the first request through a fresh app.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-03 10:38:20 +00:00
mateo-berri
39705c8edb test(anthropic_messages): configure the bridged streaming test transport through the env var only
The documented DISABLE_AIOHTTP_TRANSPORT env var already selects the httpx transport, so the extra module-global write was redundant. Types the monkeypatch fixture while here.
2026-09-03 03:35:49 -07:00
mateo-berri
a737f8625d fix(guardrails): remove the module-global translation mapping that leaked between tests
The unified guardrail cached the endpoint translation mappings in its own
module global on top of the loader's cache in litellm/llms. Tests wrote to
that second copy directly, so a teardown that restored a stale snapshot left
a test double installed for every later test on the same xdist worker, and
proxy-endpoints went red on whichever guardrail streaming test happened to
land after it.

Read through load_guardrail_translation_mappings() at each call site and give
the tests one seam to patch, so pytest owns every restore.
2026-09-03 03:28:52 -07:00
mateo-berri
86c5159d96 fix(masker): bound the credential walk at the generic recursion depth
Failing closed at the sensitive-data masker's depth of 10 turned an ordinary
nested tool JSON schema into REDACTED leaves, because a list level costs two
depth. The walk now bounds on DEFAULT_MAX_RECURSE_DEPTH, which no real payload
reaches, and the masker's own limit is left alone.
2026-09-03 03:23:22 -07:00
mateo-berri
3abed5f4c9 fix(masker): hide containers at the redaction depth limit instead of passing them through 2026-09-03 03:12:04 -07:00
mateo-berri
7d8e1c6a1d fix(anthropic_messages): key bridged streaming spend rows on the streamed msg_ id
A streaming /v1/messages call against a non-Anthropic model is served an SSE
message_start frame carrying a msg_ id the adapter mints locally, since the
Responses API upstream only issues a resp_ id. That value never left the
adapter, so the spend row was keyed on the bridged response id and
GET /spend/logs?request_id=msg_... came back empty.

The adapter now hands the id it minted to the logging object, and the
/v1/messages logging path keys the row on it.
2026-09-03 03:07:22 -07:00
mateo-berri
912572bfa5 fix(utils): redact credentials nested in extra_body on the verbose optional-params line
The "Final returned optional params" line printed whatever the caller nested inside
extra_body, so a credential tucked in there reached stdout in plaintext one line after
the request line that already redacts it.

The call site now runs redact_credentials_in_payload behind a guard reading both of
print_verbose's consumers, litellm.set_verbose and the LiteLLM logger's DEBUG level, so
the line prints in exactly the cases it did before and the walk costs nothing when
nothing would read it.
2026-09-03 02:53:50 -07:00
mateo-berri
d3b6ce98d6 fix(responses): encrypt the response id on every streamed event
Background streaming emits event types with no typed model, which arrive
as GenericEvent holding a plain dict. Only typed events had their nested
response id rewritten, so those frames advertised the raw internal id
while their siblings advertised the encrypted one. The raw shape skips
the ownership check, so any other key could retrieve or cancel that
response.

Rewrite the advertised id wherever an event carries one, whichever shape
it arrives in, so a future event type cannot reopen this.
2026-09-03 02:41:04 -07:00
mateo
d4a480fb6e Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_deflake_20260902 2026-09-03 09:18:54 +00:00
mateo-berri
0a62195db2 fix(utils): redact credentials nested inside lists and tuples
redact_credentials_in_payload only recursed into mappings, so a
credential-named key one level inside a list or tuple, the shape
extra_body and metadata routinely carry, still reached stdout under
set_verbose. Rebuild sequences element by element too, keeping the
container's own type so the printed repr is unchanged apart from the
secret.
2026-09-03 02:11:32 -07:00
mateo-berri
64601fd7ae fix(utils): redact credential kwargs from the set_verbose request line
`litellm.set_verbose = True` printed the caller's kwargs verbatim to stdout, so
`api_key` and its siblings landed in terminals and container log drains in
plaintext while the same statement's logger emission was already redacted.

Mask the kwargs at the source with a shared helper in
`litellm_core_utils/sensitive_data_masker.py`, reusing the existing
`SensitiveDataMasker` key classification and the `REDACTED` marker
`secret_redaction.py` already owns, so both debug surfaces agree.
2026-09-03 01:55:56 -07:00
Devin AI
e1b2d9de3c fix(images): forward gpt-image supported params like background to OpenAI and Azure
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-03 08:45:23 +00:00
mateo
4e9c6b5dd4 refactor(model_armor): type the buffered stream chunks as object
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-03 08:26:43 +00:00
mateo-berri
1b4d2e25db fix(proxy): stop putting the literal string "None" in error payloads
A blocked guardrail (and any other HTTP error the proxy converts) came back
with "type": "None" and "param": "None", because the converters passed the
string "None" as the getattr default instead of None. OpenAI types error.type
as a required string and error.param as nullable, so type now falls back to
the type its status code stands for and param serializes as JSON null.

Covers the non-streaming body, the SSE error frame, the client-disconnect
frame, and the unclassified-exception path, so every unified LLM endpoint and
the anthropic endpoints return the same shape.
2026-09-03 01:23:48 -07:00
mateo-berri
fa5a90e08e fix(bedrock): stop the Moonshot invoke transform from resolving AWS credentials
AmazonMoonshotConfig.transform_request called
_get_boto_credentials_from_optional_params purely for its side effect of
popping the aws_* keys off optional_params, then threw the result away. On
a box whose default AWS profile uses login_session without botocore[crt],
that call raises, so a bearer-token bedrock/invoke/moonshot.* deployment
still 500s with MissingDependencyException even after the rest of this
branch skips the chain.

It now filters the aws_* keys into a local dict the way the Qwen, OpenAI
and Claude 3 invoke transformations already do, so no credentials are
resolved and the caller's optional_params keeps the keys sign_request
reads afterwards.
2026-09-03 01:20:27 -07:00
mateo-berri
461a3a3ea5 Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_spend_logs_provider_response_id 2026-09-03 01:16:28 -07:00
mateo-berri
c85da0a75f fix(logging): key bridged /v1/messages rows on the id the caller received
/v1/messages against a non-Anthropic model answers with the Responses id,
but the spend row was built from a fresh ModelResponse, so it landed on a
chatcmpl- uuid nobody can look up. Carry that id through the same way the
Anthropic branch now does, and make the passthrough spend assertions fail
on an empty lookup instead of skipping past it.
2026-09-03 01:15:06 -07:00
mateo
3190f42abf refactor: clear fresh tech debt from the last 24 hours (2026-09-03)
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-03 08:09:51 +00:00
mateo-berri
3b814179c8 refactor(logging): name the response-id helper for what it reads, not the provider 2026-09-03 00:50:11 -07:00
mateo-berri
6edb72f79c Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_azure_ai_reclassify
# Conflicts:
#	tests/test_litellm/test_main.py
2026-09-03 00:47:14 -07:00
mateo-berri
58575c77a5 test(e2e): correlate anthropic passthrough spend rows by the served message id 2026-09-03 00:46:07 -07:00
mateo-berri
54f4fa2e1b test(passthrough): look up anthropic spend rows by the message id the caller received 2026-09-03 00:42:37 -07:00
mateo-berri
ef453c943a Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_ui_build_check_image_boundary 2026-09-03 00:38:51 -07:00
yuneng-jiang
658f50663d
fix(ui): keep Virtual Keys list state in the URL so it survives leaving the page (#39481)
* fix(ui): keep Virtual Keys list state in the URL so it survives leaving the page

The search term, sort, pagination and drawer filters lived in component
state, so navigating away from Virtual Keys and back reset the table to an
unfiltered first page. Move them into query state alongside the existing
?key= deep link, which also makes a filtered view shareable.

* fix(ui): namespace the Virtual Keys filter params and bound page inputs

The unprefixed team_id filter hijacked the /api-keys create-key deep link,
which already takes team_id as a prefill, so ?create=true&team_id=X silently
filtered the list underneath the modal. Prefix the four drawer filters.

Now that page and page_size come from the address bar, clamp them to what
/key/list accepts instead of forwarding 0, negatives or an int64-overflowing
page straight through, and trim filter values arriving from a URL the same
way the drawer already trims them.

* fix(ui): fall back to a sortable column when the URL names an unknown one

A hand-edited or stale sort_by reached /key/list, which 400s it, leaving the
Virtual Keys page on its loading skeleton with no error. Validate it against
the fields the table's own headers can produce, and clear sort_by rather than
blanking it when a sort is reset so the URL stays clean.

Also replaces a default-state URL assertion that ran before any query-state
write could land, so it could not fail for the regression it named.

* fix(ui): use TanStack's functionalUpdate instead of a hand-rolled updater resolver

The local helper narrowed typeof updater === "function" against an
unconstrained T, which TypeScript cannot do because T itself may be a function
type, so next build failed to type check. table-core already exports the same
helper.
2026-09-03 00:35:32 -07:00
mateo-berri
6582dbe1c0 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_coerce_multipart_numeric_fields 2026-09-03 00:33:18 -07:00
mateo-berri
0e537d212a Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_vector_store_hook_router_injection 2026-09-03 00:32:33 -07:00
mateo-berri
20a55146cc Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_mistral_voxtral_tts_speech 2026-09-03 00:31:24 -07:00
mateo-berri
0a2581c14c fix(mistral): keep the deployment voice default and drop the unreachable api base fallback
Review turned up two real problems in the TTS path.

Router.aspeech forwarded voice=None whenever the caller omitted it, which overwrote a
voice set in the deployment's litellm_params, so a configured fallback voice was
ignored on voice-less requests. It now leaves the key alone when no voice is passed.

get_complete_url also fell back to MISTRAL_API_BASE, but speech() always receives a
non-null api_base from get_llm_provider, whose mistral branch only reads
MISTRAL_AZURE_API_BASE and otherwise hardcodes the public host. That branch could
never run, and its unit test asserted a behavior the real path does not have. The
working override is api_base on the deployment, now pinned by an end-to-end test
2026-09-03 00:30:07 -07:00
mateo-berri
46d7e92845 fix(spend_tracking): key /v1/messages spend rows on the msg_ id the client received
POST /v1/messages returns an Anthropic-shaped body whose `id` is the only
request id the caller ever sees, but the spend row was written with a
`chatcmpl-<uuid>` (non-streaming) or the bare `litellm_call_id` (streaming and
the /anthropic/v1/messages passthrough), so
GET /spend/logs?request_id=msg_... returned [].

The logging conversion now carries the provider's response id through:
_handle_anthropic_messages_response_logging seeds the ModelResponse it builds
with the Anthropic id, and the passthrough logging handler prefers the id it
read off the response body or the message_start chunk over litellm_call_id.
get_spend_logs_id already prefers response_obj["id"], so the spend row and
standard_logging_object["id"] now both carry the id the client holds.
2026-09-03 00:26:18 -07:00
Mateo Wang
066d5f0694
Merge pull request #39502 from BerriAI/litellm_/triage-slack-message-4bd4e6
fix(test): drop the duplicate embedding_executor arg in the Bedrock KB fake handler
2026-09-03 00:26:08 -07:00
mateo-berri
62c7e84448 fix(proxy): parse numeric multipart fields on /v1/images/edits back into numbers
Every field of a multipart form arrives as a string, so `n` reached the
provider as "2" and Bedrock Nova Canvas rejected the request with
"expected type: Number, found: String". Restore the type the request
schema declares at the boundary where the form is parsed, driven by the
schema's own type hints so the helper covers any int- or float-typed
field on any multipart endpoint.
2026-09-03 00:22:54 -07:00