Commit graph

13596 commits

Author SHA1 Message Date
Emerson Gomes
0e3f52a4c0
feat(model_prices): add gemini-3.1-flash-lite-image
Register Nano Banana 2 Lite on the unprefixed, gemini/, and vertex_ai/ keys
so completion_cost and pass-through spend tracking no longer treat the
model as unmapped
2026-08-13 19:18:22 -05:00
Yassin Kortam
72ee0bb1c4
fix(cli): launch agents as a child process on Windows (#36822)
os.exec* has no process-replacement semantics on Windows, so `lite claude`
printed its routing line and returned to the prompt while Claude Code was left
detached without a usable console. Windows now spawns the agent, waits for it,
and exits with the child's status. Batch shims such as the npm-installed
claude.cmd go through cmd.exe because CreateProcess cannot run them directly,
and that command line is emitted verbatim with every token quoted so a spaced
path or an argument holding a shell metacharacter cannot be re-parsed by the
command processor. POSIX keeps using os.execvpe unchanged.
2026-08-13 17:06:25 -07:00
Yassin Kortam
56b08c19d6
fix(proxy/team): resolve member_delete cleanup by user id, not the addressed email (#36839)
/team/member_delete dropped the roster entry by matching user_email against
members_with_roles, then built its user-row lookup from that same raw email
instead of from the user_id the roster entry already carries. An email the user
row does not literally hold matched nothing, so the team id stayed in the user's
teams array and the team-membership row was left orphaned while the call still
returned 200.

/team/member_add resolves an email to a user case-insensitively but stores the
caller's casing on the roster, so inviting "Alice@Example.com" for a row holding
"alice@example.com" and removing by that same string is enough to reach it.

_cleanup_members_with_roles now returns the roster entries it removed, and both
the user-row update and the membership delete run against their user ids.
2026-08-13 17:00:52 -07:00
Yassin Kortam
ab2333b6c4
fix(auth): stop the team fallback from widening model access (#36837)
When get_team_object fails, the centralized auth gate rebuilds the team
from the token's own fields. A token whose team row was missing when the
key was read carries team_models=[] and team_blocked=False, and the
model-access check reads an empty model list as every model, so the
rebuilt team grants more than the real team ever did.

get_team_object reported a deleted team and a database that would not
answer as the same 404, so the fallback could not tell a definitive
answer from a degraded read. Raise a TeamNotFoundError subclass, still a
404 with the same detail so every other caller is unaffected, only when
the database answers and the row is absent.

A team that is provably gone now refuses, and no setting overrides that.
Otherwise the grant is merely unknown: a token carrying one may vouch,
since replaying a recorded grant cannot widen it, and a token carrying
none may not. allow_requests_on_db_unavailable still opts back out there,
and is only consulted once the failure is known to be a degraded read.
2026-08-13 16:59:58 -07:00
Yassin Kortam
4bc27f1664
fix(auth): carry team grants in lite login session tokens (#36826)
CLI session tokens minted by /sso/cli/poll set team_id and team_alias but
never team_models or team_model_aliases, so the token carried a team with
none of that team's grants. /v1/models bails out to "unrestricted" when both
key_models and team_models are empty and listed the whole proxy, and team
model aliases never resolved because both can_team_access_model and the
pre-call rewrite read team_model_aliases off the token.

The team data was not close at hand: _fetch_cli_sso_team_details projected
full team rows down to team_id and team_alias before they reached the mint.
Widen that projection to include the team's models and its joined alias
table, and populate both fields at mint time.

Also stop writing the user's personal allowlist into the key models slot
when a team is bound, matching virtual-key semantics where a team-bound
credential is governed by the team grant.

Because an empty team grant is itself a real value meaning unrestricted, a
team whose grants cannot be resolved must not be minted as empty: that is
the same "unrestricted" bail-out this fix exists to close. The poll now
refuses to mint when the selected team has no complete cached detail.

That refusal is only safe because a login can no longer be pinned to a team
whose grants will never resolve. Deleting an organization drops its team
rows but leaves the memberships behind, so the login now offers only teams
whose rows still exist, and a lookup that fails outright fails the login
rather than caching a session that silently drops every team.
2026-08-13 16:56:47 -07:00
Mateo Wang
373a0fc506
Merge pull request #36717 from BerriAI/litellm_add_muse_spark_1_2
feat(model_prices): add meta/muse-spark-1.2 and its contributor tier
2026-08-13 16:19:06 -07:00
ryan-crabbe-berri
262ed530f8
fix(proxy): honor explicit null budget_duration on team and key create + clearable UI dropdowns (#36699)
* fix(proxy): honor explicit null budget_duration over default_team_params on /team/new

* fix(ui): clearable team budget reset with explicit Never resets option

* docs(proxy): align default_team_params docstrings with actual all-teams scope

* fix(proxy): honor explicit null budget_duration on /key/generate over configured defaults

* fix(proxy): keep upperbound_key_generate_params filling explicitly-null key params

* fix(proxy): restrict explicit-null default opt-out to budget_duration
2026-08-13 15:22:11 -07:00
yuneng-jiang
0c1355d54a
Merge pull request #36824 from BerriAI/litellm_/concurrent-view-creation
fix(proxy): tolerate a concurrent creator when creating spend views
2026-08-13 15:20:11 -07:00
yuneng-jiang
69792a9529
Merge pull request #36823 from BerriAI/litellm_/e2e-access-control-allowlist
test(e2e): assert the model allow-list permits, not only denies
2026-08-13 15:17:52 -07:00
mateo
cbcc3715c6 Merge branch 'litellm_internal_staging' into litellm_add_muse_spark_1_2 2026-08-13 21:54:41 +00:00
Mateo Wang
a4ab511d0b
Merge pull request #36805 from BerriAI/litellm_grok_4_6
feat(xai): day-0 pricing for grok-4.6
2026-08-13 14:49:41 -07:00
Yuneng Jiang
726292720c
fix(proxy): guard every view creation, not just the first and last
Against a real Postgres the previous commit still died on MonthlyGlobalSpend:
only 2 of the 8 creation sites went through the tolerant helper, so the losing
replica re-raised on the first unguarded one and skipped the rest.

The regression test now makes every CREATE lose the race and asserts all 8 are
still attempted, which fails on the partial fix.
2026-08-13 14:41:59 -07:00
Yuneng Jiang
77e64c5d40
fix(proxy): tolerate a concurrent creator when creating spend views
Every replica booting against the same fresh database sees each view as
absent and issues the CREATE. Postgres fails all but one with a
duplicate-object error, and that exception propagated out of
create_missing_views, so every view after the first was never created and
/global/spend* 500'd for the life of the deployment.

Losing that race reaches the desired end state, so treat it as success.
Genuine DDL errors still propagate.
2026-08-13 13:42:57 -07:00
Yuneng Jiang
ed01f7316b
test(e2e): assert the model allow-list permits, not only denies
Every case in TestAccessControl asserted that something was refused. A gateway
that denied the allow-listed model too would have passed all of them, so the
suite could not tell "denied correctly" from "broken outright".

Adds the positive half: a key allow-listed for gemini-2.5-flash can call it and
gets back a real completion rather than a 200-wrapped error.

Also tightens the unknown-model case. It accepted any valid JSON, so a bare
"{}" or even "null" satisfied it. It now requires the OpenAI-shaped error
envelope with a message a client can actually surface, parsed through a typed
model instead of json.loads.
2026-08-13 13:37:31 -07:00
MUSE
f91e698adb fix(batch): avoid reading a nonexistent output artifact for completed batches
Completed batches that contain only failed requests do not generate an
output file, leaving output_file_id unset while the failures are recorded
through error_file_id instead.

The completion handler attempted to read the output payload regardless of
whether an output file actually existed. During retrieve polling this caused
the logging pipeline to fail with "Output file id is None cannot retrieve
file content", preventing normal completion bookkeeping from running.

Skip output retrieval when no output file is available and return an empty
batch summary (zero usage, zero cost, no model entries). The lower-level
file retrieval helper still reports an error if it is called directly with
an invalid or missing file identifier.

Closes #33987
2026-08-14 05:20:16 +09:00
tin-berri
d8fda675cc
feat: pre-adoption shadow eval for the auto-router (blind pairwise judge, derived state) (#36587) 2026-08-13 13:15:45 -07:00
Anas Khan
7fcca523aa
fix(proxy/batches): stop forwarding custom_llm_provider twice in list and cancel (#32813)
* fix(proxy/batches): stop forwarding custom_llm_provider twice in list and cancel

The model-routing branches of list_batches and cancel_batch passed
custom_llm_provider as an explicit kwarg while also leaving it inside the dict
they splat, so every such call raised "got multiple values for keyword argument
'custom_llm_provider'" and returned a 500.

list_batches SCENARIO 2 called data.update(credentials) but never removed
custom_llm_provider before litellm.alist_batches(custom_llm_provider=..., **data);
it now uses prepare_data_with_credentials, the same helper the create and
retrieve branches already use, which pops it out.

cancel_batch SCENARIO 3 resolved the provider with
`provider or data.pop("custom_llm_provider", None) or ...`, so when the path
param provider was set the pop short-circuited and a body custom_llm_provider
stayed in data and collided with the explicit kwarg. The body value is now
popped unconditionally before the fallback chain, so the path param wins cleanly
and data no longer carries a duplicate.

Both paths already had strict-xfail regression tests documented "remove when
fixed"; those markers are dropped so the tests now guard the fix.

Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>

* fix(proxy/files): avoid duplicate custom_llm_provider in list

Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>

---------

Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
2026-08-13 12:50:32 -07:00
mateo
3db1759d04 fix(bedrock): stop emitting an empty assistant delta after the finish_reason chunk
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-13 19:45:32 +00:00
Mateo Wang
50ae806784
Merge pull request #36733 from BerriAI/litellm_fix_interactions_responses_bridge
fix(interactions): map step and turn history to Responses API roles and content types
2026-08-13 12:35:21 -07:00
mateo-berri
928dfab65c feat(xai): day-0 pricing for grok-4.6 2026-08-13 12:32:09 -07:00
mateo
b8c1103c55 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_check_batch_cost_poll_starvation 2026-08-13 19:26:17 +00:00
tin-berri
5f2986a1f3
feat(complexity_router): calibrate the classifier rubric with worked examples, selectable per router (#36578)
* feat(complexity_router): calibrate the classifier rubric with worked examples

The built-in rubric stated its tier boundaries as prose alone, and prose
calibrated to consumer chat puts "non-trivial code, multi-step technical work"
at the top of the scale. That is the median request in developer and agent
traffic, so ordinary engineering read as top-tier and the router paid for the
most expensive model on it.

Adds calibration examples to the rubric, selected by a new
classifier_llm_config.rubric preset. The agentic preset (now the default)
anchors routine installs, builds, multi-file edits, and standard debugging at
MEDIUM; the chat preset omits those anchors for deployments serving only
conversational traffic. Both share the same tier criteria, the trust-boundary
paragraph, and the context-window closing line, so this moves where the
boundary sits without changing the taxonomy.

Both presets render byte-identical to the strings a prompt sweep scored, and a
test pins that, so the measured accuracy describes what a router sends.

* feat(ui): pick the classifier rubric preset on an auto-router

Adds a Rubric dropdown to the auto-router's classification panel, so the
agentic and chat presets are selectable rather than config-file only. The
prompt editor prefills from the selected preset, since prefilling agentic text
for a router on chat would show examples its classifier never receives.

The picker is disabled while a custom prompt is set, and the payload builder
drops the preset in that case: a custom prompt is the classifier's whole system
role, so the backend rejects the two together. The builder records the default
preset explicitly, so a later change to which preset is default cannot silently
move an existing router.

* fix(complexity_router): mark an unchosen rubric preset with None, not model_fields_set

The mutual-exclusion check read model_fields_set to tell an explicit preset
from the default. That flag does not survive serialization, and this config is
dumped and handed straight back to ComplexityRouter by /auto_router/test_routing,
where a dump re-states every field. So a custom-prompt classifier saved fine and
then failed validation on preview, rejecting on the second pass what it accepted
on the first.

The preset is now optional, with None meaning the default, matching how None
already means the built-in rubric for system_prompt on the same model. The
default lives in one place, DEFAULT_RUBRIC_PRESET, resolved where the prompt is
assembled. The dashboard stops sending a copy of the default it displays, so a
router nobody configured follows the default rather than pinning today's value,
and UI-built routers behave the same as hand-written config.

Regenerates schema.d.ts, which was left stale by an earlier description edit.

* feat(complexity_router): grandfather existing routers onto the uncalibrated rubric

An unset preset now means LEGACY, the rubric exactly as it shipped before
calibration examples existed, so upgrading cannot move the tier decisions or the
bill of a router that is already running. Config-file routers get this for free
since they name no preset, and a stored config that never had one reads the same
way.

New routers still get the calibrated rubric: switching a classifier to LLM
stamps the agentic preset, because a classifier being configured for the first
time has no prior tier behaviour to preserve. The picker offers legacy so an
existing router's state is representable and opening the form cannot silently
upgrade it.

Each preset is pinned byte-identical to the text the prompt sweep scored,
legacy included, which is what proves an existing router's prompt did not move.

Also collapses the preset data from a NamedTuple with group wrappers and
per-preset frozensets into plain text blocks in a MappingProxyType, matching how
the tier criteria next to it are already stored: 21 lines of prompt text no
longer cost 190 lines of constructors. Tiers are format placeholders so
tier_labels still reach the examples.

* refactor(complexity_router): name the field classification_rubric

`rubric` alone did not say what it selects, and the field sits beside
`system_prompt`, which genuinely is the whole classification prompt. The name
now says which of the two an operator is reaching for: the rubric the built-in
prompt is assembled from, not the prompt itself.

Renames the config field, the query param, the enum, and the dashboard label to
match, and moves the preset text to classification_rubrics.py.

* test(ui): set the preset the mutual-exclusion case is meant to drop

The rename left classification_classification_rubric in the custom-prompt case,
so its input never carried a preset and the assertion held for the wrong reason:
it proved an absent preset stays absent, not that a set one is dropped. A
normalizer that forwards the preset whenever one is set passed with the typo and
fails without it.

tsc reports the typo as TS2353; the earlier sweep grepped for the source file
and not the test, so it went unseen.

* test(ui): scope the role-gate assertions to each page's own endpoint

The memory, workflows, and guardrails-monitor page tests asserted that a denied
role fires no request at all. Their names, and the assertion on the very next
line, say the intent is narrower: the page must not fetch its own data.

Resolving whether a caller is an org admin goes through /organization/list for
every role, since deciding org-admin-for-any-org needs the list, and the route
scopes rows per caller. That legitimate request fails a blanket no-fetch
assertion, so all three files went red on staging for a reason unrelated to
what they test.

Drops the blanket assertion and keeps the scoped one. Bypassing the gate in
memory/page.tsx still fails five tests, so the narrower assertion continues to
catch a genuinely broken gate.

* fix(complexity_router): document that an unset rubric keeps the legacy prompt

The field said 'Leave unset for agentic' while an omitted rubric resolves to
LEGACY, so the OpenAPI schema an operator reads promised calibrated routing
where they got the uncalibrated one.

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

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-13 12:22:20 -07:00
tin-berri
add095b494
Fix: ComplexityRouter should not score system prompt text for code/technical complexity (#36721)
System prompts (harnesses, tools, framework boilerplate) are session-wide
constants identical across all requests. Scoring them saturates keyword-match
signals and produces false-positive high-complexity classifications on
trivial utterances like 'hi', routing them to expensive models (sonnet/opus)
instead of tier-1 haiku. A real ~1.6KB CLI-agent harness alone supplied
5 codePresence + 2 technicalTerms matches, overshadowing user signal.

Rescope four scoring dimensions (codePresence, technicalTerms, simpleIndicators,
multiStepPatterns) from full_text (system + user) to user_text (user only).
reasoningMarkers was already scoped this way. This returns 0.63 of the weight
budget to text that actually varies per-request.

Now that every dimension scores user_text only, _score_keyword_match's
disclosable_text param is redundant -- it existed solely to let the signal
name terms matched in the caller's own message while withholding terms
matched only in the (invisible-to-the-caller) system prompt. With no more
system-prompt text in scope, text and disclosable_text were identical at
every call site, so the param is dropped and the function collapses to a
single text argument.

Add mutation-proven regression test: trivial 'hi' message with realistic
Claude Code agent system prompt now routes to haiku tier-1 (not sonnet).

- Unfixed: haiku -> sonnet (bug)
- Fixed: haiku -> haiku (correct)

Invert three pre-existing assertions in TestSignalsNeverQuoteTheSystemPrompt
to capture the corrected behavior: system-prompt-only terms produce no signal.

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-13 11:15:14 -07:00
Noah Nistler
f8fccec108 fix(azure_ai): enforce admin-only index create on the passthrough route
POST /azure_ai/indexes carries no index name, so
get_azure_ai_search_index_from_endpoint returns None,
is_vector_store_index never matches any segment, and the request falls
through to the generic Azure passthrough on the proxy's own
AZURE_API_BASE and AZURE_API_KEY without ever reaching
is_allowed_to_call_vector_store_endpoint. A non-admin could therefore
create a Search index whenever AZURE_API_BASE points at the Search
service.

The earlier lifecycle commit made this look covered. Its test asserts
that POST /indexes?api-version=... is refused with "Only proxy admins can
create", but it calls the permission gate directly, and that gate is
exactly what the route skips for a path with no index name, so the guard
was verified in isolation while the route stayed open.

Gate the service-level create on the route itself, before the segment
loop, with assert_proxy_admin_for_vector_store_index_management. Scope it
to POST on a path whose last segment is indexes, mirroring the
endswith("/indexes") branch the lifecycle helper already uses, so the
managed-index paths and ordinary Azure OpenAI passthrough traffic are
untouched.

Add route-level tests: a non-admin is refused with the admin-only message
and never reaches the passthrough handler, an admin still creates, and the
new predicate is parametrized over the service-level, per-index, and
non-Search paths.
2026-08-13 17:56:57 +00:00
Noah Nistler
c1125f0abb fix(azure_ai): classify Search suggest, autocomplete, and analyze as reads
The endpoint map covered document reads through the ("GET", "/indexes/")
entry plus POST /docs/search, which left Azure's remaining POST query
endpoints unclassified. POST /docs/suggest, POST /docs/autocomplete, and
POST /analyze matched neither list, so the permission gate resolved
permission_type to None and raised 403 before the caller's
allowed_vector_store_indexes grant was consulted; a non-admin team with a
read grant on the index still could not call them.

Add the three as reads. They are query endpoints that never mutate the
index, so a read grant is the right gate, and each needs its own literal
entry because the write entry also matches on POST.

Keep every pattern literal rather than a {placeholder} template: the
matcher falls back to the substring before a {, which for these routes is
always /indexes/, and reads are matched before writes, so a templated
read would shadow the /docs/index write and let a read-only team upload.

Extend the regression tests to the full non-lifecycle read surface
(stats, GET-form search, $count, point lookup, and both forms of suggest
and autocomplete, plus analyze), asserting a read grant reaches all of
them and a write-only grant reaches none.
2026-08-13 17:56:57 +00:00
Noah Nistler
bdc80b11ac fix(azure_ai): authorize the targeted Search index, not any matching path segment
The Azure passthrough scanned every URL segment for one matching a registered
index, authorized against that, then forwarded the original path. A caller with
a grant on a managed index named e.g. "index" or "docs" could send
POST /azure_ai/indexes/{victim}/docs/index: the scan matched the trailing
segment and authorized on the caller's own index while Azure applied the batch
write to {victim} on the same Search service, enabling cross-index document
uploads or deletions.

Resolve the index positionally from the /indexes/{name} segment and require
that exact name to be the one authorized and credentialed, so the authorized
index and the physical target can never diverge. Add a pure helper plus
regression tests covering positional extraction and the route-level cross-index
attack.
2026-08-13 17:56:57 +00:00
Noah Nistler
23f50e1f34 fix(vector_stores): classify POST /indexes create as admin-only lifecycle with query string
The service-level index-create guard checked normalized.endswith("/indexes")
without stripping the query string, so Azure's real create request
POST /indexes?api-version=... was never classified as a lifecycle request and
fell through to the generic permission check instead of the explicit admin-only
guard. Strip the query string before the suffix check, mirroring how the
PUT/DELETE index paths already tolerate a trailing ?.

Add the POST create path to the lifecycle regression parametrize so a non-admin
team with a write grant is denied with the clear admin-only message.
2026-08-13 17:56:57 +00:00
Noah Nistler
83efa9f630 fix(azure_ai): recognize real Search doc endpoints so teams can read/write via passthrough
The Azure AI Search vector store config declared its write endpoint as
`PUT /docs` and its read endpoints as only `/docs/search`. The passthrough
permission gate (`is_allowed_to_call_vector_store_endpoint`) derives a
read/write permission type by matching the request route against those
lists, and a route matching neither resolves to `None` and raises a 403
before the caller's `allowed_vector_store_indexes` grant is ever checked.

Two real Azure routes fell through that gap for non-admins: document
upload/merge/delete is `POST /docs/index` (not `PUT /docs`), and get
index details is `GET /indexes/{name}` (no `/docs/search` suffix). So a
team with a valid write or read grant still got 403 on upload and on
reading index details, while admins slipped through because they skip the
gate entirely.

Correct the map: read is any GET under `/indexes/` (get details, stats,
count, and the GET form of search) plus `POST /docs/search`; write is
`POST /docs/index`. Index lifecycle (create/update/delete the index
itself) stays proxy-admin only because it is handled first by the
separate lifecycle check on POST/PUT/DELETE/PATCH, so this does not let a
team create or delete indexes.

Add regression tests that exercise the real AzureAIVectorStoreConfig map:
a write-granted team may upload, a read-granted team may search and get
index details, a team missing the matching grant is still denied, and a
team cannot manage index lifecycle even with a write grant.
2026-08-13 17:56:57 +00:00
mateo-berri
d9020795dd test: use _local_model_cost_map fixture in gemini 3.7 flash pricing tests 2026-08-13 10:13:40 -07:00
mateo-berri
d3d259b211 feat(gemini): day-0 pricing for gemini-3.7-flash 2026-08-13 09:49:55 -07:00
Daniel Meismer
ef1b4d09e2 test: remove unrelated session log assertion
Drop a stray assertion against a field that is not present in the session
pagination fixture.

Co-Authored-By: Codex
2026-08-13 12:27:55 -04:00
Daniel Meismer
fac2b6b56b refactor: derive request log scope immutably
Resolve the authorized own-user and permitted-team predicates once and add
regression coverage for explicit-user intersection, unfiltered team scope,
and team lookup failure fallback.

Co-Authored-By: Codex
2026-08-13 12:12:05 -04:00
Daniel Meismer
297fe272ec feat: scope request log user filter
Add a bounded spend-log user facet for the Request Logs picker and
intersect explicit user filters with the caller's own and permitted-team
scope.

Co-Authored-By: Codex
2026-08-13 11:50:43 -04:00
yassin
6346497498 fix(ui): add nvidia riva to the model provider list
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-13 13:01:40 +00:00
Daniel Vainshtein
42a2b5f057 fix(bedrock): guard cache-detail split against partial/unrecognized ttl entries
Address review feedback on #36762:
- Only use the parsed 5m/1h split when it fully accounts for
  cacheWriteInputTokens; an unrecognized ttl or missing entry now falls
  back to the aggregate (previous behavior) instead of silently
  understating cost.
- Mark TypedDict fields ReadOnly (AWS response data, never constructed
  by us) to satisfy the repo's type-discipline lint gate.
- Trim comments and add Final to locals per repo style.

Co-Authored-By: pi (Claude/GPT via @earendil-works/pi-coding-agent) <noreply@earendil.works>
2026-08-13 14:06:22 +03:00
Daniel Vainshtein
97290b4e0e fix(bedrock): parse cacheDetails for Converse 1h/5m cache write cost split
AmazonConverseConfig._transform_usage only read the aggregate
cacheWriteInputTokens field, so cache_creation_token_details was always
unset for Bedrock Converse responses. calculate_cache_writing_cost bills
the whole cache-write count at the 5m rate whenever that field is None,
so 1-hour TTL cache writes on the standard Bedrock chat path were always
undercounted, even though Bedrock returns the 5m/1h split in
usage.cacheDetails.

Parse cacheDetails (when present) into CacheCreationTokenDetails so the
correct rate applies to each portion. No cacheDetails in the response
(older models/regions) keeps the previous behavior.

Fixes #36760

Co-Authored-By: pi (Claude/GPT via @earendil-works/pi-coding-agent) <noreply@earendil.works>
2026-08-13 13:44:15 +03:00
yucheng-berri
09889e1986
fix(langfuse): coerce header-sourced mask and trace-update steering values (#36740)
langfuse_* request headers land in metadata as strings, but the trace path reads
mask_input/mask_output with a bare truthiness check and iterates update_trace_keys
directly. A header saying mask_input: false redacted the payload it was asked to
keep, and update_trace_keys was walked one character at a time so every requested
key silently failed to match
2026-08-13 00:44:26 -07:00
yucheng-berri
6f84c468d4
fix(guardrails): scan and re-emit raw Anthropic SSE streams in the bedrock post-call hook (#36598)
* fix(guardrails): scan and re-emit raw Anthropic SSE streams in the bedrock post-call hook

* fix(guardrails): keep upstream id and model on a blocked Anthropic stream

* fix(guardrails): deliver a blocked Anthropic stream as an error frame

* fix(guardrails): deliver an unscannable Anthropic stream as an error frame

* fix(guardrails): emit the guardrail block detail as JSON in the stream error frame

* fix(guardrails): deliver an Anthropic block through the shared block-SSE builder

* fix(guardrails): keep the shared SSE assembler behavior-identical for existing callers

* fix(guardrails): keep the stream error message a string and drop an unreachable branch

* chore(guardrails): drop a comment that repeated its own docstring

* fix(guardrails): let bedrock service failures keep their status instead of framing them as blocks

* fix(guardrails): key the streamed block decision on status, not detail shape

InvokeGuardrailChecks details a Mapping on its 500 for an unparseable response,
so a detail-shape test read that outage as a policy block and framed it as a 200
guardrail_error. Both block sites raise 400, so gate on the status too.

* refactor(guardrails): narrow the SSE error-frame helper to the input it actually takes

Both callers pass a string, so the Mapping overload and its json.dumps branch
were unreachable. Folds the block branch's narrative comment into the rebind
suppressions that already carry a reason.
2026-08-13 07:23:49 +00:00
Mateo Wang
73e555a3e3
test(interactions): follow Google spec drift replacing Turn with typed steps (#36730)
* test(interactions): follow Google spec drift replacing Turn with typed steps

* test(interactions): send step and content-list input to the live Gemini API
2026-08-12 23:32:57 -07:00
mateo-berri
432ea8644b test(interactions): send step and content-list input to the live Gemini API 2026-08-12 21:47:33 -07:00
mateo-berri
0aeed16125 test(interactions): follow Google spec drift replacing Turn with typed steps 2026-08-12 21:47:33 -07:00
mateo-berri
3e94f7d71e fix(interactions): map step and turn input to Responses API roles and content types 2026-08-12 21:47:00 -07:00
mateo
8947008fd2 fix(batches): only retire on a 404 that names the batch
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-13 04:22:43 +00:00
mateo-berri
da84142288 fix(batches): only trust a 404 from the batch's own deployment 2026-08-12 21:19:37 -07:00
yucheng-berri
d86336a7c6
fix(langfuse): emit otel trace version and release on the keys langfuse v4 reads (#36702)
* fix(langfuse): emit otel trace version and release on the keys langfuse v4 reads

The langfuse_otel exporter wrote version to langfuse.generation.version and
langfuse.trace.version, and release to langfuse.trace.release. Langfuse v4
recognizes neither, so both landed in the generic span attribute bag and every
trace reported version and release as null. v4 has a single langfuse.version
key, lifted to the trace when it sits on the root span, plus langfuse.release.

Also routes the otel v2 preset's per-request headers through the shared builder
so key-scoped and team-scoped exports carry x-langfuse-ingestion-version like
the other three exporter paths already do.

* fix(langfuse): give trace_version precedence over version on the shared v4 key

Matches the documented contract in docs/observability/langfuse_integration.md
and the legacy langfuse SDK callback, which both treat trace_version as the
authoritative trace version with version as its fallback.
2026-08-12 20:39:58 -07:00
Devin AI
86b24befc1 fix(proxy): stop discarding failed daily spend transactions before requeue
Some checks failed
LiteLLM Rust / rustfmt, clippy, test (push) Has been cancelled
Terraform Modules / fmt, validate, test (aws) (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
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-13 03:23:27 +00:00
devin-ai-integration[bot]
3864e12415
fix(spend): stop losing spend log rows when a flush is cancelled (#34826) 2026-08-12 20:10:50 -07:00
Devin AI
f3c64029e1 chore: merge litellm_internal_staging into litellm_fix_redis_spend_buffer_requeue_33872
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-13 02:56:14 +00:00
Mateo Wang
fdd72b5b23
Merge pull request #36627 from BerriAI/litellm_fix_autorouter_untagged_hijack
Some checks failed
Unit Tests: MCP, Secrets, Containers & Misc / misc (push) Waiting to run
Unit Tests: Proxy Auth & Key Management / proxy-auth (push) Waiting to run
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Waiting to run
Unit Tests: Proxy DB Operations / auth-checks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / budgets (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / custom-logging (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / db-and-spend (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / key-generation (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / logging-misc (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-runtime (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-server-core (push) Blocked by required conditions
Unit Tests: Proxy DB Operations / proxy-utils (push) Blocked by required conditions
Unit Tests: Proxy API Endpoints / proxy-endpoints (push) Waiting to run
Unit Tests: Proxy API Endpoints / proxy-server (push) Waiting to run
Unit Tests: Proxy Infrastructure / proxy-infra (push) Waiting to run
Unit Tests: Proxy Legacy Tests / auth-and-jwt (push) Waiting to run
Unit Tests: Proxy Legacy Tests / key-generation (push) Waiting to run
Unit Tests: Proxy Legacy Tests / proxy-config (push) Waiting to run
Unit Tests: Proxy Legacy Tests / proxy-response-and-misc (push) Waiting to run
Unit Tests: Proxy Legacy Tests / proxy-server (push) Waiting to run
Unit Tests: Proxy Legacy Tests / proxy-server-extras (push) Waiting to run
Unit Tests: Proxy Legacy Tests / proxy-token-counter (push) Waiting to run
Unit Tests: Proxy Legacy Tests / proxy-user-auth-and-spend (push) Waiting to run
Unit Tests: Proxy Legacy Tests / proxy-utils (push) Waiting to run
Unit Tests: Responses, Caching & Types / responses-caching-types (push) Waiting to run
GitHub Actions Security Analysis / zizmor (push) Waiting to run
Terraform Modules / fmt, validate, test (aws) (push) Has been cancelled
fix(router): let untagged requests bypass a tagged pre-routing strategy on shared model names
2026-08-12 19:48:00 -07:00
mateo-berri
39a5ede2d9 fix(model_prices): bill muse spark web search grounding per query 2026-08-12 19:44:20 -07:00