Commit graph

47584 commits

Author SHA1 Message Date
mateo-berri
41c8969f0a test(router): drive tag routing tests through acompletion until both deployments are seen 2026-09-04 19:54:40 -07:00
mateo-berri
da9dbdba96 fix(realtime): treat any client receive failure as a client hangup
client_ack_messages classified a websockets ConnectionClosed raised by
the client socket as the backend closing, so bidirectional_forward kept
waiting on the upstream instead of ending the session. Starlette clients
raise WebSocketDisconnect, but the realtime test client in
tests/llm_translation/realtime raises websockets.exceptions.ConnectionClosed,
which hung test_openai_realtime_simple.py until the run was killed.

Only the receive_text call now maps every exception to
CLIENT_DISCONNECTED; the loop body keeps ConnectionClosed as
BACKEND_CLOSED, since the backend socket is the only websockets socket
touched there.
2026-09-04 19:52:48 -07:00
mateo-berri
c27f1e348d fix(ui): name the object arguments at two new call sites to bring the inline-object lint budget back under its ceiling 2026-09-04 19:47:31 -07:00
ryan-crabbe-berri
1d375d8ada fix(guardrails): flag unpriced units per team and key, sort unknown cost last
The detail endpoint now returns untracked_usage_units_by_team and
untracked_usage_units_by_key next to the cost breakdowns, and the By team and
By key tables show them in an Unpriced Units column, so a row that pairs its
total units with a partial cost says how many units that cost leaves out.

The overview comparator no longer treats a missing cost as zero: guardrails
with no known cost sort last in both directions instead of mixing in with
genuinely free ones.

Refs LIT-5652
2026-09-04 19:46:14 -07:00
mateo-berri
296cd8c1f5 fix(cli): read CLAUDE_CODE_SESSION_ID and skip subagent transcripts when detecting the Claude Code session 2026-09-04 19:45:42 -07:00
mateo-berri
a90328aa8c refactor(typing): drop the dead self guard in Predibase init and use a plain list factory 2026-09-04 19:42:37 -07:00
mateo-berri
5a7e938f37 test(cost): type the cost map fixture parameter on the new tests 2026-09-04 19:40:46 -07:00
ryan-crabbe-berri
bde3f6ae46 feat(ui): show guardrail usage units and cost on the Guardrails Monitor
The overview table gains Usage Units and Cost columns plus a Guardrail Cost
card, and the detail page gains a Usage & Cost section that breaks units and
cost down by counter, team and key. Units the cost map could not price are
called out next to the cost they are left out of.

Both pages now read /guardrails/usage/* through $api.useQuery so the rows are
typed from schema.d.ts; the hand-written PerformanceRow and the untyped fetch
helpers are gone. fetchClient resolves fetch per request so integration tests
that stub the global see typed-client calls too.

Refs LIT-5652
2026-09-04 19:35:43 -07:00
mateo-berri
a0b2e7fca6 fix(proxy): only a provably missing user row counts as unrestricted in the websocket passthrough gate 2026-09-04 19:35:08 -07:00
mateo-berri
85d45fbb4b fix(realtime): relay the upstream close even when a client message hit the closed socket first
When the upstream closes while the proxy is forwarding a client message,
the client loop ends before the backend relay sees the close, and the
relay skipped closing the client because it read the client loop's exit
as the client hanging up. The client loop now reports why it stopped, so
a close observed on the backend send still reaches the client with the
error event and the upstream close code
2026-09-04 19:33:30 -07:00
mateo-berri
6ee33df952 fix(realtime): relay the upstream websocket close to the client instead of hanging
When the provider closes the realtime websocket (for example Vertex Live
refusing the session with 1008 "Publisher model ... was not found"), the
proxy swallowed the close and kept waiting on the client, so the client
sat on an open socket with nothing coming back and the session was logged
as a $0 success

The backend relay now returns the upstream close, and bidirectional_forward
sends the client an OpenAI-style error event naming the upstream code and
reason, then closes the client socket with the same code (or 1011 when the
upstream code is one a server may not send). A session the upstream refused
before sending any frame is logged through the failure handlers instead of
as a success
2026-09-04 19:25:12 -07:00
mateo-berri
f846388bb1 fix(proxy): treat a missing user row as unrestricted in the websocket passthrough gate 2026-09-04 19:24:30 -07:00
mateo-berri
5bd4da0389 test(health): score the liveliness probe on the median of five warm polls 2026-09-04 19:16:51 -07:00
mateo-berri
02cb3daf26 fix(cli): return debug failures as values, survive transport errors, size report fences to content 2026-09-04 19:15:21 -07:00
mateo-berri
001b531ae9 fix(cost): bill realtime reasoning tokens nested in text_tokens once
OpenAI and Azure realtime usage reports output_tokens == text_tokens + audio_tokens
with reasoning_tokens counted inside text_tokens, so generic_cost_per_token billed
the reasoning share twice. When the output token details sum past completion_tokens,
the nested reasoning overlap is now subtracted from text_tokens before pricing;
shapes where text_tokens already excludes reasoning are unchanged.
2026-09-04 19:11:27 -07:00
mateo-berri
822c8629ae Merge branch 'litellm_internal_staging' of https://github.com/BerriAI/litellm into litellm_deflake_20260902
# Conflicts:
#	tests/test_litellm/caching/test_redis_cache.py
2026-09-04 19:06:33 -07:00
mateo-berri
2a7fc8de01 fix(proxy): keep the token's team model list in the websocket passthrough gate without a database 2026-09-04 19:05:35 -07:00
ryan-crabbe-berri
2151dcbd73
Merge pull request #39822 from BerriAI/litellm_lit_6594_access_group_resource_names
feat(access-groups): resolve resource names on access group responses
2026-09-04 19:03:20 -07:00
tin-berri
d8ca43a800
feat(complexity_router): let the LLM classifier see request images (#39825)
The classifier scores extracted text, so a turn whose complexity lives in
its image is invisible to it: a screenshot of a stack trace classifies on
its caption, and an image-only turn flattens to empty text and never
reaches the classifier at all.

classifier_llm_config.vision opts in, off by default, with max_images
bounding what one turn can add. Images are still dropped when the
classifier model is declared supports_vision false. Anthropic and
Responses image parts are rewritten into chat-completions dialect before
they reach the classifier call, since /v1/messages hands the pre-routing
hook its own dialect untranslated.

The local scorer no longer short-circuits heuristic_first or hybrid on a
turn carrying forwarded images, because it reads text alone and its
confidence describes a request it has only partly seen.
2026-09-04 18:59:50 -07:00
mateo-berri
9c068117e7 chore: merge origin/litellm_internal_staging into litellm_decrease_anys_opus5_r4 2026-09-04 18:59:35 -07:00
mateo-berri
0157808187 fix(fireworks_ai): keep file_search on LiteLLM's emulated search for the Responses API 2026-09-04 18:58:09 -07:00
moe-berri
d1fd3a3457 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_shadow_eval_judge_output_cap
# Conflicts:
#	tests/test_litellm/integrations/test_shadow_eval_logger.py
2026-09-04 18:57:19 -07:00
mateo-berri
a61bead287 test(store_model_in_db): accept both 400 shapes in the unknown-model spend log test 2026-09-04 18:52:17 -07:00
mateo-berri
567915aeee fix(image_handling): inline url-sourced Anthropic document and image blocks in the async walker 2026-09-04 18:51:14 -07:00
mateo-berri
ee50f2bc44 test(e2e/batches): run the list assertion when a batch completes before cancel
The completed-batch early return skipped both the cancel and the list
assertion while the lifecycle's covers markers still credited both cells.
List does not depend on the batch being cancellable, so it now runs either
way; cancel on a completed batch stays a documented vacuous pass
2026-09-04 18:50:47 -07:00
Mateo Wang
2f90a264f6
Merge pull request #39827 from BerriAI/litellm_azure_gpt_6_astra
feat(cost-map): add azure/gpt-6-astra and azure/us/gpt-6-astra Foundry pricing
2026-09-04 18:48:51 -07:00
ryan-crabbe-berri
53d7b45e87
Merge pull request #38703 from BerriAI/litellm_fix_stale_team_on_user_row
fix(team_endpoints): let member_delete clear a team left on the user row
2026-09-04 18:48:07 -07:00
mateo-berri
d4fd658891 fix(datadog_llm_obs): keep guardrail_cost_by_unit on redacted spans 2026-09-04 18:47:48 -07:00
moe-berri
639b3f4f62
Merge pull request #39809 from BerriAI/litellm_stall_escalation
feat(router): auto-escalate stalled complexity-router tasks
2026-09-04 18:44:25 -07:00
moe-berri
2c3c7dd1a6
feat(shadow_eval): judge tool-call turns instead of dropping or erroring on them (#39818)
* fix(shadow_eval): tell a tool-call shadow reply apart from an empty one

Both arrive at the attempt row as the same 'shadow router returned an empty
response', because _chat_final_text returns empty for a tool-final turn by
design and for a reply that genuinely carried no text. Those are different
things: an arm that chose a tool where the real model wrote prose is a
divergence a text judge cannot score, and the sampling side already drops the
real arm's tool-final turns for exactly that reason, so the shadow side reads
as a fault where the real side reads as a filter. A job that is almost all
'empty response' gives no way to tell a tool-happy arm from a broken one.

The error now names which of the two happened, and carries the finish_reason
and the routed model so the row says what the arm was doing. Every varying
part sits behind the first semicolon: operators read these by grouping on the
error text, and interpolating the model into the leading sentence would make
each row its own group.

The outcome stays 'error'. Whether a tool-call reply should instead be its own
non-judged outcome, excluded from the loss rate the way the real arm's
tool-final turns already are, needs the four aggregation predicates that spell
judged as outcome != 'error' rewritten, and a decision on how to surface the
new bucket. That is a separate change.

* fix(shadow_eval): read the tool name of a custom tool call

A custom tool call carries its name under custom.name with no function key,
so every one of them reported as tool=unnamed.

* feat(shadow_eval): judge tool calls instead of dropping the turn

A turn where either arm called a tool was discarded before it could be
compared: the real arm's at sampling, the shadow arm's as an error row. On
agentic traffic that is most of the traffic, so a job set to sample 10% was
sampling 10% of the prose-only slice. Tool calls now serialize to text on
every surface and are judged like any other response, and the judge is told
a tool call is not a defect so it scores the choice rather than the shape.

* feat(shadow_eval): show the judge what tools were available

Both arms were offered the same tools, but the judge only ever saw the
chosen call in isolation, with no way to tell whether a better tool existed
or the arguments matched what the tool expects. Threads the request's tool
definitions (name and description only) into the judge prompt, capped and
omitted entirely on turns that offered none.

* fix(shadow_eval): read a custom tool definition's name from custom, not function

A chat-completions custom tool definition nests name and description under
custom, mirroring how a custom tool call nests them (openai.types.chat.
ChatCompletionCustomToolParam). Reading only function rendered every one as
unnamed, telling the judge nothing about what it was.
2026-09-04 18:41:47 -07:00
yucheng-berri
853fed824e
fix(bedrock): stop sending toolConfig tool definitions to guardrails on passthrough converse (#39281)
Bedrock passthrough Converse routes flattened every non-empty string under
toolConfig.tools into the guardrail INPUT texts, so tool names, tool
descriptions and JSON-schema strings (object, property names, titles, type
names, enum values) each arrived as a separate guardrail item. A request whose
only prompt was one benign user message could be blocked outright because a
denied term appeared in an app-authored tool definition.

Tool definitions are now excluded from the extracted texts, matching every
other guardrail translation handler, which carries tool definitions in the
structured tools input rather than in texts. Caller content stays scanned:
message text, toolUse.input, toolResult content and json, and
additionalModelRequestFields are unchanged.

Resolves LIT-5797
2026-09-04 18:40:35 -07:00
mateo-berri
3b9568bcdd Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_lit_7015_migration_job_node_selector 2026-09-04 18:40:25 -07:00
mateo-berri
7351911b53 fix(proxy): refuse OpenAI websocket passthrough on every enforced model allowlist and propagate the DB opt-in 2026-09-04 18:39:56 -07:00
mateo-berri
1748dd81a7 docs(e2e/batches): say the unified Bedrock lifecycle lists with plain GET /v1/batches 2026-09-04 18:39:28 -07:00
moe-berri
88ada40cda fix(type-checking): satisfy the basedpyright budget gate for auto-router compression
Two fixes for the zero-headroom basedpyright budget:

- arm_pre_call's data parameter is dict[str, object], not MutableMapping: the
  latter is itself banned by LIT001 with no benefit, and it mismatched every
  dict-typed helper (get_or_create_metadata_bucket, resolve_structured_messages,
  _get_tags_from_request_kwargs), which is what the budget was actually flagging.
- Router.async_pre_routing_hook computed pre_routing_hook_response in one shot
  instead of reassigning a Final-annotated local.

The remaining two reportArgumentType hits are pre-existing: LiteLLM_Params(**merged)
in _create_deployment_object already fails this check for all ~165 of its other
fields, since the merged dict's value type is partly untyped/float; adding two new
string fields to the model just grows that existing pile by two. Suppressed at the
one call site with a reason, since fixing the root typing is out of scope here.
2026-09-04 18:37:38 -07:00
Mateo Wang
77e27b1866
Merge pull request #39780 from BerriAI/litellm_/goofy-bohr-6cd011
fix(proxy): strip every TypedDict qualifier before numeric form-field detection
2026-09-04 18:36:54 -07:00
Mateo Wang
3d08daecfe
Merge pull request #39729 from amasen02/fix/end-user-budget-reset-cache-invalidation-39726
fix(proxy): invalidate end-user spend counter and cache on budget reset (#39726)
2026-09-04 18:36:05 -07:00
mateo-berri
a534b9fac5 fix(fireworks_ai): send developer input items as system messages on the native responses path 2026-09-04 18:35:23 -07:00
mateo-berri
f022b5eda8 test(e2e/batches): assert Bedrock batch cancel and list in the lifecycle
Bedrock batch cancel (StopModelInvocationJob) and the managed list view
both work through the proxy since LIT-4774, but the batches e2e still
gated them off and the coverage registry claimed no cell for either.
Flip can_cancel/can_list for the Bedrock provider, assert cancel the
same way the OpenAI leg does, add the two registry cells the gates
select, and update COVERAGE.md
2026-09-04 18:30:55 -07:00
mateo-berri
2674934e45 feat(helm): render nodeSelector, tolerations, and affinity on the componentized chart migrations Job 2026-09-04 18:28:53 -07:00
mateo-berri
08bb7de868 fix(image_handling): cap in-flight remote media fetches per request 2026-09-04 18:28:17 -07:00
mateo-berri
c4759a9d98 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_techdebt_20260903 2026-09-04 18:27:43 -07:00
yucheng-berri
e2741b5643
fix(datadog_llm_obs): keep the guardrail audit record under message redaction (#39702)
* fix(datadog_llm_obs): keep the guardrail audit record under message redaction

Redaction nulled `guardrail_information` on the span whole, so an operator
running `turn_off_message_logging` (or a caller sending
`x-litellm-enable-message-redaction`) lost the record of which guardrails ran,
what they returned, and what they masked. Four of the record's fields can quote
the prompt; the rest report what the guardrail decided without reproducing it.

Replace only those four, the way
`_sanitize_guardrail_information_for_spend_logs` already does for spend logs,
and declare the field list once in `litellm/types/utils.py` so both readers
share it.

* fix(datadog_llm_obs): keep a lone guardrail record, and test through the span

Review round 1.

A guardrail that writes the metadata key itself leaves a single record where
the type says list, which Prometheus already normalizes at
`_guardrail_overhead_seconds`. Redaction dropped that shape and the latency
extraction raised on it, so the span was lost outright. Normalize once and use
it in both places.

The new tests now drive `create_llm_obs_payload` instead of reading the module's
private helpers and the record's declared field names.
2026-09-04 18:24:16 -07:00
mateo-berri
f27699a1c9 test(store_model_in_db): assert the 400 contract in the unknown-model spend log test 2026-09-04 18:23:36 -07:00
Mateo Wang
41c8c2f410
Merge pull request #39763 from BerriAI/litellm_fireworks_tool_choice_short_name
fix(fireworks_ai): resolve tool_choice and reasoning support for short model names
2026-09-04 18:22:25 -07:00
yuneng-jiang
e733ca1065
Merge pull request #39811 from BerriAI/litellm_/mongodb-vector-store-e4ff63
feat(vector_stores): add a MongoDB vector store provider for Atlas and self-managed deployments
2026-09-04 18:19:03 -07:00
mateo-berri
a2d5215a4f fix(proxy): gate the OpenAI websocket passthrough behind an explicit opt-in 2026-09-04 18:14:44 -07:00
mateo-berri
cd113c3a2e ci: allowlist the bounded _unqualified qualifier peel in the recursion detector 2026-09-04 18:13:51 -07:00
mateo-berri
e355203014 fix(gemini): keep Files API refs out of the async remote media walker
Gemini AI Studio file URIs under generativelanguage.googleapis.com/v1beta/files/
answer 403 when fetched and must pass through as file_data.file_uri, which the
sync transform already did. Give the shared walker a skip_url_prefixes parameter,
pass that prefix from the Gemini body builder, and skip it in the AI Studio
message transform for both image_url and file parts
2026-09-04 18:11:41 -07:00
mateo-berri
270452db79 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/goofy-bohr-6cd011 2026-09-04 18:09:57 -07:00