Commit graph

40027 commits

Author SHA1 Message Date
yuneng-jiang
9f54da4f9d
Merge pull request #36322 from BerriAI/litellm_backport_1_91_x_bp-191x-0808
chore(release): backport proxy request-handling maintenance and refresh runtime deps for 1.91.5
2026-08-08 18:55:00 -07:00
Yuneng Jiang
44f2fdfb06
chore(deps): follow staging's resolved ddtrace and mlflow versions
The 1.91.5 lock resolved ddtrace 4.12.2 and mlflow 3.15.1, one patch ahead of
what litellm_internal_staging carries (4.11.0 and 3.15.0). The [project] ranges
on this line already match staging exactly; the drift comes from `exclude-newer`
being a relative window, so a lock resolved later picks up newer patches than one
resolved earlier. Nothing about the ranges was asking for the newer versions.

Pinned at the lock layer (`uv lock -P ddtrace==4.11.0 -P mlflow==3.15.0`) rather
than by adding constraint entries, because staging reaches these versions with no
pins of its own; adding pins here would itself be a divergence from staging.

pyproject.toml is unchanged by this commit. uv.lock moves ddtrace 4.12.2->4.11.0
and mlflow / mlflow-skinny / mlflow-tracing 3.15.1->3.15.0, and nothing else: the
17 packages this release moves now sit exactly where staging has them.

docker/build_from_pip pins ddtrace independently of the lock, so it moves to
4.11.0 alongside. That pin tracking the lock is the invariant the earlier ddtrace
commit established; without this the image would have installed 4.12.2 while the
lock resolved 4.11.0.

Supersedes the version reference in "build(deps): move ddtrace to the 4.x line",
which verified against 4.12.2. Re-verified against 4.11.0: ddtrace.trace.Tracer,
ddtrace.patch_all and ddtrace.profiling.Profiler all resolve, so the retargeted
TYPE_CHECKING import and both proxy_server call sites are unaffected. mlflow
3.15.0 declares cryptography<50,>=43.0.0, the same cap as 3.15.1, so the
override-dependencies rationale is unchanged.
2026-08-08 16:32:50 -07:00
Yuneng Jiang
f96a923790
chore(deps): move msal and mlflow off the cryptography<49 cap
The [tool.uv] override-dependencies entry that raises cryptography to 50.0.0
silences every package's cryptography cap, not just the mlflow one it was added
for. That left msal pinned at 1.36.0, which declares cryptography<49,>=2.5, so
the lock resolved a pairing msal itself does not support. msal is a production
dependency here (litellm[proxy] -> azure-identity -> msal), unlike mlflow which
is an optional extra.

msal 1.37.0 declares cryptography<51,>=2.5, so it supports 50 outright. Internal
staging already resolves msal 1.37.0, so this brings that pin back to parity.
mlflow moves in the same resolution to 3.15.1; staging is one patch behind at
3.15.0.

Verified: an environment-wide scan of every installed distribution's own declared
requirements now reports one unmet constraint instead of two, and the one that
remains is mlflow's cryptography<50 - precisely the cap the override exists to
break, and the same pairing staging ships.
2026-08-08 16:22:44 -07:00
Yuneng Jiang
005e9ed8f1
chore: refresh uv.lock for 1.91.5 2026-08-08 16:20:12 -07:00
Yuneng Jiang
d5bb3c9352
bump: version 1.91.4 → 1.91.5 2026-08-08 16:20:12 -07:00
Yuneng Jiang
cace8f5b14
chore(deps): raise dependency floors and refresh uv.lock
Routine dependency maintenance for the 1.91.x image lockfile, bringing the
resolved set in line with internal staging.

Declared ranges (pyproject):
- aiohttp      >=3.10,<4.0       -> >=3.14.2,<4.0
- cryptography >=48.0.1,<49.0    -> >=49.0.0,<51.0
- ddtrace      >=2.19.0,<3.0     -> >=4.8.2,<5.0
- Pillow       ==12.2.0          -> ==12.3.0   (ci group)

[tool.uv] constraint-dependencies gains httplib2, setuptools and soupsieve
floors, and raises the aiohttp floor off 3.14.1. cryptography can only be
raised through override-dependencies: mlflow caps cryptography<50 across every
reachable version, so a plain floor raise has no solution.

Resolved moves: aiohttp 3.14.1->3.14.3, cryptography 48.0.1->50.0.0,
ddtrace 2.19.0->4.12.2, gitpython 3.1.50->3.1.58, h2 4.3.0->4.4.1,
httplib2 0.31.2->0.32.0, pillow 12.2.0->12.3.0, pyasn1 0.6.3->0.6.4,
pypdf 6.13.3->6.14.2, setuptools 82.0.1->83.0.0.

ddtrace needs a code companion for the 4.x API; it is in the preceding commit.
2026-08-08 16:20:06 -07:00
Yuneng Jiang
aeea662859
build(deps): move ddtrace to the 4.x line
`ddtrace.tracer` was removed in the 4.x line, so the TYPE_CHECKING import is
retargeted at `ddtrace.trace.Tracer`. The `docker/build_from_pip` image pinned
ddtrace==2.19.0 independently of the lock; that pin is moved to the version the
lock now resolves.

Verified against the installed 4.12.2: `ddtrace.trace.Tracer`,
`ddtrace.patch_all` (proxy_server startup) and `ddtrace.profiling.Profiler`
(proxy_server profiling) all resolve.

Code companion for the ddtrace floor raise in the accompanying dependency
commit; the import lives under TYPE_CHECKING so this commit is inert at runtime
on either line.
2026-08-08 12:21:19 -07:00
yuneng-jiang
49995ba9ce
fix(proxy)!: apply request-parameter checks consistently across body, path and form inputs (#36011)
Cherry-pick of the #36011 merge (six commits) as a single unit.

(cherry picked from commit c898d341c0)

Backport adaptation for stable/1.91.x:
- litellm/proxy/auth/auth_utils.py: took only the `extract_nested_form_metadata`
  import this commit adds. The neighbouring
  `LITELLM_PASS_THROUGH_ENDPOINT_MARKER` import is context in the upstream diff,
  not an addition; that symbol does not exist on this line and is referenced
  nowhere here, so importing it would have broken module import.
- litellm/proxy/health_endpoints/_health_endpoints.py: kept this line's `typing`
  import idiom and added `Final` and `Mapping` there, rather than taking
  upstream's `from collections.abc import Iterable, Mapping`. This line never
  took staging's collections.abc migration and still sources `Iterable` from
  `typing`.
- litellm/proxy/litellm_pre_call_utils.py: added `Final` to the `typing` import,
  required by the extracted `reject_url_valued_destination`. Upstream already
  had it from a later lint pass that is not on this line.
- tests/test_litellm/proxy/auth/test_auth_utils.py: kept this commit's own
  `TestIsRequestBodySafeChecksBracketNotationMetadata` and omitted the
  staging-prior `TestGetKeyTagRateLimits`, whose subject `get_key_tag_rpm_limit`
  does not exist on this line.
2026-08-08 11:49:23 -07:00
yucheng-berri
8498dc0bb8
chore(proxy): clean up request parameter validation and provider destination handling (#34189)
(cherry picked from commit 065faf6e69)

Backport adaptation for stable/1.91.x:
- litellm/proxy/auth/user_api_key_auth.py: dropped `Iterator` from the typing
  import as upstream does, but kept this line's import shape rather than taking
  `Protocol`, which reached staging with a feature this line does not carry and
  is unused here.
- tests/code_coverage_tests/recursive_detector.py: added only this commit's own
  `_iter_fallback_targets` allowlist entry; the three neighbouring entries guard
  staging-prior recursive helpers absent from this line.
- tests/test_litellm/proxy/auth/test_auth_utils.py: kept all four test classes
  this commit adds (TestClientsideBaseOverrideOutboundKey,
  TestIsRequestBodySafeBlocksFallbackSmuggle,
  TestIsRequestBodySafeRejectsUrlValuedFallback,
  TestIsRequestBodySafeBlocksVertexCredentialAlias) and omitted staging-prior
  tests for nvcf_function_id, use_ssl and bedrock_tags, whose subjects are not
  on this line.
2026-08-08 11:44:54 -07:00
yucheng-berri
08429626f9
fix(anthropic): require caller api_key and SSRF-validate api_base in advisor tool (#32093)
* fix(anthropic): require caller api_key and SSRF-validate api_base in advisor tool

The advisor_20260301 interceptor honored a caller-supplied api_base once
allow_client_side_credentials was enabled, even without a caller-supplied
api_key. AnthropicModelInfo.get_auth_header() then fell back to the proxy's
own ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN, so the server's real credentials
plus the conversation history got sent to a caller-chosen destination

_resolve_advisor_credentials() now only honors api_base alongside a
non-empty caller-supplied api_key, requires the https scheme, and validates
api_base via validate_url() before use, mirroring check_complete_credentials
in auth_utils.py. https is required because validate_url only DNS-pins the
connection for http; for https with TLS verification on it returns the URL
unchanged and relies on certificate validation to block DNS rebinding

* fix(anthropic): also reject advisor api_base when ssl_verify is disabled

validate_url only DNS-pins the connection for http, or for https with
litellm.ssl_verify disabled; the previous https-only check missed the
ssl_verify=False case, where validate_url's rewritten URL was still being
discarded, per Greptile's review of this PR. Reject api_base outright when
ssl_verify is False so the discarded rewrite can no longer matter

(cherry picked from commit 07b9ea8c3b)
2026-08-08 11:40:29 -07:00
yucheng-berri
c5d2a3664d
fix(proxy): restore admin key/team callback_vars.turn_off_message_logging override (LIT-3587) (#31905)
The security fix in 34e9be1ba7 removed turn_off_message_logging from
_supported_callback_params to stop callers bypassing global redaction via
the request body. That also killed the documented admin-only per-key or
per-team override because both flows resolve through the same allowlist
in initialize_standard_callback_dynamic_params.

Put turn_off_message_logging back in _supported_callback_params so an
admin-configured metadata.logging[].callback_vars.turn_off_message_logging
survives into StandardCallbackDynamicParams and can override the global
setting for that key or team, as documented at
docs/proxy/team_logging#disableenable-message-redaction.

Consolidate the metadata traversal so the extractor and the proxy strip
walk the same set of client-controllable slots. iter_client_callback_metadata_dicts
in litellm_core_utils/initialize_dynamic_callback_params.py is the single
source of truth for metadata, litellm_metadata, and litellm_params.metadata;
_strip_client_message_redaction_opt_out imports it so a future addition
to one side automatically reaches the other. The extractor iterates the
helper in reversed order so litellm_params.metadata keeps overriding
metadata, matching the pre-refactor merge precedence.

Client bypass stays blocked. Restoring the field re-enrolls it in the
auth layer's _BANNED_REQUEST_BODY_PARAMS (derived from
_supported_callback_params via _build_banned_observability_params), so
client submissions at the top level, inside metadata, or inside a
JSON-string litellm_metadata all 401 at ingress. is_request_body_safe
also now descends into litellm_params.metadata for the same 401 defense
against the nested-body attack vector, matching how the metadata and
litellm_metadata slots are handled. _strip_client_message_redaction_opt_out
runs after the litellm_metadata JSON parse and before the admin callback_vars
unpack, so admin values survive while any leftover client-supplied
opt-out is dropped when global redaction is on and the key or team
lacks allow_client_message_redaction_opt_out.

Flip the two dynamic-param e2e tests added by the security fix to
reflect the restored override behavior, keeping the invariant that
proxy client bypass is stopped by the auth layer 401 above.

Co-authored-by: yucheng <yucheng@yuchengs-MBP.attlocal.net>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
(cherry picked from commit 8e6098adc3)
2026-08-08 11:40:22 -07:00
yuneng-jiang
4820053a81
Merge pull request #33891 from BerriAI/litellm_backport_1_91_x_bp-191x-0718
chore(release): backport #33592, #33853 to stable/1.91.x and cut 1.91.4
2026-07-18 19:41:40 -07:00
Yuneng Jiang
2829cab18b
chore: refresh uv.lock for 1.91.4 2026-07-18 18:13:58 -07:00
Yuneng Jiang
463d735fc5
bump: version 1.91.3 → 1.91.4 2026-07-18 18:13:58 -07:00
Yuneng Jiang
c3e5a3a224
chore(deps): bump soupsieve to 2.8.4 2026-07-18 18:13:58 -07:00
Yuneng Jiang
bb4a5179f4
chore(deps): bump mcp to 1.28.1 2026-07-18 18:13:58 -07:00
yuneng-jiang
6fde604031
fix(docker): bake prisma CLI and engines at a fixed path so fresh-DB migrations work for any uid offline (#33853)
* fix(docker): bake prisma CLI and engines at a fixed path so fresh-DB migrations work for any uid offline

The runtime image shipped the prisma CLI and engines under /root/.cache, the
default HOME-derived prisma-python cache location. Any deployment whose
runtime HOME is not /root (kubernetes runAsUser, docker --user, HOME
overrides) missed that cache on a fresh database, fell back to a nodeenv
Node download that crashes on Wolfi (libatomic.so.1), and started the proxy
with zero tables while every DB-backed endpoint returned 500

The bake now lives at /opt/prisma, a path no HOME resolution or cache
volume mount can shadow. The builder records the engine paths there at
generate time, and the runtime stage pins PRISMA_BINARY_CACHE_DIR,
PRISMA_CLI_PATH, PRISMA_CLI_QUERY_ENGINE_TYPE=binary and
PRISMA_OFFLINE_MODE so both litellm-proxy-extras and prisma-python resolve
the baked CLI and engines directly. prisma migrate deploy on a fresh
database now needs no npm and no network access for any runtime uid,
including readOnlyRootFilesystem deployments

Verified against live containers: fresh and existing databases as root,
uid 12345, HOME overridden, on an internal-only docker network, and with
a read-only root filesystem all migrate and serve /team/new successfully

Fixes #33650, #24554

* chore(docker): fail the image build if the baked prisma CLI layout drifts

Asserts the baked CLI shim is executable and its entrypoint exists in the
runtime stage after the COPY and chmod, so a layout change in a future
prisma-python release breaks the image build loudly instead of silently
degrading the migration path at container startup

(cherry picked from commit 567ebcb3e9)
2026-07-18 18:13:58 -07:00
yuneng-jiang
70fb207e73
fix(docker): restore litellm-proxy-extras source dir in runtime images (#33592)
* fix(docker): restore litellm-proxy-extras source dir in runtime images

#30243 narrowed the runtime stage to an allowlist COPY, which dropped
/app/litellm-proxy-extras from the published images. Downstream
migration jobs point prisma migrate deploy at that path; with the
schema gone (or a schema with no adjacent migrations dir, where prisma
exits 0 without applying anything) those jobs went green while never
migrating the database. Restore the folder in all three runtime stages
and assert in image-scan that the schema and a non-empty migrations dir
ship at the source path

* chore(ci): drop image-scan migration-assets assertion

(cherry picked from commit 111d447e1b)
2026-07-18 18:13:58 -07:00
yuneng-jiang
7a4a68f022
Merge pull request #32948 from BerriAI/litellm_backport_1_91_x_bp-guard-otel-0711
chore(release): backport #32542, #32655 to stable/1.91.x and cut 1.91.3
2026-07-11 15:39:02 -07:00
Yuneng Jiang
1a2f711d14
chore: refresh uv.lock for 1.91.3 2026-07-11 14:04:33 -07:00
Yuneng Jiang
e701f11533
bump: version 1.91.2 → 1.91.3 2026-07-11 14:04:09 -07:00
Yassin Kortam
d115edc388
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.

(cherry picked from commit 99b4c5ed3e)
2026-07-11 13:59:04 -07:00
yucheng-berri
c0814be2c2
fix(guardrails): walk Responses-API text taxonomy in shared content helpers (#32542)
* fix(guardrails): walk Responses-API text taxonomy in shared content helpers

Every guardrail sharing litellm/proxy/guardrails/_content_utils.py silently
drops all text on the /v1/responses path. AIM turns it into a loud 422 (
{"error":"No messages in the request"}); every other guardrail (Lakera v2,
Cato, Lasso, Repello, IBM, Azure Content Safety, enterprise secret
detection) scans an empty payload and lets the request through unscanned.

Three defects, all in _content_utils.py:

1. _iter_text_parts_in_content recognised only part.type == "text", but the
   Responses API uses input_text (request) and output_text (assistant).
2. _coerce_input_to_messages gated on "every item has a role key"; any
   Responses input list containing a function_call or function_call_output
   item failed the check and was wrapped as one opaque blob.
3. build_inspection_messages forwarded any role through, including a bare
   tool role missing tool_call_id, which validators like AIM's /fw/v1/analyze
   reject with a schema error.

Fix walks the actual Responses item taxonomy (message, function_call,
function_call_output, bare content parts and strings), recognises
{text, input_text, output_text} everywhere, and coerces any role outside
{system, user, assistant} to user in the outbound inspection payload.

* style: ruff-format changed guardrail files

* test(guardrails): cover function_call_output string form; drop em-dash in new docstring

* fix(guardrails): map function_call_output straight to user role

Avoids ever materialising a schema-invalid bare tool message. The
downstream role-safety coercion in build_inspection_messages still
guards genuinely caller-supplied non-standard roles (developer,
function, custom values); add a regression test covering that path
so the coercion has real coverage after this simplification.

* test(guardrails): pin chat-completions tool-role coercion in build_inspection_messages

* docs(test): soften AIM-specific claims in LIT-4294 test docstrings

Ryan's review flagged that several test docstrings assert AIM's
/fw/v1/analyze validates + rejects specific schema violations. That
behavior is customer-reported in the LIT-4294 writeup, not directly
verified by us. Rephrase to attribute the AIM 422 to the customer's
writeup and describe the underlying constraint as the OpenAI chat
schema; any downstream API that validates against that schema rejects
the same shape.

* refactor(guardrails): move unsupported-role coercion into AIM only

The generic coercion in build_inspection_messages collapsed any role
outside {system, user, assistant} to user for every caller of the
helper. Combined with the pre-existing apply_redacted_messages_back
write-back behavior in Lakera/AIM/Cato, that turned a loud OpenAI 400
on chat-completions tool-message masking into a silent semantic
corruption of the outbound request (role tool with tool_call_id got
rewritten to bare role user, dropping the assistant + tool_calls
sibling).

AIM specifically requires the coercion because its /fw/v1/analyze
validates the payload against the OpenAI chat schema; other guardrails
either do not validate roles or do their own reconstruction. Move the
coercion to AimGuardrail._build_aim_inspection_messages so the shared
helper keeps caller roles intact and no new cross-guardrail role
corruption is introduced. The pre-existing apply_redacted_messages_back
structural flatten remains as separate follow-up work.

function_call_output items still synthesise role user in the shared
helper because they have no natural role field, which is a different
concern from coercing a caller-supplied role.

* refactor(guardrails): preserve role fidelity in shared _content_utils

Shared inspection helpers should extract text and preserve semantic
role signals; role coercion for third-party schema safety stays inside
the guardrail that needs it (AIM).

Three shared-helper changes:
- Bare content-part dicts (input_text/output_text) with an explicit role
  keep it; only role-less parts default to user.
- Responses message items already had their role preserved; the
  behavior is now covered by an explicit test.
- function_call_output items default to role tool (semantic equivalent
  of the chat-completions tool message shape) instead of role user, so
  Responses and chat completions produce symmetric inspection payloads.
  A caller-supplied role on the item is still preserved.

AIM's schema-safe coercion in _build_aim_inspection_messages already
handles the resulting role tool: it collapses to user before the POST
to /fw/v1/analyze so AIM's OpenAI-schema validator does not reject the
bare tool message (no tool_call_id can survive the flatten). Added a
regression test in test_aim.py covering that path.

(cherry picked from commit e84a19acd5)
2026-07-11 13:58:15 -07:00
Mateo Wang
6950a52a15
Merge pull request #32872 from BerriAI/litellm_cherrypick_1_91_x
fix(bedrock): backport #32578 and #32831 to stable/1.91.x for v1.91.2
2026-07-10 22:56:58 -07:00
mateo-berri
994d45756c fix(bedrock): flag mapped Claude 4.8+ entries with supports_mid_conversation_system (#32882)
Backport of #32882 to stable/1.91.x.
Cherry-picked from litellm_internal_staging (pending merge there).

Adapted for this line: only the four anthropic.claude-fable-5 cost map
entries exist on stable/1.91.x (no sonnet-5 or jp opus-4-8 entries), and
the fallback-generalizations invariant test is omitted because the
feature is absent here.
2026-07-10 22:42:23 -07:00
devin-ai-integration[bot]
62367705e1 test(bedrock): switch image gen live test off EOL Titan to Nova Canvas (#31937)
Backport of #31937 to stable/1.91.x.
Cherry-picked from 912ca6255c02e0532e4bd44e93b0eb2107f5bd5c (litellm_internal_staging).
Fixes the base-level image_gen_testing failure on this line: amazon.titan-image-generator-v2:0 reached end of life on Bedrock.
2026-07-10 20:47:26 -07:00
mateo-berri
cb973ae523 bump: version 1.91.2 2026-07-10 20:39:08 -07:00
Mateo Wang
951e0ae29b fix(bedrock): gate in-place system role messages on model support for Claude Invoke (#32831)
Backport of #32831 to stable/1.91.x.
Cherry-picked from 5e23a5ab05 (litellm_internal_staging).

Adapted for this line: the fallback-generalizations feature does not exist on
stable/1.91.x, so the fallback rule for unmapped Claude 4.8+ models (and its
tests) is omitted; unmapped models fall back to hoist-all, which is the safe
default. Only supports_mid_conversation_system is added to the Opus 4.8 cost
map entries.
2026-07-10 20:38:52 -07:00
Mateo Wang
62f239b09b fix(bedrock): keep mid-conversation system messages in place for Claude Invoke (#32578)
Backport of #32578 to stable/1.91.x.
Cherry-picked from cc36d5469c (litellm_internal_staging).
2026-07-10 20:33:24 -07:00
yuneng-jiang
cdc8c72c97
Merge pull request #32552 from BerriAI/litellm_backport_1_91_x_0708
chore(release): backport #32256, #32405, #32524 to stable/1.91.x and cut 1.91.1
2026-07-08 16:01:36 -07:00
Yuneng Jiang
8fb1667a59
chore: refresh uv.lock for 1.91.1 2026-07-08 15:43:32 -07:00
Yuneng Jiang
f2bc53255a
bump: version 1.91.0 → 1.91.1 2026-07-08 15:43:12 -07:00
yucheng-berri
d5d6e87434
fix(otel): restore error.* span attributes on v2 error spans (LIT-4179) (#32524)
The v2 emitter has never stamped error.message / error.code /
error.stack_trace / error.llm_provider as span attributes; only error.type
reached the wire. Backends that flatten span attributes into label
indexes (Elastic APM labels.error_*, Datadog span tags) lost these
four fields when v2 became the active integration on v1.90+ for
otel_v2-flagged deployments. The pre-existing exception span event
carrying the full message (LIT-3758) is unchanged; the message now
rides both places at once, matching v1s shape.

SpanError grows three optional detail fields; _parse_error threads
them from StandardLoggingPayloadErrorInformation; the emitters error
branch stamps them via a new module-level helper, guarded per field so
guardrail-shape errors are not polluted with empty attributes. New
semconv constants mirror open_inference.ErrorAttributes byte-for-byte,
so v1 and v2 consumers read the same keys.

Regression tests extend the mapped test files under
tests/test_litellm/integrations/otel/. pytest reports 243 passed.

(cherry picked from commit 85d1fe6e2a)
2026-07-08 15:17:16 -07:00
yuneng-jiang
272c0f895c
Merge pull request #32405 from BerriAI/litellm_kraken-remove-envref-gates
fix(proxy): resolve os.environ/ refs universally in DB-sourced models

(cherry picked from commit ec4f324482)
2026-07-08 15:17:07 -07:00
yuneng-jiang
fec5076c77
Merge pull request #32256 from BerriAI/litellm_bedrock_db_env_expansion
fix(proxy): resolve os.environ/ refs for all AWS auth params in DB-sourced models

(cherry picked from commit 7d13f03f22)
2026-07-08 15:16:57 -07:00
yuneng-jiang
0519dbf25b
Merge pull request #32100 from BerriAI/litellm_backport_32032_191rc1
revert(auth): backport the teamless all-team-models denial revert (#32032) to 1.91.0rc1
2026-07-04 10:55:59 -07:00
devin-ai-integration[bot]
fb408608ea
revert: undo teamless all-team-models denial from #32022 and #29746 (#32032)
(cherry picked from commit 5ece78fb5f)
2026-07-04 02:41:01 +00:00
yuneng-jiang
7590e7f1ec
Merge pull request #32094 from BerriAI/litellm_ui_rebuild_191rc1
chore(ui): rebuild Next.js bundle for #31921/#31920 on patch-1.91.0rc1
2026-07-03 18:13:06 -07:00
Yuneng Jiang
9bee45ff7c
chore: update Next.js build artifacts (2026-07-04 01:07 UTC, node v20.20.2) 2026-07-03 18:07:16 -07:00
yuneng-jiang
465a7b22d5
Merge pull request #32091 from BerriAI/litellm_backport_191_rc1
chore(release): backport #31912/#31920/#31921 (+#31923/#31929 parity, #31635 prereq) onto patch-1.91.0rc1
2026-07-03 18:03:52 -07:00
tin-berri
a3c1ece4c4
fix(mcp): persist DCR client_id from on-create MCP OAuth Authorize & Fetch (#31920)
* fix(mcp): persist DCR client_id so interactive OAuth token refresh works

Interactive authorization_code MCP servers register an OAuth client via Dynamic
Client Registration (RFC 7591) during the authorize flow, but the minted
client_id and the discovered token_url were returned to the caller and never
written to the server row. The autonomous refresh_token grant reads client_id,
client_secret and token_url off the server, so an expired access token could not
be refreshed; the user was bounced back to re-authorize and tools/list returned
zero tools

Persist the DCR client_id (plus client_secret and token_endpoint_auth_method when
the registration returns them) and the discovered token_url onto the server row,
reusing the encrypt_credentials write that client_credentials and token exchange
already use, then refresh the in-memory registry so the value is live at refresh
time. Both the v1 refresher and the v2 AuthorizationCodeRefresher read those same
fields, so egress needs no change

* fix: reuse persisted MCP DCR clients

* fix(ui): persist DCR client_id from on-create MCP OAuth "Authorize & Fetch"

The interactive "Authorize & Fetch" flow on the create form registers an OAuth
client (RFC 7591) against a temporary server that has no DB row, then creates the
real server afterward. useMcpOAuthFlow captured the DCR client_id and client_secret
but passed only the token to onTokenReceived, so the create request dropped the
client identity and the created server could not refresh its access token; its row
had credentials={} and the refresh_token grant 401d at the upstream token endpoint

Forward the registered client to onTokenReceived and write client_id (and
client_secret when present) into the create form credentials, so the create request
carries them and the backend persists them through its existing encrypt_credentials
path. token_url is omitted because it is re-discovered on load (RFC 9728 then 8414);
token_endpoint_auth_method is unused because this flow only ever registers as
client_secret_post or none, never client_secret_basic

* fix(ui): prevent stale MCP OAuth credentials

* fix(ui): reset MCP OAuth authorization state

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
(cherry picked from commit 3235f4a499)
2026-07-03 17:01:02 -07:00
tin-berri
fe9fb0b81a
fix(mcp): persist DCR client_id so interactive OAuth token refresh works (#31912)
* fix(mcp): persist DCR client_id so interactive OAuth token refresh works

Interactive authorization_code MCP servers register an OAuth client via Dynamic
Client Registration (RFC 7591) during the authorize flow, but the minted
client_id and the discovered token_url were returned to the caller and never
written to the server row. The autonomous refresh_token grant reads client_id,
client_secret and token_url off the server, so an expired access token could not
be refreshed; the user was bounced back to re-authorize and tools/list returned
zero tools

Persist the DCR client_id (plus client_secret and token_endpoint_auth_method when
the registration returns them) and the discovered token_url onto the server row,
reusing the encrypt_credentials write that client_credentials and token exchange
already use, then refresh the in-memory registry so the value is live at refresh
time. Both the v1 refresher and the v2 AuthorizationCodeRefresher read those same
fields, so egress needs no change

* fix: reuse persisted MCP DCR clients

* fix: reuse persisted MCP DCR clients

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
(cherry picked from commit 15ff389eb4)
2026-07-03 17:00:14 -07:00
tin-berri
13db8cb140
fix(mcp): surface tools/list 401 auth failures as a challenge on single-server routes (#31921)
A 401 while listing tools (a missing or expired per-user OAuth token, or an
upstream 401 for any auth_type) was swallowed to an empty tool list, so a
single-server client got a 200 with no tools and no WWW-Authenticate challenge
instead of a 401 it could re-authenticate against. Only oauth pass-through and
delegate-to-upstream oauth2 servers surfaced it; every other auth_type, and the
missing-token case for all of them, masked it.

The surface-vs-absorb decision now keys on the route, not the auth_type. An
upstream 401 in _fetch_tools_with_timeout becomes an MCPUpstreamAuthError
regardless of auth_type, and the per-user OAuth challenge raised during client
creation (a bare HTTPException 401 carrying a WWW-Authenticate header) is
converted to the same type in _get_tools_from_server. The challenge is scoped
to 401: a 403 (authenticated but forbidden, e.g. insufficient scope) is not a
re-auth signal and degrades to an empty list like any other non-auth error, and
the stdio-allowlist 403 (no challenge header) stays absorbed. The existing
routing then does the right thing: single-server routes turn the error into a
401 + WWW-Authenticate, while the multi-server aggregator absorbs it to an empty
list so one unauthenticated server does not fail the whole listing.

On the UI tools page, an OBO (per-user authorization_code) server now shows the
Authorize gate when the list call returns 401, not only when no credential row
exists. The backend already refreshes a still-refreshable token on the list
call, so a 401 means there is no valid token and none could be minted (expired
with no usable refresh token), which is exactly when the user must reauthorize.

(cherry picked from commit b9df7fa705)
2026-07-03 16:56:56 -07:00
Shivam Rawat
e356ead560
fix(bedrock): honor ttl for tool_config cache injection points (#31929)
* fix(bedrock): honor ttl for tool_config cache injection points

Pass cache_control_injection_points control.ttl through to Bedrock
toolConfig cachePoint blocks, matching message/system cache behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(bedrock): drive Claude 4.5+ ttl support from pricing JSON, not regex

is_claude_4_5_on_bedrock hardcoded a model-name pattern list that needed a
manual update for every new Claude release (it already silently missed
Sonnet 5 and Fable 5). Replace it with a lookup against
cache_creation_input_token_cost_above_1hr in model_prices_and_context_window.json,
which AWS docs confirm tracks the same 1h-TTL-capable model set.

Also fixes two bedrock Claude 3.5 Sonnet entries that incorrectly carried
that pricing field (their own regional variants didn't have it), which
would have made the JSON-driven check wrongly grant them 1h TTL support.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(tests): use real Claude Sonnet 4.5 release id in ttl cache-point tests

test_add_cache_point_tool_block_passes_ttl_for_claude_4_5 and
test_bedrock_tools_pt_passes_ttl_for_claude_4_5 used a fabricated model id
(...-20250514-v1:0) that never shipped. This passed under the old regex-based
is_claude_4_5_on_bedrock, which matched on substring alone, but fails now
that it looks up cache_creation_input_token_cost_above_1hr in
litellm.model_cost, since the fake id has no pricing entry.

Also force the bundled local cost map in both tests so ttl eligibility reads
this branch's pricing data instead of the network-fetched main copy, which
lacks the fix until merge.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(bedrock): restore cache and tool config compatibility

* fix(bedrock): preserve Sonnet 5 parallel tool config

* fix(bedrock): decouple parallel tool support from cache ttl

* refactor(bedrock): drive parallel tool use config from JSON, not hardcoded patterns

Replace the hardcoded _CLAUDE_BEDROCK_PARALLEL_TOOL_USE_PATTERNS tuple and
bedrock_converse_supports_strict_tool_schemas (dead code) with a
supports_parallel_tool_use_config key in model_prices_and_context_window.json,
matching how is_claude_4_5_on_bedrock already reads
cache_creation_input_token_cost_above_1hr from the pricing JSON.

New models pick up parallel tool use support automatically when their
pricing entry ships with the key set, with no code change required

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

* fix(tests): use real model id in parallel-tool-use-without-ttl-pricing test

anthropic.claude-opus-4-7-unlisted-v1:0 has no entry in
model_prices_and_context_window.json, so
bedrock_converse_supports_parallel_tool_use_config returned False and the
test died with KeyError on additionalModelRequestFields. Use
jp.anthropic.claude-opus-4-7, a real entry that carries
supports_parallel_tool_use_config without 1h-TTL cache pricing, which is
exactly the decoupling this test exists to cover

* test(utils): allow supports_parallel_tool_use_config in pricing schema

The misc unit test job validates model_prices_and_context_window.json
against the INTENDED_SCHEMA allowlist in test_utils.py, which rejects
unknown keys. Add the supports_parallel_tool_use_config key this PR
introduced so test_aaamodel_prices_and_context_window_json_is_valid
passes again

* fix(bedrock): preserve ttl for regional claude models

* fix(bedrock): fall back to base model entry when regional pricing lacks capability fields

Regional model_cost entries like jp.anthropic.claude-opus-4-7 that omit
cache_creation_input_token_cost_above_1hr shadowed the base entry that has it,
so is_claude_4_5_on_bedrock returned False and requested cache ttl values were
dropped for those deployments. Both capability lookups now consult the full
model id and the region-stripped base entry, matching the coverage of the old
name-pattern list. Also restores ToolBlock keyword construction for the
tool_config cachePoint; PEP 589 TypedDict keyword instantiation works on every
supported Python version

---------

Co-authored-by: Shivam Rawat <shivamrawat@Shivams-MacBook-Pro.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: mateo <mateo@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
(cherry picked from commit 1543725916)
2026-07-03 16:52:15 -07:00
Mateo Wang
c734ee772f
fix(bedrock/converse): drop toolSpec.strict for Opus 4.7/4.8 (#31582) (#31923)
* fix(bedrock/converse): drop toolSpec.strict for Opus 4.7/4.8

Bedrock Converse routes Claude Opus 4.7/4.8 through an Anthropic-compatible
validator that maps toolSpec to the native tool shape and rejects the extra
`strict` key with `tools.N.custom.strict: Extra inputs are not permitted`,
even though Anthropic's native API accepts `strict` as a top-level tool field
for the same models. Sonnet 4.5/4.6 and Opus <=4.6 accept `toolSpec.strict`
unchanged.

The existing gate `get_bedrock_base_model(model).startswith("anthropic")`
(introduced in #29814 to forward `strict` for Claude on Bedrock Converse) is
too broad and regressed Opus 4.7/4.8 callers — see #31582.

Replace the inline check with a small `bedrock_converse_supports_strict_tools`
helper that excludes the Opus 4.7/4.8 family from strict forwarding. All
other Anthropic models on Bedrock keep the existing behavior.

Closes #31582.

* fix(bedrock/converse): move strict-tools regression to a clean test file

The original regression test was added to
test_litellm_core_utils_prompt_templates_factory.py, which has
pre-existing ruff-format violations throughout (multi-line asserts that
fit on one line). The lint workflow runs `ruff format --check` on
changed files only, so touching that file surfaces those pre-existing
violations and fails CI for unrelated reasons.

Move the #31582 regression coverage into a new dedicated test file so
the format check stays green. Also collapses the helper's `not any(...)`
onto a single line to satisfy ruff format.

Covers: #31582

* refactor(bedrock/converse): drive strict-tools gate from model cost map

Replace the hardcoded Opus 4.7/4.8 pattern list with a
bedrock_converse_supports_strict_tools flag on the affected entries in
model_prices_and_context_window.json, resolved via get_model_info with a
local cost map fallback, so future models with the same restriction only
need a JSON update

* chore: revert unrelated credential_migration.py reformat

---------

Co-authored-by: ly-wang19 <ly-wang19@users.noreply.github.com>
(cherry picked from commit 85f924148a)
2026-07-03 16:43:50 -07:00
tin-berri
2578d9557b
fix(mcp): support client_secret_basic for upstream OAuth token endpoints (#31635)
The MCP gateway authenticated to upstream OAuth token endpoints only with
client_secret_post (client_secret placed in the POST body). Providers that
require HTTP Basic client authentication (client_secret_basic, the OIDC
default) reject that with invalid_client, which surfaced as a 500 on the
/<server>/token exchange and broke both the initial authorization_code
exchange and refresh.

Add a per-server token_endpoint_auth_method ("client_secret_basic" |
"client_secret_post") and a single helper that builds the right headers and
body for the configured method, then route every upstream token-endpoint POST
through it: the inbound exchange and refresh in discoverable_endpoints, the v1
per-user refresh in db, the v2 authorization_code refresher, the M2M
client_credentials fetch, and the RFC 8693 token exchange. The default stays
client_secret_post so existing servers are unaffected; basic sends
Authorization: Basic base64(form-urlencode(client_id):form-urlencode(secret))
per RFC 6749 section 2.3.1 and omits the secret from the body.

client_secret_basic is a confidential-client method, so a server configured for
it with a missing client_id/secret raises rather than silently downgrading to a
body request (no-silent-fallback); the inbound endpoint maps that to a 400 and
the refresh paths to a failed-refresh / needs-reauth. A secretless client_id
under the default method stays valid for public clients authenticating with PKCE.

Resolves LIT-4091

(cherry picked from commit 7baf25526f)
2026-07-03 16:41:08 -07:00
Shivam Rawat
0ade44f4da
Merge pull request #31542 from BerriAI/litellm_internal_staging
Some checks failed
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
CodSpeed Benchmarks / benchmarks (push) Has been cancelled
Helm unit test / unit-test (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
GitHub Actions Security Analysis / zizmor (push) Has been cancelled
chore(ci): promote internal staging to main
2026-06-27 20:13:02 -07:00
Shivam Rawat
cc57fe2111
chore: update Next.js build artifacts (2026-06-28 00:38 UTC, node v20.20.2) (#31539)
Co-authored-by: Shivam Rawat <shivamrawat@Shivams-MacBook-Pro.local>
2026-06-27 17:49:43 -07:00
ryan-crabbe-berri
234263fdda
fix(router): persist global retry_policy via /config/update (#29540)
* fix(router): persist global retry_policy via /config/update (LIT-3152)

The Admin UI Model Retry Settings tab POSTs
{router_settings: {retry_policy: {...}}} to /config/update, but the
field was dropped on two write-side layers so it never reached the
router. UpdateRouterConfig did not declare retry_policy, so
dict(exclude_none=True) stripped it before the DB upsert. And even when
fed directly, Router.update_settings had no "retry_policy" entry in
_allowed_settings, so the assignment was a silent no-op. The DB row
stayed at {"model_group_alias": {}}, llm_router.retry_policy stayed
None, and the UI fell back to defaultRetry = num_retries = 2 on refresh.

Declare retry_policy on UpdateRouterConfig as a plain dict, and add a
retry_policy branch to update_settings that coerces dict payloads to
RetryPolicy before setattr, mirroring Router.__init__. get_settings
already lists retry_policy, so reads work once writes land.

* fix(router): guard retry_policy type in update_settings

Mirror Router.__init__ semantics in update_settings: only assign
retry_policy when it is None or a RetryPolicy (after dict coercion).
Previously a non-dict, non-RetryPolicy value (e.g. a YAML typo like
retry_policy: 5 flowing through /config/update) was stored verbatim,
deferring the failure to request time in get_num_retries_from_retry_policy
instead of being dropped at write time.

* refactor(ui): harden Model Retry Settings flow and validate retry_policy at the boundary

Types UpdateRouterConfig.retry_policy as RetryPolicy and model_group_retry_policy as Dict[str, RetryPolicy] so /config/update validates the payload and rejects malformed counts instead of silently persisting them; the apply path in update_settings keeps coercing the stored dict back to RetryPolicy

Makes the Model Retry Settings tab the single owner of retry_policy and model_group_retry_policy so the generic Router Settings page no longer renders or writes them, replaces the fire-and-forget save with a react-query mutation that only shows the success toast after the write resolves, surfaces real errors, disables Save while in flight, and re-reads authoritative state on success, and sends both the global and per-group policies atomically so edits in the inactive scope are no longer dropped

Decouples the retry-scope selector from the All Models filter and defaults it to Global, seeds the displayed default from num_retries (falling back to 2), and gives per-group rows real inherit semantics so an empty input shows the global value as a placeholder with a Reset control, keeping 0 ("no retries") distinct from inheriting the global value

* fix(keys): align router_settings examples with typed RetryPolicy and resync UI artifacts

model_group_retry_policy is now Dict[str, RetryPolicy], so the {"max_retries": 5} sample in the key-generate test and the /key/generate and /key/update docstrings no longer validate; they now use a valid {"gpt-4": {"RateLimitErrorRetries": 5}} shape.

Regenerated eslint-metrics.json (no-explicit-any drifted 2027 -> 2026) and schema.d.ts (new RetryPolicy schema, retry_policy field, model_group_retry_policy value type) so the UI build and api-types-sync checks pass

* test(router): pin retry_policy persistence end to end (LIT-3152)

The existing retry_policy tests exercise UpdateRouterConfig and Router.update_settings in isolation, so they would all still pass if a regression flipped ConfigYAML.router_settings back to a loose dict or stopped add_deployment from applying the stored row. This drives the real handler chain an Admin UI save triggers: update_config writes the LiteLLM_Config row, the apply path forwards it to the live router, and get_config serializes it back, pinning retry_policy across persist, apply, and read-back.

* fix(teams): use valid model_group_retry_policy example in router_settings docstring

Same stale {"max_retries": 5} example the key endpoints carried; model_group_retry_policy maps a model group to a RetryPolicy, so the team /team/new and /team/update docs now show {"gpt-4": {"RateLimitErrorRetries": 5}}. Regenerated schema.d.ts to match.

* fix(ui): load retry settings via deferred fetch to satisfy set-state-in-effect

The Model Retry Settings effect called loadRetrySettings synchronously; eslint-plugin-react-hooks (react-hooks/set-state-in-effect) traces into it and flags the setState calls, failing frontend-lint. Split the loader into fetchRouterSettings + applyRouterSettings and run the fetch in an inline async IIFE with a cancellation flag, so state is applied in the post-await callback rather than on the effect's synchronous path. Behavior is unchanged and onSuccess still refreshes via loadRetrySettings.

* fix(ui): match CI rendering of RateLimitError 429 docstring in generated schema

gen:api run on a dev env (python 3.13 / newer fastapi) rendered the RateLimitError response description with 4-space indentation, but CI regenerates it with 8-space under its frozen python 3.12 toolchain, which is the canonical committed form. The Check UI API Types Sync job regenerates and diffs, so restore that block to the CI rendering; verified byte-identical to the pre-existing committed version.

* fix(ui): pin RateLimitError 429 docstring to CI's frozen schema rendering

Base #29619 regenerated schema.d.ts on a newer FastAPI that renders the RateLimitError response description at 4-space indent, but the Check UI API Types Sync job regenerates under the frozen python 3.12 toolchain, which renders 8-space. Merging base pulled in the 4-space form; restore the 8-space rendering so the generated types match what CI produces (verified byte-identical to the pre-#29619 committed form), which also corrects the base drift once this PR merges.
2026-06-28 00:20:20 +00:00
ryan-crabbe-berri
ac56320f26
fix(agents): show an agent's attached virtual key in the UI (#29619)
* fix(agents): show an agent's attached virtual key in the UI

The A2A agent detail view never surfaced which virtual key was attached to
an agent, so after assigning a key during agent creation there was no way to
see it again. Surface the attached key(s) in the agent detail view, derived
from the key table's agent_id foreign key the same way spend is already
joined into the agent response.

Backend adds an agent_id filter to /key/list (mirrors team_id) and enriches
GET /v1/agents and GET /v1/agents/{id} with a non-secret key summary (alias,
masked key_name, hashed token id). The frontend renders a Virtual Keys
section in the agent detail view that lists the agent's keys and links
through to the key detail, and the list view drops its fetch-500-keys-and-
filter-client-side workaround in favor of the enriched response. The orphaned
AgentCard and AgentCardGrid components, left behind when the agent list
switched from a card grid to a table, are removed

* fix(agents): redact attached virtual keys for non-admins

_attach_keys_to_agents joins keys onto the agent response by agent_id with
no caller scoping, but _redact_sensitive_agent_fields never cleared the new
keys field. A non-admin able to view an agent therefore received the alias,
masked name, and hashed token of every key attached to it, including keys
owned by other users or teams; the old client-side path used the scoped
key list, so this was a visibility regression. Clear keys in the redaction
path so only admins see attached-key metadata.

Adds an endpoint-level regression test asserting keys is populated for admins
and null for non-admins, and a list-view test covering the Active vs Needs
Setup badge that lost coverage when the agent card tests were removed.

* fix(agents): satisfy strict lint and resync key/list types

- use builtin list/dict generics in the new agent key helpers to stay
  under the UP006 strict-rule ceiling
- swap @tremor/react for antd Typography in agent_virtual_keys (tremor is
  being phased out; the new component was the only unsuppressed import)
- regenerate schema.d.ts so the /key/list agent_id query param is typed

* style(agents): prettier-format key hook test and agent_info
2026-06-27 16:44:25 -07:00