diff --git a/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py b/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py index 3587aa8042f..aaf357effa6 100644 --- a/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py +++ b/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py @@ -605,11 +605,15 @@ class AmazonAnthropicClaudeMessagesConfig( anthropic_messages_request["system"] = system_messages_to_add elif isinstance(existing_system, str): if existing_system: - anthropic_messages_request["system"] = [{"type": "text", "text": existing_system}] + system_messages_to_add + anthropic_messages_request["system"] = [ + {"type": "text", "text": existing_system} + ] + system_messages_to_add else: anthropic_messages_request["system"] = system_messages_to_add elif isinstance(existing_system, list): - anthropic_messages_request["system"] = list(existing_system) + system_messages_to_add + anthropic_messages_request["system"] = ( + list(existing_system) + system_messages_to_add + ) def transform_anthropic_messages_request( self, diff --git a/tests/pass_through_unit_tests/test_bedrock_anthropic_messages_test.py b/tests/pass_through_unit_tests/test_bedrock_anthropic_messages_test.py index 0ec468b5d8a..4dbf841ca2a 100644 --- a/tests/pass_through_unit_tests/test_bedrock_anthropic_messages_test.py +++ b/tests/pass_through_unit_tests/test_bedrock_anthropic_messages_test.py @@ -144,58 +144,58 @@ def test_bedrock_anthropic_messages_system_role_transformation(): to the top-level system parameter for Bedrock. """ config = litellm.AmazonAnthropicClaudeMessagesConfig() - + # 1. Test case: System message in messages array, no existing system param req = config.transform_anthropic_messages_request( model="bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0", messages=[ {"role": "system", "content": "You are a helpful assistant"}, - {"role": "user", "content": "Hello"} + {"role": "user", "content": "Hello"}, ], anthropic_messages_optional_request_params={"max_tokens": 100}, litellm_params={}, - headers={} + headers={}, ) - + assert req["messages"] == [{"role": "user", "content": "Hello"}] assert req["system"] == [{"type": "text", "text": "You are a helpful assistant"}] - + # 2. Test case: System message in messages array, and existing system param req = config.transform_anthropic_messages_request( model="bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0", messages=[ {"role": "system", "content": "System message in messages array"}, - {"role": "user", "content": "Hello"} + {"role": "user", "content": "Hello"}, ], anthropic_messages_optional_request_params={ "max_tokens": 100, - "system": "Existing top-level system message" + "system": "Existing top-level system message", }, litellm_params={}, - headers={} + headers={}, ) - + assert req["messages"] == [{"role": "user", "content": "Hello"}] assert req["system"] == [ {"type": "text", "text": "Existing top-level system message"}, - {"type": "text", "text": "System message in messages array"} + {"type": "text", "text": "System message in messages array"}, ] - + # 3. Test case: Empty system message content (should remove system message from messages, system param unaffected) req = config.transform_anthropic_messages_request( model="bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0", messages=[ {"role": "system", "content": ""}, - {"role": "user", "content": "Hello"} + {"role": "user", "content": "Hello"}, ], anthropic_messages_optional_request_params={"max_tokens": 100}, litellm_params={}, - headers={} + headers={}, ) - + assert req["messages"] == [{"role": "user", "content": "Hello"}] assert "system" not in req - + # 4. Test case: Non-text items in list content (should only extract text items, ignore non-text) req = config.transform_anthropic_messages_request( model="bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0", @@ -204,16 +204,155 @@ def test_bedrock_anthropic_messages_system_role_transformation(): "role": "system", "content": [ {"type": "text", "text": "This is text"}, - {"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": "abc"}} - ] + { + "type": "image", + "source": { + "type": "base64", + "media_type": "image/jpeg", + "data": "abc", + }, + }, + ], }, - {"role": "user", "content": "Hello"} + {"role": "user", "content": "Hello"}, ], anthropic_messages_optional_request_params={"max_tokens": 100}, litellm_params={}, - headers={} + headers={}, ) - + assert req["messages"] == [{"role": "user", "content": "Hello"}] assert req["system"] == [{"type": "text", "text": "This is text"}] + # 5. Test case: cache_control in system message (should preserve cache_control) + req = config.transform_anthropic_messages_request( + model="bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0", + messages=[ + { + "role": "system", + "content": "Cached system message", + "cache_control": {"type": "ephemeral"}, + }, + {"role": "user", "content": "Hello"}, + ], + anthropic_messages_optional_request_params={"max_tokens": 100}, + litellm_params={}, + headers={}, + ) + assert req["messages"] == [{"role": "user", "content": "Hello"}] + assert req["system"] == [ + { + "type": "text", + "text": "Cached system message", + "cache_control": {"type": "ephemeral"}, + } + ] + + # 6. Test case: content as list, type text with empty text, cache_control, and list containing non-dict items + req = config.transform_anthropic_messages_request( + model="bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0", + messages=[ + { + "role": "system", + "content": [ + { + "type": "text", + "text": "This is text", + "cache_control": {"type": "ephemeral"}, + }, + {"type": "text", "text": ""}, # empty text, should be ignored + "invalid_non_dict_item", # non-dict item in list, should be ignored + ], + }, + {"role": "user", "content": "Hello"}, + ], + anthropic_messages_optional_request_params={"max_tokens": 100}, + litellm_params={}, + headers={}, + ) + assert req["messages"] == [{"role": "user", "content": "Hello"}] + assert req["system"] == [ + { + "type": "text", + "text": "This is text", + "cache_control": {"type": "ephemeral"}, + } + ] + + # 7. Test case: system_messages_found is True but system_messages_to_add is empty (e.g., content list only has non-text item) + req = config.transform_anthropic_messages_request( + model="bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0", + messages=[ + { + "role": "system", + "content": [ + { + "type": "image", + "source": { + "type": "base64", + "media_type": "image/jpeg", + "data": "abc", + }, + } + ], + }, + {"role": "user", "content": "Hello"}, + ], + anthropic_messages_optional_request_params={"max_tokens": 100}, + litellm_params={}, + headers={}, + ) + assert req["messages"] == [{"role": "user", "content": "Hello"}] + assert "system" not in req + + # 8. Test case: existing_system is empty string + req = config.transform_anthropic_messages_request( + model="bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0", + messages=[ + {"role": "system", "content": "System message"}, + {"role": "user", "content": "Hello"}, + ], + anthropic_messages_optional_request_params={ + "max_tokens": 100, + "system": "", + }, + litellm_params={}, + headers={}, + ) + assert req["messages"] == [{"role": "user", "content": "Hello"}] + assert req["system"] == [{"type": "text", "text": "System message"}] + + # 9. Test case: existing_system is list + req = config.transform_anthropic_messages_request( + model="bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0", + messages=[ + {"role": "system", "content": "System message"}, + {"role": "user", "content": "Hello"}, + ], + anthropic_messages_optional_request_params={ + "max_tokens": 100, + "system": [{"type": "text", "text": "Existing list item"}], + }, + litellm_params={}, + headers={}, + ) + assert req["messages"] == [{"role": "user", "content": "Hello"}] + assert req["system"] == [ + {"type": "text", "text": "Existing list item"}, + {"type": "text", "text": "System message"}, + ] + + # 10. Test case: messages contains elements that are not dicts or do not have role="system" + req_data = { + "messages": [ + "not_a_dict_message", + {"content": "no_role_key"}, + {"role": "user", "content": "Hello"}, + ] + } + config._extract_system_messages_from_messages(req_data) + assert req_data["messages"] == [ + "not_a_dict_message", + {"content": "no_role_key"}, + {"role": "user", "content": "Hello"}, + ]