Skip to content

chore(release): backport #29015, #29444, #29447, #30480, #30573 to stable/1.88.x and cut 1.88.4 - #30889

Merged
yuneng-berri merged 7 commits into
stable/1.88.xfrom
litellm_backport_1_88_x_bp-188x-0620
Jun 20, 2026
Merged

chore(release): backport #29015, #29444, #29447, #30480, #30573 to stable/1.88.x and cut 1.88.4#30889
yuneng-berri merged 7 commits into
stable/1.88.xfrom
litellm_backport_1_88_x_bp-188x-0620

Conversation

@yuneng-berri

Copy link
Copy Markdown
Collaborator

Relevant issues

Backports four already-merged fixes onto stable/1.88.x and cuts 1.88.4. The picks are #29444 (Datadog batch splitting on 413), #29447 (stop the use_chat_completions_api control flag from leaking into the provider request body), #30480 (cap Anthropic cache_control injection at 4 blocks), and #30573 (AIM guardrail blocks return a conformant 400 instead of a 500). #30573 needs ProxyException to populate Exception.args so str(exc) returns the message; that came from #29015 (LIT-3094), which was not yet on this line, so #29015 is included as a required dependency.

Pre-Submission checklist

  • I have added meaningful tests
  • My PR's scope is as isolated as possible
  • Greptile review requested

What is included

In merge order:

#29444, #29447, #30480 are picked verbatim (patch-id identical to their source commits). #30573 and #29015 are adapted; see below.

Adaptation notes

#30573: the upstream change inserts the ProxyException re-raise block right after a call to self._apply_router_cooldown_retry_after(headers, e). That helper does not exist on 1.88.x, so the call line is dropped and only the ProxyException block is applied, attached to the existing error funnel just before the HTTPException branch. The block itself is byte-for-byte the upstream code. The two production changes to proxy/utils.py (excluding ProxyException from llm_exceptions alerting, and classifying it as a proxy-only error) and the aim.py rewrite applied cleanly. The added tests were placed at the equivalent anchors on this line (the upstream insertion points TestStreamCloseOnDisconnect and TestShouldUseSmtpSsl do not exist here), and test_proxy_utils.py needed "patch" added to its module-level unittest.mock import because 1.88.x imported it per-method rather than at module scope. Test bodies are unchanged.

#29015: included as the dependency that makes str(ProxyException) return its message, which #30573's AIM tests assert through pytest.raises(..., match=...). The production change to _types.py (super().init(self.message)) applied cleanly. Its five LIT-3094 regression tests were placed after the existing 1.88.x test in test_proxy_types.py, which is preserved alongside them.

Known noise on this line

The targeted test baseline on stable/1.88.x has one pre-existing failure unrelated to these picks: tests/test_litellm/proxy/test_proxy_utils.py::test_get_custom_url, which asserts a host of 0.0.0.0 while the local environment resolves localhost. It fails identically before and after the picks. Everything else in the targeted set passes (1 failed, 187 passed post-pick; the failure is this known-noise case).

Screenshots / Proof of Fix

Live proxy on stable/1.88.x with the picks applied. The #29447 fix strips the use_chat_completions_api control flag so a strict provider no longer rejects the request:

# use_chat_completions_api is stripped, request succeeds
curl /v1/chat/completions -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"reply with the single word: works"}],"use_chat_completions_api":true}'
-> SUCCESS content: works

# contrast: a genuinely unknown field still leaks and is rejected, proving the flag above was stripped, not silently accepted
curl /v1/chat/completions -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"totally_bogus_unknown_param":true}'
-> REJECTED: litellm.BadRequestError: OpenAIException - Unrecognized request argument supplied: totally_bogus_unknown_param

Proxy sanity green vs the pre-pick baseline: health/liveliness 200, a real gpt-4o-mini completion returns content, and a scoped generated key completes a call. Targeted-test delta is zero new failures. A behavioral stress-test over the picks (symbol resolution, each pick's own tests, and existing callers of every modified function) returned no regressions across all three checks.

Type

🐛 Bug Fix

Changes

Backport-only. No new behavior beyond the cherry-picked fixes and the version bump.

yassin-berriai and others added 7 commits June 20, 2026 11:45
…quest body (#29447)

* fix: stop use_chat_completions_api flag from leaking into provider request body

use_chat_completions_api is a LiteLLM control flag that forces the
/responses -> /chat/completions bridge. It was missing from
all_litellm_params, so get_non_default_completion_params treated it as a
model-specific param and forwarded it to the upstream provider. A
model-level "use_chat_completions_api: true" in the proxy config therefore
reached the chat-completions path and was rejected by strict providers
(OpenAI/Anthropic) with HTTP 400 for an unknown body field.

Register it as a known internal param so it is stripped on every path
(completion, the responses bridge that calls litellm.completion, and
filter_out_litellm_params).

Adds a regression test driving litellm.completion() with a mocked OpenAI
client that asserts the flag never reaches the request body.

* test: clarify extra_body assertion in use_chat_completions_api leak test

Replace the misleading 'not in ... or {}' precedence idiom with an explicit
parenthesized guard that also handles extra_body being None.

(cherry picked from commit 65b6e04)
…30480)

* fix(integrations): cap Anthropic cache_control injection at 4 blocks

Respect Anthropic's 4 cache_control breakpoint limit by counting client-supplied blocks, skipping messages that already carry cache_control, and stopping further auto-injection once the limit is reached.

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

* fix(integrations): reserve cache slot for tool_config and short-circuit cap

Address review feedback on the cache_control cap: break out of the injection loop before resolving target indices once the limit is reached, and reserve one of the four breakpoint slots when a tool_config injection point is present so the cachePoint appended by the Bedrock transform does not push the total past Anthropic's limit.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit fc9d789)
* fix(guardrails): return 400 not 500 when AIM blocks a request

AIM guardrail blocks raised a bare HTTPException whose type and param
serialized as the literal string "None", which broke OpenAI-SDK error
parsing for downstream consumers. Switching AIM to raise a ProxyException
surfaced a second bug: the shared error funnel re-derived the HTTP status
from a nonexistent status_code attribute and downgraded the 400 to a 500.
The funnel now honors an already-normalized ProxyException rather than
rebuilding it, and ProxyException is excluded from llm_exceptions alerting
so a content-policy block no longer pages on-call as an LLM API failure

Resolves LIT-3751

* fix(guardrails): route all AIM rejection paths through ProxyException

The block-action fix left two AIM rejection paths raising a bare
HTTPException: the multimodal anonymize rejection and the output-side
block. Both serialized type and param as the literal string "None", the
same malformed shape the block fix removed. Funnel all three through a
shared _rejection helper so they return a conformant OpenAI error body.
The output block carries content_policy_violation; the multimodal
rejection stays a plain invalid_request_error because it is a usage
error, not a policy violation

Resolves LIT-3751

* fix(guardrails): record AIM ProxyException blocks in failure logs

Switching AIM blocks from HTTPException to ProxyException made
_is_proxy_only_llm_api_error return False for them, so
_handle_logging_proxy_only_error was skipped and the blocked prompt was
dropped from the configured failure loggers. Classify ProxyException as a
proxy-only error alongside HTTPException so guardrail blocks are recorded
again, matching the prior behavior. The llm_exceptions alert suppression
is a separate check and stays in place

Resolves LIT-3751

* style(guardrails): use str | None over Optional[str] in AIM _rejection

* style(guardrails): collapse AIM _rejection signature per black

(cherry picked from commit b5fcd85)
…ssage (LIT-3094) (#29015)

* fix(proxy): populate Exception.args so str(ProxyException) returns message

Adds super().__init__(self.message) to ProxyException.__init__ so that
str(exc) returns the stored message instead of empty string. Fixes LIT-3094.

* test(proxy): regression tests for ProxyException.args (LIT-3094)

* fix(proxy): populate Exception.args so str(ProxyException) returns message (LIT-3094)

* fix(proxy): clean up unintended drift; keep only ProxyException.args fix (LIT-3094)

(cherry picked from commit 1fe911d)
@yuneng-berri
yuneng-berri requested a review from a team June 20, 2026 19:34
@CLAassistant

CLAassistant commented Jun 20, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
4 out of 6 committers have signed the CLA.

✅ mateo-berri
✅ ryan-crabbe-berri
✅ shivamrawat1
✅ yuneng-berri
❌ yassin-berriai
❌ oss-agent-shin
You have signed the CLA already but the status is still pending? Let us recheck it.

@codecov

codecov Bot commented Jun 20, 2026

Copy link
Copy Markdown

@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This backport cherry-picks five targeted bug fixes onto stable/1.88.x and cuts version 1.88.4. Each pick addresses a discrete production defect with a focused code change and dedicated mock-only tests.

Confidence Score: 4/5

Safe to merge; all five changes are narrowly scoped bug fixes with thorough mock-only test coverage and no schema or API-contract changes.

The DataDog splitting logic is the most complex change and carries a minor misleading debug log in mock mode (flagged above), but the delivery logic itself is sound and well-tested across six new scenarios including partial-delivery and transient-error cases. The remaining four changes are minimal and each is guarded by dedicated regression tests. The known pre-existing test failure (test_get_custom_url) is documented and unrelated to any of the picks.

litellm/integrations/datadog/datadog.py — the split-on-413 implementation is the most intricate new code; the mock mode success log could be tightened as suggested.

Important Files Changed

Filename Overview
litellm/integrations/datadog/datadog.py Replaces the old 413-requeue loop with a _send_with_413_split method that recursively halves oversized batches; adds _resolve_dd_batch_size() to allow env-var override of batch size
litellm/integrations/anthropic_cache_control_hook.py Refactors cache-control injection to enforce Anthropic's 4-block limit; fixes a latent bug where role-based injection silently dropped its write (local variable reassignment instead of list index assignment)
litellm/proxy/guardrails/guardrail_hooks/aim/aim.py Replaces HTTPException raises with ProxyException via a new _rejection() factory, removing the fastapi dependency from this file and returning conformant 400 errors on guardrail blocks
litellm/proxy/common_request_processing.py Inserts a ProxyException branch before the existing HTTPException branch so retry/cooldown headers are forwarded on guardrail rejections
litellm/proxy/_types.py Adds super().__init__(self.message) to ProxyException.__init__ so str(exc) returns the message instead of an empty string (LIT-3094)
litellm/types/utils.py Adds use_chat_completions_api to all_litellm_params so the control flag is stripped from the provider request body instead of leaking through
litellm/proxy/utils.py Excludes ProxyException from LLM alerting and adds it to the "proxy-only error" classification alongside HTTPException

Reviews (1): Last reviewed commit: "chore: refresh uv.lock for 1.88.4" | Re-trigger Greptile

Comment on lines 365 to 368
if self.is_mock_mode:
verbose_logger.debug(
f"[DATADOG MOCK] Batch of {len(batch_to_send)} events successfully mocked"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The "successfully mocked" log fires unconditionally after _send_with_413_split, even when undelivered is non-empty. In mock mode with an oversized payload this would print a success message while some events are still queued for re-delivery, making it hard to diagnose partial-delivery scenarios.

Suggested change
if self.is_mock_mode:
verbose_logger.debug(
f"[DATADOG MOCK] Batch of {len(batch_to_send)} events successfully mocked"
)
if self.is_mock_mode and not undelivered:
verbose_logger.debug(
f"[DATADOG MOCK] Batch of {len(batch_to_send)} events successfully mocked"
)

@yuneng-berri
yuneng-berri merged commit 26b3917 into stable/1.88.x Jun 20, 2026
31 of 32 checks passed
@yuneng-berri
yuneng-berri deleted the litellm_backport_1_88_x_bp-188x-0620 branch June 20, 2026 21:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants