diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index a5a4ab6cbf24..01e16fc1e7fc 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -3803,7 +3803,9 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915 assistant_parts=assistants_parts, ) elif element["type"] == "text": - assistants_part = BedrockContentBlock(text=element["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) elif element["type"] == "image_url": if isinstance(element["image_url"], dict): @@ -3827,7 +3829,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): - assistant_content.append(BedrockContentBlock(text=_assistant_content)) + # 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)) # Add cache point block for assistant string content _cache_point_block = ( litellm.AmazonConverseConfig()._get_cache_point_block( diff --git a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py index d701b54d7f83..c09d3b8d841b 100644 --- a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py @@ -2584,3 +2584,95 @@ def test_request_metadata_not_provided(): # requestMetadata should not be in the request assert "requestMetadata" not in request_data + + +def test_empty_assistant_message_handling(): + """ + Test that empty assistant messages are handled correctly by replacing + empty or whitespace-only content with a placeholder to prevent AWS Bedrock + Converse API 400 Bad Request errors. + """ + from litellm.litellm_core_utils.prompt_templates.factory import _bedrock_converse_messages_pt + + # Test case 1: Empty string content - test with modify_params=True to prevent merging + messages = [ + {"role": "user", "content": "Hello"}, + {"role": "assistant", "content": ""}, # Empty content + {"role": "user", "content": "How are you?"} + ] + + # Enable modify_params to prevent consecutive user message merging + original_modify_params = litellm.modify_params + litellm.modify_params = True + + try: + result = _bedrock_converse_messages_pt( + messages=messages, + model="anthropic.claude-3-5-sonnet-20240620-v1:0", + llm_provider="bedrock_converse" + ) + + # Should have 3 messages: user, assistant (with placeholder), user + assert len(result) == 3 + assert result[0]["role"] == "user" + assert result[1]["role"] == "assistant" + assert result[2]["role"] == "user" + + # Assistant message should have placeholder text instead of empty content + assert len(result[1]["content"]) == 1 + assert result[1]["content"][0]["text"] == "Please continue." + + # Test case 2: Whitespace-only content + messages = [ + {"role": "user", "content": "Hello"}, + {"role": "assistant", "content": " "}, # Whitespace-only content + {"role": "user", "content": "How are you?"} + ] + + result = _bedrock_converse_messages_pt( + messages=messages, + model="anthropic.claude-3-5-sonnet-20240620-v1:0", + llm_provider="bedrock_converse" + ) + + # Assistant message should have placeholder text instead of whitespace + assert len(result[1]["content"]) == 1 + assert result[1]["content"][0]["text"] == "Please continue." + + # Test case 3: Empty list content + messages = [ + {"role": "user", "content": "Hello"}, + {"role": "assistant", "content": [{"type": "text", "text": ""}]}, # Empty text in list + {"role": "user", "content": "How are you?"} + ] + + result = _bedrock_converse_messages_pt( + messages=messages, + model="anthropic.claude-3-5-sonnet-20240620-v1:0", + llm_provider="bedrock_converse" + ) + + # Assistant message should have placeholder text instead of empty text + assert len(result[1]["content"]) == 1 + assert result[1]["content"][0]["text"] == "Please continue." + + # Test case 4: Normal content should not be affected + messages = [ + {"role": "user", "content": "Hello"}, + {"role": "assistant", "content": "I'm doing well, thank you!"}, # Normal content + {"role": "user", "content": "How are you?"} + ] + + result = _bedrock_converse_messages_pt( + messages=messages, + model="anthropic.claude-3-5-sonnet-20240620-v1:0", + llm_provider="bedrock_converse" + ) + + # Assistant message should keep original content + assert len(result[1]["content"]) == 1 + assert result[1]["content"][0]["text"] == "I'm doing well, thank you!" + + finally: + # Restore original modify_params setting + litellm.modify_params = original_modify_params