Skip to content

Revert "fix: strip empty text content blocks in /v1/messages endpoint" - #23232

Merged
Sameerlite merged 1 commit into
mainfrom
revert-23097-fix/sanitize-empty-text-blocks-v1-messages
Mar 10, 2026
Merged

Revert "fix: strip empty text content blocks in /v1/messages endpoint"#23232
Sameerlite merged 1 commit into
mainfrom
revert-23097-fix/sanitize-empty-text-blocks-v1-messages

Conversation

@Sameerlite

Copy link
Copy Markdown
Contributor

Reverts #23097

Breaks test_bad_request_error_handling_streaming

@vercel

vercel Bot commented Mar 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Building Building Preview, Comment Mar 10, 2026 4:23am

Request Review

@Sameerlite
Sameerlite merged commit 1ec9d98 into main Mar 10, 2026
24 of 36 checks passed
@greptile-apps

greptile-apps Bot commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR reverts #23097 (fix: strip empty text content blocks in /v1/messages endpoint) because it broke test_bad_request_error_handling_streaming. The justification is insufficient — the test broke due to a narrow, one-line bug in the sanitization helper (message.get("content") being called on a bare-string list element like "hi" rather than a dict), not a fundamental flaw in the approach. A full revert is overly broad.

Key issues:

  • Re-introduces production bug [Bug]: /v1/messages endpoint does not sanitize empty text content blocks #22930: Multi-turn tool-use conversations via /v1/messages will again receive 400 "text content blocks must be non-empty" errors when Claude returns empty text blocks alongside tool_use blocks, because the sanitization pass has been removed.
  • Deletes 10 unit tests with no replacement: test_v1_messages_empty_text_sanitization.py is fully removed, leaving zero coverage for the empty-text-block scenario.
  • Root cause of test failure was trivial: The original sanitization function lacked an isinstance(message, dict) guard. When messages=["hi"] (a bare string in a list) was passed, message.get("content") raised AttributeError before the request reached Anthropic — the fix was a single guard line, not a revert.
  • PR description lacks evidence of resolution: The description only states what broke — it does not demonstrate that the underlying issue ([Bug]: /v1/messages endpoint does not sanitize empty text content blocks #22930) is acceptable to regress, or that an alternative fix was considered.

Confidence Score: 1/5

  • This PR re-introduces a confirmed production regression ([Bug]: /v1/messages endpoint does not sanitize empty text content blocks #22930) and removes all test coverage for the affected scenario without a corrective plan.
  • The revert is triggered by a narrow, fixable bug (missing isinstance check) in the sanitizer, yet removes the entire sanitization pass. This re-exposes all /v1/messages multi-turn tool-use users to 400 errors. The deleted test file means the regression will not be caught by automated tests going forward.
  • litellm/llms/custom_httpx/llm_http_handler.py (missing sanitization) and the deleted tests/test_litellm/llms/anthropic/test_v1_messages_empty_text_sanitization.py (lost coverage).

Important Files Changed

Filename Overview
litellm/llms/custom_httpx/llm_http_handler.py Removes _sanitize_anthropic_messages_empty_text_blocks and its call site, re-introducing issue #22930 where empty text content blocks in multi-turn tool-use conversations cause 400 errors from the Anthropic API. The original test failure had a narrow fix that didn't require a full revert.
tests/test_litellm/llms/anthropic/test_v1_messages_empty_text_sanitization.py Entire test file with 10 unit tests for empty-text-block sanitization is deleted, removing all coverage for the /v1/messages multi-turn tool-use scenario described in issue #22930.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Client sends /v1/messages request\nwith multi-turn tool-use history] --> B{Messages contain\nempty text blocks?}
    B -- No --> C[Forward to Anthropic API]
    B -- Yes --> D_before["BEFORE revert:\n_sanitize_anthropic_messages_empty_text_blocks()"]
    D_before --> E{All blocks\nempty text?}
    E -- No --> F["Remove empty text blocks\n(keep tool_use, etc.)"]
    E -- Yes --> G["Replace with placeholder\n{type: text, text: ...}"]
    F --> C
    G --> C
    C --> H[✅ Anthropic returns 200]

    B -- Yes --> D_after["AFTER revert (this PR):\nNo sanitization"]
    D_after --> I["Forward empty text blocks\nas-is to Anthropic"]
    I --> J["❌ Anthropic returns 400\n'text content blocks must be non-empty'"]

    style J fill:#f55,color:#fff
    style H fill:#5a5,color:#fff
Loading

Comments Outside Diff (1)

  1. tests/test_litellm/llms/anthropic/test_v1_messages_empty_text_sanitization.py

    Test coverage for a real bug is being deleted

    This file contains 10 unit tests that validate the sanitization of empty text content blocks — a behaviour that is required for multi-turn tool-use conversations to work correctly with the Anthropic API (issue [Bug]: /v1/messages endpoint does not sanitize empty text content blocks #22930). Deleting it means there is now no test coverage ensuring those scenarios are handled, and anyone who later re-introduces the sanitization fix will have to re-write all these tests.

    If the revert is necessary as a temporary measure, the test file should be kept and its import updated (or the function temporarily stubbed) to preserve coverage, rather than dropped entirely.

Last reviewed commit: c1b860b

Comment on lines 152 to 157
LiteLLMLoggingObj = Any


def _sanitize_anthropic_messages_empty_text_blocks(
messages: List[Dict],
) -> List[Dict]:
"""
Strip empty text content blocks from Anthropic-format messages.

Claude's API returns assistant messages with ``{"type": "text", "text": ""}``
alongside ``tool_use`` blocks, but rejects them when sent back in subsequent
requests. This helper removes those empty text blocks so the /v1/messages
native path doesn't forward them as-is.

- If a content list contains a mix of empty text blocks and other blocks
(e.g. tool_use), the empty text blocks are removed.
- If *all* blocks in a content list are empty text, the content is replaced
with a single non-empty placeholder to avoid sending an empty array.

Ref: https://github.com/BerriAI/litellm/issues/22930
"""
sanitized: List[Dict] = []
for message in messages:
content = message.get("content")
if not isinstance(content, list):
sanitized.append(message)
continue

filtered = [
block
for block in content
if not (
isinstance(block, dict)
and block.get("type") == "text"
and not block.get("text", "").strip()
)
]

if filtered == content:
# Nothing was removed — keep original message as-is.
sanitized.append(message)
elif filtered:
# Some empty text blocks removed, but other content remains.
new_message = message.copy()
new_message["content"] = filtered
sanitized.append(new_message)
else:
# All blocks were empty text blocks. Replace with a placeholder
# so we don't send an empty content array.
new_message = message.copy()
new_message["content"] = [{"type": "text", "text": "..."}]
sanitized.append(new_message)

return sanitized


class BaseLLMHTTPHandler:
async def _make_common_async_call(
self,

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.

Revert re-introduces issue #22930

This revert removes the fix for a confirmed production bug: when Claude returns assistant messages with {"type": "text", "text": ""} alongside tool_use blocks during multi-turn agentic conversations, forwarding those messages back to the API fails with a 400 "text content blocks must be non-empty" error.

The root cause of the test breakage was a narrow, trivially-fixable bug in the sanitization function itself. test_bad_request_error_handling_streaming passes messages=["hi"] (a list containing a bare string, not a dict). The function iterated over messages and called message.get("content"), which raises AttributeError on a string, crashing before the request reached Anthropic's API.

The correct minimal fix would have been a single isinstance guard:

for message in messages:
    if not isinstance(message, dict):
        sanitized.append(message)
        continue
    content = message.get("content")
    ...

A full revert is too broad — it undoes a valid fix and re-exposes all users doing multi-turn tool-use conversations via /v1/messages to the original 400-error regression.

@saurabhghere

Copy link
Copy Markdown

@Sameerlite Is this issue fixed in any latest version?

@ishaan-berri
ishaan-berri deleted the revert-23097-fix/sanitize-empty-text-blocks-v1-messages branch March 26, 2026 22:30
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…ze-empty-text-blocks-v1-messages

Revert "fix: strip empty text content blocks in /v1/messages endpoint"
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.

2 participants