Skip to content

Commit 7e8e9e3

Browse files
authored
Python: Fix duplicate system message from instructions (microsoft#5049) (microsoft#5051)
Add deduplication to `prepend_instructions_to_messages()` to skip instructions that are already present as leading messages with the same role and text. This prevents duplicate system messages when instructions are injected by multiple layers (e.g. Agent + chat client). Fixes microsoft#5049
1 parent 7607acd commit 7e8e9e3

3 files changed

Lines changed: 123 additions & 1 deletion

File tree

python/packages/core/agent_framework/_types.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1796,7 +1796,19 @@ def prepend_instructions_to_messages(
17961796
if isinstance(instructions, str):
17971797
instructions = [instructions]
17981798

1799-
instruction_messages = [Message(role, [instr]) for instr in instructions]
1799+
# Skip instructions that are already present as leading messages with the
1800+
# same role and text. This prevents duplicate system messages when
1801+
# instructions are injected by multiple layers (e.g. Agent + chat client).
1802+
deduplicated: list[str] = []
1803+
for idx, instr in enumerate(instructions):
1804+
if idx < len(messages) and messages[idx].role == role and messages[idx].text == instr:
1805+
continue
1806+
deduplicated.append(instr)
1807+
1808+
if not deduplicated:
1809+
return messages
1810+
1811+
instruction_messages = [Message(role, [instr]) for instr in deduplicated]
18001812
return [*instruction_messages, *messages]
18011813

18021814

python/packages/core/tests/core/test_types.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4068,3 +4068,87 @@ def test_oauth_consent_request_serialization_roundtrip():
40684068

40694069

40704070
# endregion
4071+
4072+
4073+
# region prepend_instructions_to_messages tests
4074+
4075+
4076+
def test_prepend_instructions_basic():
4077+
"""Test that instructions are prepended as system message."""
4078+
from agent_framework._types import prepend_instructions_to_messages
4079+
4080+
messages = [Message("user", ["Hello"])]
4081+
result = prepend_instructions_to_messages(messages, "You are helpful.")
4082+
assert len(result) == 2
4083+
assert result[0].role == "system"
4084+
assert result[0].text == "You are helpful."
4085+
assert result[1].role == "user"
4086+
4087+
4088+
def test_prepend_instructions_none():
4089+
"""Test that None instructions returns messages unchanged."""
4090+
from agent_framework._types import prepend_instructions_to_messages
4091+
4092+
messages = [Message("user", ["Hello"])]
4093+
result = prepend_instructions_to_messages(messages, None)
4094+
assert result is messages
4095+
4096+
4097+
def test_prepend_instructions_skips_duplicate():
4098+
"""Test that duplicate system instructions are not prepended again."""
4099+
from agent_framework._types import prepend_instructions_to_messages
4100+
4101+
messages = [
4102+
Message("system", ["You are helpful."]),
4103+
Message("user", ["Hello"]),
4104+
]
4105+
result = prepend_instructions_to_messages(messages, "You are helpful.")
4106+
assert len(result) == 2
4107+
assert result[0].role == "system"
4108+
assert result[0].text == "You are helpful."
4109+
assert result[1].role == "user"
4110+
4111+
4112+
def test_prepend_instructions_skips_duplicate_list():
4113+
"""Test deduplication with a list of instructions."""
4114+
from agent_framework._types import prepend_instructions_to_messages
4115+
4116+
messages = [
4117+
Message("system", ["First instruction"]),
4118+
Message("system", ["Second instruction"]),
4119+
Message("user", ["Hello"]),
4120+
]
4121+
result = prepend_instructions_to_messages(messages, ["First instruction", "Second instruction"])
4122+
assert len(result) == 3
4123+
assert result[0].text == "First instruction"
4124+
assert result[1].text == "Second instruction"
4125+
assert result[2].text == "Hello"
4126+
4127+
4128+
def test_prepend_instructions_adds_when_different():
4129+
"""Test that different instructions are still prepended."""
4130+
from agent_framework._types import prepend_instructions_to_messages
4131+
4132+
messages = [
4133+
Message("system", ["Old instruction"]),
4134+
Message("user", ["Hello"]),
4135+
]
4136+
result = prepend_instructions_to_messages(messages, "New instruction")
4137+
assert len(result) == 3
4138+
assert result[0].role == "system"
4139+
assert result[0].text == "New instruction"
4140+
assert result[1].text == "Old instruction"
4141+
assert result[2].text == "Hello"
4142+
4143+
4144+
def test_prepend_instructions_custom_role():
4145+
"""Test prepending with a custom role."""
4146+
from agent_framework._types import prepend_instructions_to_messages
4147+
4148+
messages = [Message("user", ["Hello"])]
4149+
result = prepend_instructions_to_messages(messages, "Be concise.", role="developer")
4150+
assert len(result) == 2
4151+
assert result[0].role == "developer"
4152+
4153+
4154+
# endregion

python/packages/openai/tests/openai/test_openai_chat_completion_client.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,6 +1233,32 @@ def test_prepare_options_with_instructions(
12331233
assert prepared_options["messages"][0]["content"] == "You are a helpful assistant."
12341234

12351235

1236+
def test_prepare_options_with_instructions_no_duplicate(
1237+
openai_unit_test_env: dict[str, str],
1238+
) -> None:
1239+
"""Test that duplicate system message from instructions is not added again.
1240+
1241+
Regression test for https://github.com/microsoft/agent-framework/issues/5049
1242+
"""
1243+
client = OpenAIChatCompletionClient()
1244+
1245+
# Simulate messages that already contain the system instruction
1246+
messages = [
1247+
Message(role="system", text="You are a helpful assistant."),
1248+
Message(role="user", text="Hello"),
1249+
]
1250+
options = {"instructions": "You are a helpful assistant."}
1251+
1252+
prepared_options = client._prepare_options(messages, options)
1253+
1254+
# Should NOT duplicate the system message
1255+
assert "messages" in prepared_options
1256+
assert len(prepared_options["messages"]) == 2
1257+
assert prepared_options["messages"][0]["role"] == "system"
1258+
assert prepared_options["messages"][0]["content"] == "You are a helpful assistant."
1259+
assert prepared_options["messages"][1]["role"] == "user"
1260+
1261+
12361262
def test_prepare_message_with_author_name(openai_unit_test_env: dict[str, str]) -> None:
12371263
"""Test that author_name is included in prepared message."""
12381264
client = OpenAIChatCompletionClient()

0 commit comments

Comments
 (0)