chore(release): backport #29015, #29444, #29447, #30480, #30573 to stable/1.88.x and cut 1.88.4 - #30889
Conversation
…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)
|
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Greptile SummaryThis backport cherry-picks five targeted bug fixes onto
Confidence Score: 4/5Safe 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 (
|
| 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
| if self.is_mock_mode: | ||
| verbose_logger.debug( | ||
| f"[DATADOG MOCK] Batch of {len(batch_to_send)} events successfully mocked" | ||
| ) |
There was a problem hiding this comment.
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.
| 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" | |
| ) |
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
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:
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.