mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix(factory): handle list content in map_system_message_pt
Fixes #23757 When the Anthropic pass-through endpoint converts messages to OpenAI format, content fields can be lists of content blocks rather than plain strings. map_system_message_pt assumed string content and crashed with TypeError on concatenation. Add _get_content_as_str() helper that normalizes both str and list content to a string before merging, using the existing convert_content_list_to_str utility. Tests: 3 new test cases covering list content, mixed str/list, and list content as last message.
This commit is contained in:
parent
3dccdde9c8
commit
cd710fe919
2 changed files with 69 additions and 3 deletions
|
|
@ -86,6 +86,15 @@ DEFAULT_ASSISTANT_CONTINUE_MESSAGE = ChatCompletionAssistantMessage(
|
|||
) # similar to autogen. Only used if `litellm.modify_params=True`.
|
||||
|
||||
|
||||
def _get_content_as_str(content: Union[str, list]) -> str:
|
||||
"""Extract text from content that may be a string or a list of content blocks."""
|
||||
if isinstance(content, str):
|
||||
return content
|
||||
if isinstance(content, list):
|
||||
return convert_content_list_to_str({"content": content})
|
||||
return str(content)
|
||||
|
||||
|
||||
def map_system_message_pt(messages: list) -> list:
|
||||
"""
|
||||
Convert 'system' message to 'user' message if provider doesn't support 'system' role.
|
||||
|
|
@ -100,6 +109,7 @@ def map_system_message_pt(messages: list) -> list:
|
|||
new_messages = []
|
||||
for i, m in enumerate(messages):
|
||||
if m["role"] == "system":
|
||||
system_text = _get_content_as_str(m["content"])
|
||||
if i < len(messages) - 1: # Not the last message
|
||||
next_m = messages[i + 1]
|
||||
next_role = next_m["role"]
|
||||
|
|
@ -107,13 +117,14 @@ def map_system_message_pt(messages: list) -> list:
|
|||
next_role == "user" or next_role == "assistant"
|
||||
): # Next message is a user or assistant message
|
||||
# Merge system prompt into the next message
|
||||
next_m["content"] = m["content"] + " " + next_m["content"]
|
||||
next_text = _get_content_as_str(next_m["content"])
|
||||
next_m["content"] = system_text + " " + next_text
|
||||
elif next_role == "system": # Next message is a system message
|
||||
# Append a user message instead of the system message
|
||||
new_message = {"role": "user", "content": m["content"]}
|
||||
new_message = {"role": "user", "content": system_text}
|
||||
new_messages.append(new_message)
|
||||
else: # Last message
|
||||
new_message = {"role": "user", "content": m["content"]}
|
||||
new_message = {"role": "user", "content": system_text}
|
||||
new_messages.append(new_message)
|
||||
else: # Not a system message
|
||||
new_messages.append(m)
|
||||
|
|
|
|||
|
|
@ -55,6 +55,61 @@ def test_supports_system_message():
|
|||
assert isinstance(response, litellm.ModelResponse)
|
||||
|
||||
|
||||
def test_supports_system_message_list_content():
|
||||
"""
|
||||
Test map_system_message_pt when content is a list of content blocks
|
||||
(e.g. from Anthropic pass-through endpoint).
|
||||
|
||||
Fixes: https://github.com/BerriAI/litellm/issues/23757
|
||||
"""
|
||||
# System message with list content (Anthropic format)
|
||||
messages = [
|
||||
{"role": "system", "content": [{"type": "text", "text": "You are helpful."}]},
|
||||
{"role": "user", "content": [{"type": "text", "text": "Hello!"}]},
|
||||
]
|
||||
|
||||
new_messages = map_system_message_pt(messages=messages)
|
||||
|
||||
assert len(new_messages) == 1
|
||||
assert new_messages[0]["role"] == "user"
|
||||
assert isinstance(new_messages[0]["content"], str)
|
||||
assert "You are helpful." in new_messages[0]["content"]
|
||||
assert "Hello!" in new_messages[0]["content"]
|
||||
|
||||
|
||||
def test_supports_system_message_mixed_content():
|
||||
"""
|
||||
Test map_system_message_pt with mixed str and list content types.
|
||||
"""
|
||||
messages = [
|
||||
{"role": "system", "content": "System prompt"},
|
||||
{"role": "user", "content": [{"type": "text", "text": "User message"}]},
|
||||
]
|
||||
|
||||
new_messages = map_system_message_pt(messages=messages)
|
||||
|
||||
assert len(new_messages) == 1
|
||||
assert new_messages[0]["role"] == "user"
|
||||
assert isinstance(new_messages[0]["content"], str)
|
||||
assert "System prompt" in new_messages[0]["content"]
|
||||
assert "User message" in new_messages[0]["content"]
|
||||
|
||||
|
||||
def test_supports_system_message_list_content_last_message():
|
||||
"""
|
||||
Test map_system_message_pt when system message with list content is the last message.
|
||||
"""
|
||||
messages = [
|
||||
{"role": "system", "content": [{"type": "text", "text": "Only system"}]},
|
||||
]
|
||||
|
||||
new_messages = map_system_message_pt(messages=messages)
|
||||
|
||||
assert len(new_messages) == 1
|
||||
assert new_messages[0]["role"] == "user"
|
||||
assert new_messages[0]["content"] == "Only system"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"stop_sequence, expected_count", [("\n", 0), (["\n"], 0), (["finish_reason"], 1)]
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue