mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-17 04:26:20 +00:00
Merge pull request #20390 from BerriAI/litellm_fix_empty_message
Fix: empty assistant message for converse API
This commit is contained in:
@@ -3987,10 +3987,12 @@ class BedrockConverseMessagesProcessor:
|
||||
assistant_parts=assistants_parts,
|
||||
)
|
||||
elif element["type"] == "text":
|
||||
assistants_part = BedrockContentBlock(
|
||||
text=element["text"]
|
||||
)
|
||||
assistants_parts.append(assistants_part)
|
||||
# Skip completely empty strings to avoid blank content blocks
|
||||
if element.get("text", "").strip():
|
||||
assistants_part = BedrockContentBlock(
|
||||
text=element["text"]
|
||||
)
|
||||
assistants_parts.append(assistants_part)
|
||||
elif element["type"] == "image_url":
|
||||
if isinstance(element["image_url"], dict):
|
||||
image_url = element["image_url"]["url"]
|
||||
@@ -4015,9 +4017,12 @@ class BedrockConverseMessagesProcessor:
|
||||
elif _assistant_content is not None and isinstance(
|
||||
_assistant_content, str
|
||||
):
|
||||
assistant_content.append(
|
||||
BedrockContentBlock(text=_assistant_content)
|
||||
)
|
||||
# Skip completely empty strings to avoid blank content blocks
|
||||
if _assistant_content.strip():
|
||||
assistant_content.append(
|
||||
BedrockContentBlock(text=_assistant_content)
|
||||
)
|
||||
# If content is empty/whitespace, skip it (don't add a placeholder)
|
||||
# Add cache point block for assistant string content
|
||||
_cache_point_block = (
|
||||
litellm.AmazonConverseConfig()._get_cache_point_block(
|
||||
@@ -4348,12 +4353,11 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915
|
||||
assistant_parts=assistants_parts,
|
||||
)
|
||||
elif element["type"] == "text":
|
||||
# AWS Bedrock doesn't allow empty or whitespace-only text content, so use placeholder for empty strings
|
||||
text_content = (
|
||||
element["text"] if element["text"].strip() else "."
|
||||
)
|
||||
assistants_part = BedrockContentBlock(text=text_content)
|
||||
assistants_parts.append(assistants_part)
|
||||
# AWS Bedrock doesn't allow empty or whitespace-only text content
|
||||
# Skip completely empty strings to avoid blank content blocks
|
||||
if element.get("text", "").strip():
|
||||
assistants_part = BedrockContentBlock(text=element["text"])
|
||||
assistants_parts.append(assistants_part)
|
||||
elif element["type"] == "image_url":
|
||||
if isinstance(element["image_url"], dict):
|
||||
image_url = element["image_url"]["url"]
|
||||
@@ -4376,9 +4380,9 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915
|
||||
assistants_parts.append(_cache_point_block)
|
||||
assistant_content.extend(assistants_parts)
|
||||
elif _assistant_content is not None and isinstance(_assistant_content, str):
|
||||
# AWS Bedrock doesn't allow empty or whitespace-only text content, so use placeholder for empty strings
|
||||
text_content = _assistant_content if _assistant_content.strip() else "."
|
||||
assistant_content.append(BedrockContentBlock(text=text_content))
|
||||
# Skip completely empty strings to avoid blank content blocks
|
||||
if _assistant_content.strip():
|
||||
assistant_content.append(BedrockContentBlock(text=_assistant_content))
|
||||
# Add cache point block for assistant string content
|
||||
_cache_point_block = (
|
||||
litellm.AmazonConverseConfig()._get_cache_point_block(
|
||||
|
||||
@@ -330,3 +330,118 @@ async def test_bedrock_converse_sync_async_parity_with_duplicates():
|
||||
)
|
||||
|
||||
assert sync_result == async_result
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Empty content filtering tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_bedrock_converse_filters_empty_assistant_content():
|
||||
"""Verify that empty assistant content blocks are filtered out to avoid
|
||||
Bedrock API errors about blank text fields."""
|
||||
messages = [
|
||||
{"role": "user", "content": "Say hello"},
|
||||
{"role": "assistant", "content": "Hello"},
|
||||
{"role": "assistant", "content": " there"},
|
||||
{"role": "assistant", "content": "!"},
|
||||
{"role": "assistant", "content": ""}, # Empty content
|
||||
{"role": "assistant", "content": ""}, # Empty content
|
||||
{"role": "user", "content": "How are you?"},
|
||||
]
|
||||
|
||||
result = _bedrock_converse_messages_pt(messages, MODEL, PROVIDER)
|
||||
|
||||
# Should have 3 messages: user, assistant (with merged non-empty content), user
|
||||
assert len(result) == 3
|
||||
assert result[0]["role"] == "user"
|
||||
assert result[1]["role"] == "assistant"
|
||||
assert result[2]["role"] == "user"
|
||||
|
||||
# Assistant message should only contain non-empty text blocks
|
||||
assistant_content = result[1]["content"]
|
||||
text_blocks = [block for block in assistant_content if "text" in block]
|
||||
assert len(text_blocks) == 3 # "Hello", " there", "!"
|
||||
assert text_blocks[0]["text"] == "Hello"
|
||||
assert text_blocks[1]["text"] == " there"
|
||||
assert text_blocks[2]["text"] == "!"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bedrock_converse_filters_empty_assistant_content_async():
|
||||
"""Verify that the async path also filters empty assistant content blocks."""
|
||||
messages = [
|
||||
{"role": "user", "content": "Say hello"},
|
||||
{"role": "assistant", "content": "Hello"},
|
||||
{"role": "assistant", "content": " there"},
|
||||
{"role": "assistant", "content": "!"},
|
||||
{"role": "assistant", "content": ""}, # Empty content
|
||||
{"role": "assistant", "content": ""}, # Empty content
|
||||
{"role": "user", "content": "How are you?"},
|
||||
]
|
||||
|
||||
result = await BedrockConverseMessagesProcessor._bedrock_converse_messages_pt_async(
|
||||
messages, MODEL, PROVIDER
|
||||
)
|
||||
|
||||
# Should have 3 messages: user, assistant (with merged non-empty content), user
|
||||
assert len(result) == 3
|
||||
assert result[0]["role"] == "user"
|
||||
assert result[1]["role"] == "assistant"
|
||||
assert result[2]["role"] == "user"
|
||||
|
||||
# Assistant message should only contain non-empty text blocks
|
||||
assistant_content = result[1]["content"]
|
||||
text_blocks = [block for block in assistant_content if "text" in block]
|
||||
assert len(text_blocks) == 3 # "Hello", " there", "!"
|
||||
assert text_blocks[0]["text"] == "Hello"
|
||||
assert text_blocks[1]["text"] == " there"
|
||||
assert text_blocks[2]["text"] == "!"
|
||||
|
||||
|
||||
def test_bedrock_converse_filters_whitespace_only_content():
|
||||
"""Verify that whitespace-only content is also filtered out."""
|
||||
messages = [
|
||||
{"role": "user", "content": "Test"},
|
||||
{"role": "assistant", "content": "Response"},
|
||||
{"role": "assistant", "content": " "}, # Whitespace only
|
||||
{"role": "assistant", "content": "\n\t"}, # Whitespace only
|
||||
{"role": "assistant", "content": ""}, # Empty
|
||||
]
|
||||
|
||||
result = _bedrock_converse_messages_pt(messages, MODEL, PROVIDER)
|
||||
|
||||
# Should have 2 messages: user and assistant
|
||||
assert len(result) == 2
|
||||
assistant_content = result[1]["content"]
|
||||
text_blocks = [block for block in assistant_content if "text" in block]
|
||||
# Only "Response" should be present
|
||||
assert len(text_blocks) == 1
|
||||
assert text_blocks[0]["text"] == "Response"
|
||||
|
||||
|
||||
def test_bedrock_converse_filters_empty_list_content():
|
||||
"""Verify that empty text elements in list content are filtered out."""
|
||||
messages = [
|
||||
{"role": "user", "content": "Test"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{"type": "text", "text": "Hello"},
|
||||
{"type": "text", "text": ""}, # Empty
|
||||
{"type": "text", "text": "World"},
|
||||
{"type": "text", "text": " "}, # Whitespace only
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
result = _bedrock_converse_messages_pt(messages, MODEL, PROVIDER)
|
||||
|
||||
# Should have 2 messages: user and assistant
|
||||
assert len(result) == 2
|
||||
assistant_content = result[1]["content"]
|
||||
text_blocks = [block for block in assistant_content if "text" in block]
|
||||
# Only "Hello" and "World" should be present
|
||||
assert len(text_blocks) == 2
|
||||
assert text_blocks[0]["text"] == "Hello"
|
||||
assert text_blocks[1]["text"] == "World"
|
||||
|
||||
@@ -2356,9 +2356,8 @@ def test_bedrock_no_default_message():
|
||||
assistant_messages = [
|
||||
msg for msg in formatted_messages if msg["role"] == "assistant"
|
||||
]
|
||||
assert len(assistant_messages) == 2
|
||||
assert assistant_messages[0]["content"][0]["text"] == "."
|
||||
assert assistant_messages[1]["content"][0]["text"] == "Valid response"
|
||||
assert len(assistant_messages) == 1
|
||||
assert assistant_messages[0]["content"][0]["text"] == "Valid response"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("top_k_param", ["top_k", "topK"])
|
||||
|
||||
Reference in New Issue
Block a user