Commit graph

10548 commits

Author SHA1 Message Date
Devin AI
9c410fffb4 fix(guardrails): strip credential headers on cross-origin redirects in async_safe_request 2026-07-11 11:32:48 +00:00
Devin AI
d9292e129b test: cover too-many-redirects path in async_safe_request 2026-07-11 11:21:56 +00:00
Devin AI
7e5e5d424f fix(guardrails): apply SSRF protection to custom code http_request() primitive 2026-07-11 11:15:30 +00:00
Mateo Wang
c15891fc98
fix(bedrock): flag mapped Claude 4.8+ entries with supports_mid_conversation_system (#32882)
Exact cost-map hits resolve before fallback-generalization rules, so the
mapped Sonnet 5, Fable 5 and jp Opus 4.8 Bedrock entries bypassed the
bedrock-anthropic-claude-mid-conversation-system rule and hoisted
mid-conversation system messages, invalidating the prompt cache.
2026-07-10 22:51:41 -07:00
Mateo Wang
735a5bfc5b
test(models): assert capability fields on regional Azure gpt-5.6 entries (#32875) 2026-07-10 21:10:36 -07:00
Mateo Wang
3a62e5428f
fix(anthropic): translate adaptive thinking/effort to pre-4.6 model support (#32867)
* fix(anthropic): translate adaptive thinking/effort to pre-4.6 model support

AnthropicMessagesConfig now reshapes the 4.6+ adaptive-thinking interface
(thinking:{type:adaptive} + output_config:{effort:...}) to whatever the routed
model supports. Thinking-capable non-adaptive models (e.g. Haiku 4.5, Sonnet 4.5)
get the effort translated to a legacy thinking budget_tokens. Models with no
reasoning support have thinking/effort dropped under drop_params. And because
adaptive thinking carries no budget while the legacy form must satisfy Anthropic's
max_tokens > budget_tokens rule, the translated budget is capped below max_tokens,
dropping thinking when max_tokens can't fit the minimum budget. 4.6+ models pass
through untouched.

This matters because clients like Claude Code speak native Anthropic /v1/messages
and send the adaptive interface unconditionally, regardless of the routed model.
The native passthrough previously only capability-gated the OpenAI-style
reasoning_effort alias and forwarded native output_config/adaptive thinking raw, so
a pre-4.6 model rejected it with "This model does not support the effort parameter"
and the request failed. Claude Code already gets drop_params auto-set, so its
requests now succeed.

* test(anthropic): gate undersized-max_tokens thinking drop on drop_params; add edge tests

Addresses review feedback on the max_tokens-too-small branch. Previously a
thinking-capable model whose max_tokens could not fit the minimum thinking budget
had thinking silently dropped regardless of drop_params, while a residual
output_config field in the same call still raised when drop_params was off. Gate
both consistently on drop_params: raise a clear error (naming max_tokens for the
undersized case) when drop_params is off, drop otherwise. Claude Code gets
drop_params auto-set, so it still succeeds.

Adds tests for the undersized-max_tokens raise, the residual output_config raise,
and the no-adaptive-interface passthrough on a non-adaptive model.

* fix(anthropic): make adaptive-effort translation silent to avoid breaking provider strip contracts

The previous raise-when-not-drop_params behavior broke existing bedrock and vertex
messages tests: those providers already silently strip unsupported output_config
for pre-4.6 models (issue #22797) with no drop_params required, and the shared
parent transform raising pre-empted that. It also conflicted with the goal of
keeping requests working rather than failing them.

Make the reshape silent: translate effort to legacy thinking for thinking-capable
models, drop thinking for non-reasoning models, and remove only the consumed effort
key from output_config, leaving any residual (e.g. format) for provider subclasses
(bedrock/vertex) to handle. No raise, no drop_params gating. This also resolves the
review note about inconsistent drop_params handling by making every path uniform.

Updates the tests to assert the silent behavior and residual output_config
preservation.

* fix(anthropic): handle output_config-capable but non-adaptive models (Opus 4.5)

Greptile caught a real bug: the early-return guard treated supports_output_config
as equivalent to supporting adaptive thinking. Claude Opus 4.5 advertises
supports_output_config (it accepts output_config.effort) but is not adaptive, so it
rejects thinking:{type:adaptive} with "adaptive thinking is not supported on this
model". The guard early-returned for Opus 4.5 and forwarded the adaptive thinking
block raw, reproducing the exact failure the fix is meant to prevent.

thinking:{type:adaptive} and output_config.effort are independent capabilities.
Only early-return for adaptive-thinking models. For a model that supports
output_config.effort but is not adaptive, keep the native effort and drop only the
unsupported adaptive thinking block. Verified live against Opus 4.5: the Claude Code
payload now returns 200 instead of 400.

Adds regression tests for Opus 4.5 with and without adaptive thinking.

* fix(anthropic): translate adaptive thinking for effort-capable pre-4.6 models

Claude Opus 4.5 advertises supports_output_config but not adaptive thinking,
so the early-return guard forwarded thinking.type=adaptive raw and Anthropic
rejected it. The guard now only skips true adaptive models; effort-only
requests on effort-capable models still pass through untouched. The
_map_reasoning_effort call is wrapped to surface unrecognized effort values
as a clean 400, matching _translate_reasoning_effort_to_anthropic

* fix(anthropic): fall back to legacy thinking when effort level unsupported

Opus 4.5 accepts output_config.effort but only low/medium/high; Claude Code
defaults to xhigh on newer models, so preserving that level raw gets rejected
by Anthropic. Gate the native-effort passthrough on _validate_effort_for_model
and fall through to the budget translation for unsupported levels

* fix(anthropic): keep effort-only requests untouched for provider normalization

The xhigh fall-through consumed effort-only requests on effort-capable
models, breaking bedrock invoke's own normalization which clamps xhigh to
the model's ceiling after the base transform runs
(test_bedrock_messages_normalizes_output_config_effort_for_opus). Restrict
the fall-through to requests that carry adaptive thinking; effort-only
requests pass through so provider subclasses keep owning level clamping

---------

Co-authored-by: Abhimanyu Kapur <38531241+akapur99@users.noreply.github.com>
2026-07-10 21:04:46 -07:00
yucheng-berri
60d557c930
fix(datadog): split log batches proactively under intake payload limits (#32860)
* fix(datadog): split log batches proactively under intake payload limits

* fix(datadog): size intake chunks with exact wire serialization
2026-07-10 20:54:42 -07:00
Mateo Wang
4baf326a39
fix(anthropic): strip @version suffix in _model_map_lookup_candidates (#32833)
vertex_ai/claude-opus-4-8@default (and sibling @default models) were
misclassified as non-adaptive because _model_map_lookup_candidates only
stripped provider prefixes but never the @<suffix> portion. The lookup
produced candidates like ["vertex_ai/claude-opus-4-8@default",
"claude-opus-4-8@default"], neither of which exists in model_cost, so
_is_adaptive_thinking_model returned False. LiteLLM then sent
thinking.type=enabled to a @default Vertex AI endpoint that requires
thinking.type=adaptive, resulting in a 400.

_strip_version_suffix now removes @<suffix> from each candidate,
adding the bare model name (e.g. "claude-opus-4-8") to the lookup
chain. Also adds supports_adaptive_thinking: true to the three
@default model_cost entries that were missing it as belt-and-suspenders.

Co-authored-by: Deepanshu <deepanshu.lulla@alpha-sense.com>
2026-07-10 20:25:56 -07:00
Mateo Wang
1ab1515d9e
fix(prometheus): skip budget metric DB lookups when gauges are NoOpMetric (#32834)
adds a top-level guard in _increment_remaining_budget_metrics that returns early
when all four budget gauges are NoOpMetric (excluded from prometheus_metrics_config),
and per-entity guards in each _set_*_budget_metrics_after_api_request helper for
partial disabling. eliminates four async DB/cache round-trips per successful LLM
request when budget metrics are disabled.

Co-authored-by: Deepanshu <deepanshu.lulla@alpha-sense.com>
2026-07-10 20:25:47 -07:00
Mateo Wang
249a999506
fix(responses-api): raise APIError on in-stream error events; widen ErrorEventError.param to accept dict (#32835)
* fix(responses-api): raise APIError on in-stream error events; widen ErrorEventError.param

- BaseResponsesAPIStreamingIterator._maybe_raise_for_error_event inspects each
  chunk and raises litellm.APIError for type=error and type=response.failed events
  so callers see an exception instead of a benign stream chunk
- rate_limit* codes map to 429; client error codes (invalid_request_error,
  context_length_exceeded, etc.) map to 400; all other codes default to 500;
  raw integer codes are never used as-is as HTTP status codes
- ErrorEventError.param widened from Optional[str] to Optional[Union[str, Dict]]
  to prevent Pydantic ValidationError on dict-typed param payloads silently
  dropping error events before any type inspection

* test(responses-api): add streaming iterator error event tests to CI-covered path

* test(responses-api): cover response.failed, dict-error, null-error, and sync iterator paths

* test(responses-api): set completion_start_time on mock logging objects for internal staging _process_chunk

* fix(responses-api): map insufficient_quota to 429, derive failed-response log status from error code, and record failed-stream usage for spend accounting

insufficient_quota moves out of the 400 bucket; OpenAI returns HTTP 429 for it and the non-streaming exception mapping treats 429 as RateLimitError, so the in-stream mapping now agrees

_handle_logging_failed_response previously hardcoded APIError(status_code=500), so a rate-limited response.failed was logged to integrations as 500 while the caller saw 429; it now shares the same error-code-to-status mapping via _error_event_fields and _status_code_for_error_code

usage carried on a response.failed event is now stashed as combined_usage_object with its computed cost on the logging object before failure handlers run, reusing the mid-stream-interruption spend recovery path (_failure_handler_helper_fn, proxy post_call_failure_hook, _ProxyDBLogger), so failed streams count their billed tokens instead of logging zero cost

dedupe: TestMaybeRaiseForErrorEvent in tests/llm_responses_api_testing duplicated tests/test_litellm/responses/test_streaming_iterator_error_events.py, which is the canonical mirrored location and CI-covered via test-unit-responses-caching-types; the duplicate class is removed

* fix(responses-api): wrap retriable in-stream errors in MidStreamFallbackError and map error type field to status

Mirror chat streaming semantics from _handle_stream_fallback_error: 429 and
5xx in-stream error events now raise MidStreamFallbackError carrying the
mapped APIError so the router's FallbackResponsesStreamWrapper triggers
mid-stream fallback and cooldown; non-retriable 4xx still raise APIError
directly. Status mapping now reads both the OpenAI error type and code
fields, so type-classified client errors (e.g. invalid_request_error with
code invalid_prompt) map to 400 instead of falling through to 500.

* fix(responses-api): accumulate streamed output text so mid-stream fallback continues instead of restarting

MidStreamFallbackError was always raised with generated_content="", so the
router's stream_with_fallbacks treated every mid-stream error as pre-first-chunk
and retried with the original input, streaming duplicated content to clients
that had already received partial output. The iterators now accumulate
response.output_text.delta text (mirroring chat's response_uptil_now) and pass
it as generated_content, letting the router build a continuation input via
_build_responses_continuation_input.

* test(responses-api): pin in-stream token limit error to raised APIError

---------

Co-authored-by: Deepanshu <deepanshu.lulla@alpha-sense.com>
2026-07-10 20:25:38 -07:00
Mateo Wang
5e23a5ab05
fix(bedrock): gate in-place system role messages on model support for Claude Invoke (#32831)
* fix(bedrock): gate in-place system role messages on model support for Claude Invoke

* feat(bedrock): default unmapped Claude 4.8+ to in-place system role handling via fallback rule
2026-07-10 20:21:48 -07:00
Mateo Wang
ee6e8077ae
fix(responses): preserve reasoning_tokens through chat->responses usage translation (#32837)
* fix(responses): preserve reasoning_tokens through chat->responses usage translation

Remove the unconditional else-branch that wrote reasoning_tokens=0 whenever
completion_tokens_details.reasoning_tokens was None or absent. Also change
OutputTokensDetails.reasoning_tokens from int=0 to Optional[int]=None so that
re-instantiation without explicit reasoning_tokens no longer silently zeroes out
the field, and remove the same hardcoded zero from the mock_responses_api_response
initializer.

* test(responses): update assertions to match Optional[int] reasoning_tokens default

* fix(responses): preserve explicit reasoning_tokens=0 in usage translation

Align the reasoning_tokens guard with the is-not-None guards used for
text_tokens and image_tokens: a provider-reported zero passes through
while an absent value stays omitted.

---------

Co-authored-by: Deepanshu <deepanshu.lulla@alpha-sense.com>
2026-07-10 20:15:49 -07:00
Mateo Wang
4737e75c86
fix(bedrock): add jp.anthropic.claude-opus-4-8 to model cost map (#32840)
* fix(bedrock): add jp.anthropic.claude-opus-4-8 to model cost map

* test: use apac regional profile for cost-map fallback test since jp now has an entry
2026-07-10 20:05:37 -07:00
Krrish Dholakia
109193f26a
feat(router): add LLM-based classifier option to complexity router (#32169)
* feat(router): add LLM-based classifier option to complexity router

Adds classifier_type: "heuristic" | "llm" to complexity_router_config.
When set to "llm", the router calls a configured model (e.g. a small
model like haiku) via structured output to pick the complexity tier,
falling back to the existing regex/keyword scorer on any error, empty
response, or unparseable output.

* feat(ui): add classifier_type option to complexity router UI, fix edit flow

Adds an "Advanced: Classification Method" section to ComplexityRouterConfig
with a heuristic/LLM toggle, revealing a classifier model picker and timeout
when LLM is selected.

Also fixes the auto router edit modal, which never rendered the complexity
router UI at all (it only handled the semantic router), and the "Edit Auto
Router" button visibility check, which was gated on auto_router_config and
never matched complexity router deployments.

* fix(router): attribute classifier calls to caller, raise default timeout

Forwards the original request's litellm_metadata into the classifier's
acompletion call. Without it, the proxy's cost-tracking gate sees no
user_api_key/team_id/user_id and silently drops spend logging and budget
accounting for every classifier call, letting an authenticated user rack
up unaccounted provider spend via repeated requests.

Also raises the default classifier timeout from 400ms to 3000ms (400ms
undershoots real LLM latency and would silently degrade to the heuristic
scorer on most requests) and corrects the module/class docstrings, which
still claimed zero external API calls after the llm classifier path was
added.

* fix(ci): resolve ruff strict-budget and frontend-lint failures

- Use PEP 585 generics (dict/tuple/list) in the new aclassify/_classify_with_llm
  signatures instead of typing.Dict/Tuple/List, and suppress BLE001 on the
  intentionally broad except in aclassify's fallback path with a reason.
- Fix prettier formatting in ComplexityRouterConfig.tsx.
- Regenerate eslint-metrics.json (was stale after the classifier UI changes).

* fix(ci): regenerate stale eslint-metrics.json

* fix(router): strip parent budget reservation from classifier metadata

The classifier's internal acompletion call previously forwarded the
parent request's full litellm_metadata, including its budget
reservation (user_api_key_budget_reservation / user_api_key_auth).
That reservation belongs to the routed completion the classifier is
deciding on, not to the classifier call itself, so it's now stripped
while key/team attribution fields are still forwarded for spend
logging.
2026-07-10 18:55:34 -07:00
Krrish Dholakia
dacf1cfb26
fix: strip trailing slash from --base-url in lite CLI (#32845)
A trailing slash on --base-url (or LITELLM_PROXY_URL) produced
double-slash URLs like https://host//sso/cli/start, which 404s. Normalize
once in the CLI's top-level group callback so every subcommand benefits.
2026-07-10 18:00:00 -07:00
yucheng-berri
14338a2471
fix(redis): only use SSLConnection when ssl is truthy in connection pool kwargs (#32825) 2026-07-10 17:16:10 -07:00
tin-berri
ad224e74a8
Merge pull request #32847 from BerriAI/litellm_liccheck_authorize_expression
ci: authorize the expression package (MIT) in liccheck.ini
2026-07-10 17:13:58 -07:00
tin-berri
5205af9d13
Merge pull request #32815 from BerriAI/litellm_mcp_credential_class_merge
fix(mcp): merge credentials within the client-forwarded class on an auth-type switch
2026-07-10 17:06:31 -07:00
yucheng-berri
51f6e1b7a5
fix(proxy): match multi-mode guardrail_mode without false-COMPLIANT (#32832)
* fix(proxy): match list/dict guardrail_mode in compliance mode checks

* test(compliance): cover ComplianceChecker guardrail_mode shapes (str/list/dict/None)

* fix(proxy): trust only Mode.default in compliance mode matching (ignore tag overrides)

* fix(proxy): match dict guardrail_mode only when every branch runs in mode (no false-compliant)

* fix(proxy): treat multi-mode guardrail_mode as unresolved (no false-compliant)

The list branch previously counted a guardrail configured with mode:
[pre_call, post_call] under every listed mode. But when the writer cannot
infer the concrete hook that fired (apply_guardrail invocations), the raw
list is logged, and an image-only request that only reaches the post-call
path still records both modes. That let a pre_call compliance check pass on
a request that only ran post_call.

Match the tightened dict semantics: a list now counts for mode only when
every listed mode equals mode. Same trade-off (under-report instead of
false-COMPLIANT). Speculative set support is dropped (spend logs are
JSON-serialized, sets do not cross the wire).

Tests updated to reflect the tightened list semantics, deduplicated (single
TestModeMatching class), and shortened. The invariant is now expressed as
a computed check: True implies every branch runs in the matched mode.

---------

Co-authored-by: Marton Schneider <marton@schneider.co.nl>
2026-07-10 16:41:28 -07:00
tin-berri
8f24f2f767
Merge pull request #32794 from BerriAI/litellm_lit4338_envelope_edge_consumer
feat(mcp): add dcr_bridge envelope consumer helpers
2026-07-10 16:31:36 -07:00
Tin Chi Lo
e12f28ebed ci: authorize the expression package (MIT) in liccheck.ini 2026-07-10 16:19:08 -07:00
Yassin Kortam
3ea7f98725
feat(proxy): configure the coordination redis independently of the response cache (#32661)
* fix(proxy): build redis usage cache from REDIS_* env when cache backend is not Redis

Selecting a semantic (or any non-Redis-KV) response cache left
redis_usage_cache unset, silently downgrading cross-pod rate limits,
parallel-request limits, spend coordination, and the pod lock manager
to per-pod in-memory state. Fall back to a standalone RedisCache built
from REDIS_* environment variables, mirroring the existing
use_redis_transaction_buffer escape hatch, which now shares the same
helper.

Resolves LIT-3861

* feat(proxy): configure the coordination redis independently of the response cache

Adds general_settings.coordination_redis, an explicit block for the Redis
the proxy uses for cross-pod rate limits, parallel-request limits, spend
tracking, the pod lock manager, and shared health checks. Resolution order
is the explicit block, then a plain-Redis response-cache backend, then the
REDIS_* environment. Cluster and sentinel targets are supported, and a
cluster target now builds a RedisClusterCache so cluster-aware consumers
take the cluster path.

Admins can configure it from the Caching page of the dashboard via
/coordination_redis/settings, which reports which source is in effect,
redacts credentials on read, and offers a connection test. Settings saved
there are read back at startup so they take effect on restart.

Also fixes redis client construction so an explicitly configured host
outranks REDIS_URL in the environment. Previously the url branch stripped
the caller's host and port, so an explicit block, or a connection test
typed into the dashboard, silently targeted whatever REDIS_URL named

* fix(ui): move coordination_redis_settings into renamed _components directory

---------

Co-authored-by: Yucheng Zhu <yucheng@berri.ai>
2026-07-10 16:15:59 -07:00
Yassin Kortam
99b4c5ed3e
feat(otel): emit the gen_ai.client.operation.exception event on failed LLM calls (#32655)
* feat(otel): emit the gen_ai.client.operation.exception event on failed LLM calls

The GenAI semantic conventions record failures of a GenAI client operation as
a log-based event named gen_ai.client.operation.exception, carrying the
exception.type / exception.message / exception.stacktrace trio at severity
WARN and correlated to the failed span. OTel v2 never emitted it: a failed LLM
call produced only the deprecated error.* span attributes, a generic exception
span event without a stacktrace, and the stacktrace under the vendor key
litellm.provider.error.stack_trace.

Build the logs pipeline (LoggerProvider + console/OTLP log exporters mirroring
the metrics plumbing) and record the event behind the enable_events flag, which
until now was defined but consumed nowhere. An operator-configured LoggerProvider
global is reused so the events ride their existing logs pipeline; an explicit
NoOpLoggerProvider global is honored as an opt-out and builds no recorder at all.

The existing span-side error surface (error.type, error.message, the exception
span event, and the litellm.provider.error.* detail keys) is untouched for
backwards compatibility.

* fix(otel): always ride the semconv-required exception pair on the GenAI event

Filtering the event attributes on truthiness conflated "absent" with "empty",
so an empty exception.type or exception.message would have been dropped, leaving
an event with neither semconv-required field. Build the attributes so the pair is
unconditional and only the recommended stacktrace is omitted when the payload
carries none.

* docs(otel): document the events plumbing module in the package README

* test(otel): cover the log exporter selection and logs endpoint normalization

The new logs plumbing had no coverage for exporter-kind selection, the
console fallback for an unrecognized kind, the /v1/logs signal-path rewriting
that lets one OTEL_ENDPOINT serve every signal, or the simple-vs-batch
processor split.
2026-07-10 16:08:10 -07:00
Tin Chi Lo
0053527ccf fix(mcp): keep bridge server-id match total on non-ascii and strip bearer with any whitespace 2026-07-10 15:30:03 -07:00
Yassin Kortam
d1ae9571ae
fix(proxy): build redis usage cache from REDIS_* env when cache backend is not Redis (#32635)
Selecting a semantic (or any non-Redis-KV) response cache left
redis_usage_cache unset, silently downgrading cross-pod rate limits,
parallel-request limits, spend coordination, and the pod lock manager
to per-pod in-memory state. Fall back to a standalone RedisCache built
from REDIS_* environment variables, mirroring the existing
use_redis_transaction_buffer escape hatch, which now shares the same
helper.

Resolves LIT-3861
2026-07-10 15:24:33 -07:00
Tin Chi Lo
ef9637035b fix(mcp): derive bridge envelope keys with memory-hard scrypt, cached per master key 2026-07-10 15:04:37 -07:00
Tin Chi Lo
08963b744f feat(mcp): bind bridge envelope to its server, key derivation via HMAC, add producer + shape helpers 2026-07-10 14:36:56 -07:00
Tin Chi Lo
424532e63d feat(mcp): add dcr_bridge envelope consumer helpers (key derivation, authorization classifier) 2026-07-10 14:36:56 -07:00
Mateo Wang
cc36d5469c
fix(bedrock): keep mid-conversation system messages in place for Claude Invoke (#32578)
Hoisting every role system entry into the top-level system field mutates
the cache prefix whenever a client such as Claude Code appends a new
mid-conversation system message, invalidating the prompt cache for the
entire message history on Bedrock Invoke. Bedrock only rejects a system
entry at messages.0, so hoist just the leading run and forward the rest
in place
2026-07-10 14:30:32 -07:00
Tin
7d64f9d26b fix(mcp): merge credentials within the client-forwarded class on an auth-type switch 2026-07-10 14:15:24 -07:00
Tin Chi Lo
9176744735 perf(mcp): O(1) character precheck before the exact byte size guard in open_envelope 2026-07-10 13:56:49 -07:00
Tin Chi Lo
2883e36a97 fix(mcp): measure envelope open-side size cap in utf-8 bytes to match mint 2026-07-10 13:32:00 -07:00
Tin Chi Lo
d6503d1d87 fix(mcp): make open_envelope total over hostile jwt claim types and cap candidate size 2026-07-10 13:16:10 -07:00
Tin Chi Lo
dd38e9f1a0 fix(mcp): return malformed_payload for signed envelopes with empty identity claims
A correctly signed JWT whose user_id or server_id claim was an empty string
passed claims validation but raised ValidationError from the EnvelopeIdentity
constructor inside open_envelope, breaking its never-raises guarantee. The
claims model now mirrors the identity's min_length constraints, so any claim
set that validates also constructs, and the empty-identity case maps to
MalformedPayload like every other bad claim shape.
2026-07-10 13:16:10 -07:00
Tin Chi Lo
65c80919aa feat(mcp): add sealed envelope module for dcr_bridge client-held credentials
Pure, unwired module: mints and opens the single client-held bearer that
carries both a litellm identity and the encrypted upstream OAuth grant with
zero server-side storage. HS256 JWT signing (same approach as the BYOK
session bearer) plus the existing encrypt_value/decrypt_value symmetric
helpers, with all key material and the clock injected as parameters. Opening
returns typed frozen error values (not_an_envelope, bad_signature, expired,
malformed_payload, decrypt_failed); minting rejects envelopes over
MAX_ENVELOPE_BYTES with a typed error instead of truncating. Error values
and reprs never carry token material.
2026-07-10 13:16:10 -07:00
Tin Chi Lo
849ceb0c19 feat(mcp): relay upstream registration errors to the client on the dcr_bridge arm 2026-07-10 12:37:47 -07:00
Tin Chi Lo
04a11a439a feat(mcp): dcr_bridge discovery facade and register relay 2026-07-10 12:37:47 -07:00
tin-berri
69f0a6d5b6
Merge pull request #32747 from BerriAI/litellm_lit4337_dcr_bridge_authorize_relay
feat(mcp): dcr_bridge authorize and token relay redirect handling with mandatory S256
2026-07-10 12:36:52 -07:00
tin-berri
220aad0e7f
Merge pull request #32715 from BerriAI/litellm_lit4284_semantic_filter_fail_closed
fix(mcp): fail closed and surface semantic filter context window errors
2026-07-10 12:33:46 -07:00
tin-berri
b9008cca35
Merge pull request #32556 from BerriAI/litellm_mcp_passthrough_call_relay
feat(mcp): relay upstream 401 on client-forwarded pass-through tool calls
2026-07-10 11:56:56 -07:00
Tin Chi Lo
aa4f585e4c feat(mcp): dcr_bridge authorize and token relay redirect handling with mandatory S256 2026-07-10 11:46:01 -07:00
tin-berri
bca3e88c5f
Merge pull request #32745 from BerriAI/litellm_lit4337_dcr_bridge_plumbing
feat(mcp): add dcr_bridge column and plumbing for client-forwarded auth modes
2026-07-10 11:45:21 -07:00
mubashir1osmani
54d404ef2c
fix(e2e): batch credentials wiring and compose harness for live proxy suite (#32744)
* fix(e2e): wire batch provider secrets for docker and k8s

Point batch deployments at the credential field names and os.environ refs
the gateway actually resolves from process env (compose .env or EKS secret
mounts). Missing secrets skip instead of failing red so a red run means a
product bug. Mirror S3 bucket env aliases in docker-compose for provider_fallback

* fix(e2e): drop batch provider_env unit tests

The batches suite is live e2e only; no monkeypatch or unit-level tests

* fix: batch credentials, provider list, and team db lookup

Keep object-storage fields through CredentialLiteLLMParams and resolve
os.environ/ refs when reading deployment credentials so Vertex/Bedrock
batch file uploads see bucket and AWS keys from K8s/docker env

Skip managed batch list when the request is provider-scoped so
/{provider}/v1/batches list works instead of 500

Force DB on check_db_only team lookups and stop masking non-404 errors
as "team doesn't exist"

Drop e2e runner-side skip helpers; hard-fail on missing gateway secrets

* fix: tag reseed, team window spend, and remaining e2e flakes

Reseed spend:tag counters from LiteLLM_TagTable so cold redis still
enforces after the spend writer flushes

When applying post-call cost to team multi-window counters, load the
team from the DB if it is missing from the management cache so window
spend is not dropped on cache misses

Harden cold-counter reseed e2e (namespace-aware keys, burst success,
poll). Give tag budget more headroom. Retry /key/update on redis DNS
blips. Ensure NLTK punkt_tab is present for pipecat realtime audio

* revert: drop product code changes; e2e-only scope

Reverts all litellm/ and unit-test product edits. This branch is limited
to tests/e2e per contributor instruction

* fix(e2e): harden batch list and team member setup races

provider_fallback list falls back when managed batches reject provider
filtering. Team create waits for /team/info and member_add retries on
transient team-not-found so split control-plane lag does not red the suite

* fix(e2e): remove .env.example

Leave local .env and docker-compose env wiring as the secret source

* fix(e2e): wire files_settings and faster budget rescheduler for compose

OpenAI/Azure batch file uploads need files_settings; budget reset e2e needs a
short rescheduler window. Drop unsupported bedrock-encoded create_batch cells,
tolerate bedrock file.bytes=0, and surface team-info wait failures instead of
hanging silently

* chore(e2e): strip verbose comments from batch capabilities

* fix(e2e): assert managed list fallback before provider_fallback skip

When provider-scoped list is rejected, still fetch the unfiltered list and
check the envelope. Only skip membership when the id is a raw
provider_fallback batch that managed list cannot index
2026-07-10 11:31:40 -07:00
tin-berri
11aeeea1fb
Merge pull request #32735 from BerriAI/litellm_mcp_no_dcr_persist_for_passthrough
fix(mcp): stop persisting the DCR client onto true_passthrough and oauth_delegate server rows
2026-07-10 11:03:38 -07:00
devin-ai-integration[bot]
190ea0802d
fix(spend): sum multi-round session cost in logs UI (#32796)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
2026-07-10 10:44:33 -07:00
yucheng-berri
b8bb95be8d
fix(spend-logs): honor store_prompts_in_spend_logs for guardrail_information (LIT-4314) (#32688)
* fix(spend-logs): honor store_prompts_in_spend_logs for guardrail_information (LIT-4314)

_get_spend_logs_metadata passed guardrail_information entries through
verbatim, so guardrail hooks that echo the LLM request into
guardrail_response leaked the raw prompt into LiteLLM_SpendLogs.metadata
regardless of store_prompts_in_spend_logs. This mirrored the pre-existing
gap for the other prompt-carrying fields (vector_store_request_metadata,
error_information, etc.), which already sanitize via
_should_store_prompts_and_responses_in_spend_logs.

Add _sanitize_guardrail_information_for_spend_logs alongside the other
per-field sanitizers and wire it into _get_spend_logs_metadata. When the
flag is False the sanitizer replaces guardrail_request and
guardrail_response with REDACTED_BY_LITELM_STRING while preserving every
other typed field on the entry (name, provider, mode, status, timings,
action, violation_categories, risk_score, masked_entity_count, ...) so
guardrail dashboards keep working. When the flag is True (or the field
is None) the entries pass through unchanged.

Widen StandardLoggingGuardrailInformation.guardrail_request from
Optional[dict] to Optional[Union[dict, str]] so the redacted sentinel
satisfies the TypedDict without needing a cast; guardrail_response
already accepted str.

Regression tests cover the three cases (flag=False redacts,
flag=True passes through, None passes through) plus an end-to-end
get_logging_payload path that fails if the wire-in at line 139 is
reverted.

* chore(spend-logs): review nits (one-shot dict build, scrub identifier in tests)

- _redact_prompt_fields_in_guardrail_entry now returns the redacted
  dict in one expression instead of seed-then-mutate (TYPE-3)
- swap the illustrative guardrail_name in the new test fixtures for
  a generic 'demo-echo-guard' identifier

* chore(spend-logs): only redact guardrail prompt fields when caller supplied them

Greptile P2: the sanitizer was unconditionally writing REDACTED_BY_LITELM
into both guardrail_request and guardrail_response on the copy, so entries
that never carried one of those fields (e.g. a guardrail that only emits
a guardrail_response) came out with a phantom guardrail_request key added.
Guard both assignments with an in-check so the output shape is stable.
Add a mutation-checked regression test that fails if either guard is
removed.

* fix(spend-logs): also redact match_details and classification in guardrail_information

The initial LIT-4314 fix redacted guardrail_request and guardrail_response,
but two other typed fields on StandardLoggingGuardrailInformation also
carry raw prompt content when a first-party guardrail populates them:

- litellm_content_filter/content_filter.py:1676 sets classification =
  dict(CompetitorIntentDetection), whose evidence[*].match is a substring
  taken directly from the user's normalized prompt (see
  litellm_content_filter/competitor_intent/base.py:184-194).
- block_code_execution/block_code_execution.py:571 sets match_details =
  guardrail_response = [dict(d) for d in detections], where detections
  carry the fenced-code-block content extracted from the user's message.

Reproduced live against localhost:4000 with store_prompts_in_spend_logs
false and a custom guardrail passing tracing_detail with both fields:
before this commit the raw prompt shows up in metadata.guardrail_information[0]
under match_details and classification; after, both are the sentinel.

Widen the two TypedDict fields to Optional[Union[..., str]] so the
sentinel string satisfies the schema without a cast, and consolidate
the redaction set into a tuple so future prompt-carrying additions are
one-line changes.

* fix(spend-logs): normalize non-list guardrail_information shapes in sanitizer

xecguard's logging hook (xecguard.py:246) assigns a bare dict to
standard_logging_object['guardrail_information'] instead of a list,
violating the typed contract Optional[List[StandardLoggingGuardrailInformation]].
Without defensive normalization, _sanitize_guardrail_information_for_spend_logs
iterates the dict's string keys and _redact_prompt_fields_in_guardrail_entry
raises TypeError on {**'guardrail_name'}, which get_logging_payload's
downstream update_database catches with a broad except and silently drops
the entire spend-log write for that request.

Normalize a bare-dict input to a single-item list at the sanitizer's
entry point, and skip any non-dict entries defensively (matching OTEL's
existing isinstance filter at opentelemetry.py:1751-1753 for the same
field). Downstream readers already model this defensively; make the
spend-log write path match.

The root cause is xecguard's writer, not the sanitizer. That is being
tracked as a separate ticket; this PR keeps xecguard-enabled deploys
from silently losing spend logs when store_prompts_in_spend_logs=false.

* fix(types): declare guardrail Union members str-first to avoid poisoning typing cache

CPython's typing module caches Union[...] order-insensitively (first-
construction wins), and litellm/types/utils.py has no 'from __future__
import annotations', so its unions are constructed eagerly at import
time -- before any proxy model. Declaring guardrail_request,
classification, and match_details with dict-first ordering seeds the
typing cache with a dict-first tuple, and later proxy models that
declare custom_llm_provider / model_aliases / vertex_credentials as
Optional[Union[str, dict]] pick up the same dict-first object.

Downstream, Pydantic's get_args() then reports anyOf in dict-first
order, FastAPI emits the OpenAPI accordingly, and 'npm run gen:api'
produces a schema.d.ts diff on unrelated fields, tripping the schema-
sync CI check.

Behaviorally identical in Python and at the wire; the flip only reorders
the union members so the first construction matches how the codebase
had always declared these unions, and 'npm run gen:api' now produces a
zero diff against the committed schema.d.ts.
2026-07-10 10:44:10 -07:00
Tin
e33654be91 feat(mcp): relay upstream 401 on client-forwarded pass-through tool calls
The multi-server list path already relays an upstream 401 from a client-forwarded
server (true_passthrough / oauth_delegate) as an MCPUpstreamAuthError so the caller
re-runs its own upstream OAuth. The single-server REST call path did not: an upstream
401 was masked as a graceful isError result, so an MCP client holding an expired
upstream token never learned it had to re-authenticate

Relay the upstream 401 on the call path too. For these modes the manager calls the
client with raise_on_error=True, extracts the WWW-Authenticate through the existing
upstream-auth exception walk, and raises MCPUpstreamAuthError; the REST endpoint turns
it into a real 401 + WWW-Authenticate. Only 401 is treated as a re-auth signal (a 403 is
a genuine authorization failure that re-auth will not fix, so it stays a masked isError
with a visible warning), matching the list path and MCPUpstreamAuthError's contract. The
legacy oauth2 + delegate_auth_to_upstream mode is deliberately left off the call-path
relay since it is being removed

To keep this expected caller-must-reauth signal from tripping error-rate alerts, the
client layer logs at debug when the caller opted into raise_on_error and therefore owns
the exception (both call_tool/list_tools and the run_with_session helper they share, so an
expected re-auth emits no warning per call either), the manager's non-auth branch logs the
exception type only (never str(e), which for an httpx error embeds the upstream URL a
credential can hide in), and the streamable and REST handlers log the relayed 401 at info
rather than as an error with a traceback

Tests cover the manager raising on a client-forwarded 401 while keeping a 403/503 as a
masked isError, the client-layer debug-vs-error logging split, the streamable handler's
informational isError, and the REST endpoint relaying both the direct and virtual
mcp_tool_call branches as a real 401 + WWW-Authenticate; each was mutation-checked to fail
when the corresponding behavior is broken
2026-07-10 10:09:31 -07:00
Tin Chi Lo
898182b0e6 fix(mcp): redact provider error from client-facing semantic filter message
Keep the full provider exception in server-side logs only; the client
receives a fixed actionable message. Also follow implicit exception
context when detecting context window overflows and pin the detection
variants plus the redaction in tests
2026-07-10 00:41:54 -07:00
Tin Chi Lo
1e8c2f7240 fix(mcp): fail closed and surface semantic filter context window errors
Resolves LIT-4284

When the embedding model exceeded its context window, the MCP semantic
tool filter silently passed all tools through and reported N->N success
in the filter header; when the overflow happened while embedding tool
descriptions at router build time, the hook was never registered at all
and filtering was silently disabled

Semantic filtering now fails closed on context window overflows: the
request is rejected with HTTP 400 and a message that names the embedding
model and advises switching to one with a larger context window or
disabling the filter. Build time overflows are recorded on the filter so
the hook still registers and blocks MCP tool requests with the same
actionable error while leaving native-only requests untouched. The
dashboard test panel renders the backend message in an error banner
instead of a success state. OpenAI's embedding overflow message
(maximum input length is N tokens) now maps to ContextWindowExceededError
2026-07-10 00:41:54 -07:00
Tin Chi Lo
5ec4c162ea fix(mcp): let a missing server 404 on a dcr_bridge enablement instead of a misleading 400 2026-07-10 00:29:17 -07:00