fix: sanitize empty text content blocks in /v1/messages endpoint

Claude's API returns assistant messages with empty text blocks
alongside tool_use blocks, but rejects them when sent back in
subsequent requests. The /v1/messages endpoint passes messages
through without sanitizing these blocks, causing 400 errors in
multi-turn tool-use conversations.

Add _sanitize_anthropic_empty_text_blocks() to strip empty text
content blocks from Anthropic-format messages before forwarding.

Fixes #22930
This commit is contained in:
atian8179 2026-03-07 00:01:14 +08:00
parent 8b0375f99c
commit fd12506ef8

View file

@ -7,6 +7,7 @@
import asyncio
import contextvars
from copy import deepcopy
from functools import partial
from typing import Any, AsyncIterator, Coroutine, Dict, List, Optional, Union
@ -202,6 +203,38 @@ def validate_anthropic_api_metadata(metadata: Optional[Dict] = None) -> Optional
return anthropic_metadata_obj.model_dump(exclude_none=True)
def _sanitize_anthropic_empty_text_blocks(messages: List[Dict]) -> List[Dict]:
"""Remove empty text content blocks from Anthropic-format messages.
Claude's API can return assistant messages with empty text blocks alongside
tool_use blocks (e.g., {"type": "text", "text": ""}). These are valid in
responses but rejected when sent back in subsequent requests. This function
strips empty text blocks from content arrays, preserving all other blocks.
See: https://github.com/BerriAI/litellm/issues/22930
"""
sanitized = []
for msg in messages:
content = msg.get("content")
if isinstance(content, list):
filtered = [
block for block in content
if not (
isinstance(block, dict)
and block.get("type") == "text"
and not block.get("text", "").strip()
)
]
if len(filtered) != len(content):
msg = deepcopy(msg)
# If all blocks were empty text, keep one with placeholder
msg["content"] = filtered if filtered else [{"type": "text", "text": "."}]
sanitized.append(msg)
else:
sanitized.append(msg)
return sanitized
def anthropic_messages_handler(
max_tokens: int,
messages: List[Dict],
@ -237,6 +270,12 @@ def anthropic_messages_handler(
metadata = validate_anthropic_api_metadata(metadata)
# Sanitize empty text content blocks in messages to prevent 400 errors.
# Claude's API can return assistant messages with empty text blocks alongside
# tool_use blocks, but rejects them when sent back in subsequent requests.
# See: https://github.com/BerriAI/litellm/issues/22930
messages = _sanitize_anthropic_empty_text_blocks(messages)
local_vars = locals()
is_async = kwargs.pop("is_async", False)
# Use provided client or create a new one