Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 0 additions & 60 deletions litellm/llms/custom_httpx/llm_http_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,59 +152,6 @@
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,
Comment on lines 152 to 157

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.

Expand Down Expand Up @@ -1958,13 +1905,6 @@ async def async_anthropic_messages_handler(
anthropic_messages_optional_request_params, path
)

# Sanitize empty text content blocks from messages before forwarding.
# Claude's API returns assistant messages with empty text blocks
# ({"type": "text", "text": ""}) alongside tool_use blocks, but rejects
# them when sent back. Strip these to prevent 400 errors.
# Ref: https://github.com/BerriAI/litellm/issues/22930
messages = _sanitize_anthropic_messages_empty_text_blocks(messages)

# Prepare request body
request_body = anthropic_messages_provider_config.transform_anthropic_messages_request(
model=model,
Expand Down

This file was deleted.

Loading