fix: preserve non-leading developer message order

This commit is contained in:
Genmin 2026-04-30 08:31:50 -07:00
parent 079d6e40fd
commit e656c4529c
2 changed files with 71 additions and 21 deletions

View file

@ -218,28 +218,44 @@ def map_developer_role_to_system_role(
if not any(m["role"] == "developer" for m in messages):
return messages
system_message: Optional[Dict[str, Any]] = None
system_contents: List[Any] = []
non_system_messages: List[AllMessageValues] = []
for m in messages:
if m["role"] in {"developer", "system"}:
if system_message is None:
system_message = dict(m)
system_message["role"] = "system"
system_contents.append(m["content"])
else:
non_system_messages.append(m)
new_messages: List[AllMessageValues] = []
leading_system_message: Optional[Dict[str, Any]] = None
leading_system_contents: List[Any] = []
idx = 0
while idx < len(messages) and messages[idx]["role"] in {"developer", "system"}:
m = messages[idx]
if leading_system_message is None:
leading_system_message = dict(m)
leading_system_message["role"] = "system"
leading_system_contents.append(m["content"])
if m["role"] == "developer":
verbose_logger.debug(
"Translating developer role to system role for non-OpenAI providers."
) # ensure user knows what's happening with their input.
_log_developer_role_translation()
idx += 1
if system_message is None:
return non_system_messages
if leading_system_message is not None:
leading_system_message["content"] = _merge_system_message_contents(
leading_system_contents
)
new_messages.append(cast(AllMessageValues, leading_system_message))
system_message["content"] = _merge_system_message_contents(system_contents)
return [cast(AllMessageValues, system_message), *non_system_messages]
for m in messages[idx:]:
if m["role"] in {"developer", "system"}:
if m["role"] == "developer":
_log_developer_role_translation()
new_messages.append(cast(AllMessageValues, {**m, "role": "system"}))
else:
new_messages.append(m)
continue
new_messages.append(m)
return new_messages
def _log_developer_role_translation() -> None:
verbose_logger.debug(
"Translating developer role to system role for non-OpenAI providers."
) # ensure user knows what's happening with their input.
def _merge_system_message_contents(contents: List[Any]) -> Union[str, List[Any]]:

View file

@ -13,12 +13,12 @@ def test_map_developer_role_leaves_messages_without_developer_role_unchanged():
assert map_developer_role_to_system_role(messages=messages) is messages
def test_map_developer_role_merges_system_equivalent_messages():
def test_map_developer_role_merges_leading_system_equivalent_messages():
messages = [
{"role": "system", "content": "Follow the product policy."},
{"role": "developer", "content": "Prefer concise answers."},
{"role": "user", "content": "Hello!"},
{"role": "system", "content": "Use markdown only when helpful."},
{"role": "user", "content": "Hello!"},
]
result = map_developer_role_to_system_role(messages=messages)
@ -36,9 +36,11 @@ def test_map_developer_role_merges_system_equivalent_messages():
]
def test_map_developer_role_preserves_structured_system_content():
def test_map_developer_role_preserves_structured_leading_system_content():
messages = [
{"role": "developer", "content": ""},
{"role": "system", "content": [{"type": "text", "text": "System rules."}]},
{"role": "developer", "content": None},
{"role": "developer", "content": "Developer rules."},
{"role": "user", "content": "Hello!"},
]
@ -58,6 +60,38 @@ def test_map_developer_role_preserves_structured_system_content():
]
def test_map_developer_role_converts_later_developer_messages_in_place():
messages = [
{"role": "system", "content": "Follow the product policy."},
{"role": "user", "content": "Hello!"},
{"role": "developer", "content": "Prefer concise answers."},
{"role": "assistant", "content": "Hi."},
]
result = map_developer_role_to_system_role(messages=messages)
assert result == [
{"role": "system", "content": "Follow the product policy."},
{"role": "user", "content": "Hello!"},
{"role": "system", "content": "Prefer concise answers."},
{"role": "assistant", "content": "Hi."},
]
def test_map_developer_role_converts_later_developer_without_leading_system():
messages = [
{"role": "user", "content": "Hello!"},
{"role": "developer", "content": "Prefer concise answers."},
]
result = map_developer_role_to_system_role(messages=messages)
assert result == [
{"role": "user", "content": "Hello!"},
{"role": "system", "content": "Prefer concise answers."},
]
def test_responses_instructions_and_developer_input_become_single_system_message():
request = LiteLLMCompletionResponsesConfig.transform_responses_api_request_to_chat_completion_request(
model="anthropic/claude-sonnet-4-5",