fix(bedrock): address edge cases and formatting for system role extraction helper

This commit is contained in:
Charbel Toumieh 2026-06-05 09:42:01 +01:00
parent 1ce773bd92
commit 6ec8bf4fe9
2 changed files with 165 additions and 22 deletions

View file

@ -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,

View file

@ -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"},
]