mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
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:
parent
8b0375f99c
commit
fd12506ef8
1 changed files with 39 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue