fix: merge responses developer messages into system prompt

This commit is contained in:
Sami Rusani 2026-04-30 15:26:35 +02:00
parent d3891e6eae
commit 7356b87757
2 changed files with 167 additions and 1 deletions

View file

@ -282,7 +282,81 @@ class LiteLLMCompletionResponsesConfig:
)
)
return messages
return LiteLLMCompletionResponsesConfig._merge_responses_system_messages(
messages=messages
)
@staticmethod
def _get_text_from_message_content(content: Any) -> str:
if content is None:
return ""
if isinstance(content, str):
return content
if isinstance(content, list):
text_parts: List[str] = []
for content_part in content:
if isinstance(content_part, str):
text_parts.append(content_part)
elif isinstance(content_part, dict):
content_type = content_part.get("type")
text = content_part.get("text")
if content_type in {"text", "input_text"} and isinstance(text, str):
text_parts.append(text)
return "\n\n".join(text_parts)
return str(content)
@staticmethod
def _merge_responses_system_messages(
messages: List[
Union[
AllMessageValues,
GenericChatCompletionMessage,
ChatCompletionMessageToolCall,
ChatCompletionResponseMessage,
Message,
]
],
) -> List[
Union[
AllMessageValues,
GenericChatCompletionMessage,
ChatCompletionMessageToolCall,
ChatCompletionResponseMessage,
Message,
]
]:
system_parts: List[str] = []
non_system_messages: List[
Union[
AllMessageValues,
GenericChatCompletionMessage,
ChatCompletionMessageToolCall,
ChatCompletionResponseMessage,
Message,
]
] = []
for message in messages:
role = message.get("role") if isinstance(message, dict) else None
if role in {"system", "developer"}:
content = message.get("content") if isinstance(message, dict) else None
text_content = (
LiteLLMCompletionResponsesConfig._get_text_from_message_content(
content=content
)
)
if text_content:
system_parts.append(text_content)
continue
non_system_messages.append(message)
if not system_parts:
return messages
system_message = ChatCompletionSystemMessage(
role="system", content="\n\n".join(system_parts)
)
return [system_message, *non_system_messages]
@staticmethod
async def async_responses_api_session_handler(

View file

@ -808,6 +808,98 @@ class TestFunctionCallTransformation:
assert result["extra_headers"] == {"X-Test-Header": "test-value"}
def test_request_transformation_merges_instructions_and_developer_messages(self):
"""Responses instructions and developer messages should become one system message."""
test_input = [
{
"type": "message",
"role": "developer",
"content": "Use concise responses.",
},
{
"type": "message",
"role": "developer",
"content": [{"type": "input_text", "text": "Never expose secrets."}],
},
{
"type": "message",
"role": "user",
"content": "Hello",
},
]
responses_api_request = {"instructions": "You are helpful."}
result = LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request(
model="custom_openai/qwen",
input=test_input,
responses_api_request=responses_api_request,
)
messages = result["messages"]
assert messages == [
{
"role": "system",
"content": "You are helpful.\n\nUse concise responses.\n\nNever expose secrets.",
},
{"role": "user", "content": "Hello"},
]
def test_request_transformation_preserves_messages_without_system_content(self):
"""Requests without instructions/developer content should not be rewritten."""
test_input = [
{
"type": "message",
"role": "user",
"content": "Hello",
},
{
"type": "message",
"role": "assistant",
"content": "Hi there",
},
]
result = LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request(
model="custom_openai/qwen",
input=test_input,
responses_api_request={},
)
assert result["messages"] == [
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "Hi there"},
]
def test_merge_responses_system_messages_extracts_supported_text_content(self):
"""System/developer content blocks should merge only supported text parts."""
messages = [
{"role": "system", "content": None},
{
"role": "developer",
"content": [
"Plain text",
{"type": "text", "text": "Text block"},
{"type": "input_text", "text": "Input text block"},
{"type": "image_url", "text": "Ignored image text"},
{"type": "text", "text": 123},
],
},
{"role": "developer", "content": 123},
{"role": "user", "content": "Hello"},
]
result = LiteLLMCompletionResponsesConfig._merge_responses_system_messages(
messages=messages
)
assert result == [
{
"role": "system",
"content": "Plain text\n\nText block\n\nInput text block\n\n123",
},
{"role": "user", "content": "Hello"},
]
def test_function_call_without_call_id_fallback_to_id(self):
"""Test that function_call items can use 'id' field when 'call_id' is missing"""
function_call_item = {