Skip to content

Commit b3f3731

Browse files
SongChiYoungekzhu
andauthored
Fix: deserialize model_context in AssistantAgent and SocietyOfMindAgent and CodeExecutorAgent (#6337)
This PR fixes a bug where `model_context` was either ignored or explicitly set to `None` during agent deserialization (`_from_config`) in: - `AssistantAgent`: `model_context` was serialized but not restored. - `SocietyOfMindAgent`: `model_context` was neither serialized nor restored. - `CodeExecutorAgent`: `model_context` was serialized but not restored. As a result, restoring an agent from its config silently dropped runtime context settings, potentially affecting agent behavior. This patch: - Adds proper serialization/deserialization of `model_context` using `.dump_component()` and `load_component(...)`. - Ensures round-trip consistency when using declarative agent configs. ## Related issue number Closes #6336 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 parent 8a97292 commit b3f3731

4 files changed

Lines changed: 137 additions & 2 deletions

File tree

python/packages/autogen-agentchat/src/autogen_agentchat/agents/_assistant_agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1337,7 +1337,7 @@ def _from_config(cls, config: AssistantAgentConfig) -> Self:
13371337
model_client=ChatCompletionClient.load_component(config.model_client),
13381338
tools=[BaseTool.load_component(tool) for tool in config.tools] if config.tools else None,
13391339
handoffs=config.handoffs,
1340-
model_context=None,
1340+
model_context=ChatCompletionContext.load_component(config.model_context) if config.model_context else None,
13411341
memory=[Memory.load_component(memory) for memory in config.memory] if config.memory else None,
13421342
description=config.description,
13431343
system_message=config.system_message,

python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,13 @@ def produced_message_types(self) -> Sequence[type[BaseChatMessage]]:
369369
"""The types of messages that the code executor agent produces."""
370370
return (TextMessage,)
371371

372+
@property
373+
def model_context(self) -> ChatCompletionContext:
374+
"""
375+
The model context in use by the agent.
376+
"""
377+
return self._model_context
378+
372379
async def on_messages(self, messages: Sequence[BaseChatMessage], cancellation_token: CancellationToken) -> Response:
373380
async for message in self.on_messages_stream(messages, cancellation_token):
374381
if isinstance(message, Response):
@@ -566,7 +573,7 @@ def _from_config(cls, config: CodeExecutorAgentConfig) -> Self:
566573
sources=config.sources,
567574
system_message=config.system_message,
568575
model_client_stream=config.model_client_stream,
569-
model_context=None,
576+
model_context=ChatCompletionContext.load_component(config.model_context) if config.model_context else None,
570577
)
571578

572579
@staticmethod

python/packages/autogen-agentchat/src/autogen_agentchat/agents/_society_of_mind_agent.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ def _to_config(self) -> SocietyOfMindAgentConfig:
286286
description=self.description,
287287
instruction=self._instruction,
288288
response_prompt=self._response_prompt,
289+
model_context=self._model_context.dump_component(),
289290
)
290291

291292
@classmethod
@@ -299,4 +300,5 @@ def _from_config(cls, config: SocietyOfMindAgentConfig) -> Self:
299300
description=config.description or cls.DEFAULT_DESCRIPTION,
300301
instruction=config.instruction or cls.DEFAULT_INSTRUCTION,
301302
response_prompt=config.response_prompt or cls.DEFAULT_RESPONSE_PROMPT,
303+
model_context=ChatCompletionContext.load_component(config.model_context) if config.model_context else None,
302304
)
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import pytest
2+
from autogen_agentchat.agents import (
3+
AssistantAgent,
4+
CodeExecutorAgent,
5+
SocietyOfMindAgent,
6+
)
7+
from autogen_agentchat.teams import RoundRobinGroupChat
8+
from autogen_core.model_context import (
9+
BufferedChatCompletionContext,
10+
ChatCompletionContext,
11+
HeadAndTailChatCompletionContext,
12+
TokenLimitedChatCompletionContext,
13+
UnboundedChatCompletionContext,
14+
)
15+
from autogen_ext.code_executors.local import LocalCommandLineCodeExecutor
16+
from autogen_ext.models.replay import ReplayChatCompletionClient
17+
18+
19+
@pytest.mark.parametrize(
20+
"model_context_class",
21+
[
22+
UnboundedChatCompletionContext(),
23+
BufferedChatCompletionContext(buffer_size=5),
24+
TokenLimitedChatCompletionContext(model_client=ReplayChatCompletionClient([]), token_limit=5),
25+
HeadAndTailChatCompletionContext(head_size=3, tail_size=2),
26+
],
27+
)
28+
def test_serialize_and_deserialize_model_context_on_assistant_agent(model_context_class: ChatCompletionContext) -> None:
29+
"""Test the serialization and deserialization of the message context on the AssistantAgent."""
30+
agent = AssistantAgent(
31+
name="assistant",
32+
model_client=ReplayChatCompletionClient([]),
33+
description="An assistant agent.",
34+
model_context=model_context_class,
35+
)
36+
37+
# Serialize the agent
38+
serialized_agent = agent.dump_component()
39+
# Deserialize the agent
40+
deserialized_agent = AssistantAgent.load_component(serialized_agent)
41+
42+
# Check that the deserialized agent has the same model context as the original agent
43+
original_model_context = agent.model_context
44+
deserialized_model_context = deserialized_agent.model_context
45+
46+
assert isinstance(original_model_context, type(deserialized_model_context))
47+
assert isinstance(deserialized_model_context, type(original_model_context))
48+
assert original_model_context.dump_component() == deserialized_model_context.dump_component()
49+
50+
51+
@pytest.mark.parametrize(
52+
"model_context_class",
53+
[
54+
UnboundedChatCompletionContext(),
55+
BufferedChatCompletionContext(buffer_size=5),
56+
TokenLimitedChatCompletionContext(model_client=ReplayChatCompletionClient([]), token_limit=5),
57+
HeadAndTailChatCompletionContext(head_size=3, tail_size=2),
58+
],
59+
)
60+
def test_serialize_and_deserialize_model_context_on_society_of_mind_agent(
61+
model_context_class: ChatCompletionContext,
62+
) -> None:
63+
"""Test the serialization and deserialization of the message context on the AssistantAgent."""
64+
agent1 = AssistantAgent(
65+
name="assistant1", model_client=ReplayChatCompletionClient([]), description="An assistant agent."
66+
)
67+
agent2 = AssistantAgent(
68+
name="assistant2", model_client=ReplayChatCompletionClient([]), description="An assistant agent."
69+
)
70+
team = RoundRobinGroupChat(
71+
participants=[agent1, agent2],
72+
)
73+
agent = SocietyOfMindAgent(
74+
name="assistant",
75+
model_client=ReplayChatCompletionClient([]),
76+
description="An assistant agent.",
77+
team=team,
78+
model_context=model_context_class,
79+
)
80+
81+
# Serialize the agent
82+
serialized_agent = agent.dump_component()
83+
# Deserialize the agent
84+
deserialized_agent = SocietyOfMindAgent.load_component(serialized_agent)
85+
86+
# Check that the deserialized agent has the same model context as the original agent
87+
original_model_context = agent.model_context
88+
deserialized_model_context = deserialized_agent.model_context
89+
90+
assert isinstance(original_model_context, type(deserialized_model_context))
91+
assert isinstance(deserialized_model_context, type(original_model_context))
92+
assert original_model_context.dump_component() == deserialized_model_context.dump_component()
93+
94+
95+
@pytest.mark.parametrize(
96+
"model_context_class",
97+
[
98+
UnboundedChatCompletionContext(),
99+
BufferedChatCompletionContext(buffer_size=5),
100+
TokenLimitedChatCompletionContext(model_client=ReplayChatCompletionClient([]), token_limit=5),
101+
HeadAndTailChatCompletionContext(head_size=3, tail_size=2),
102+
],
103+
)
104+
def test_serialize_and_deserialize_model_context_on_code_executor_agent(
105+
model_context_class: ChatCompletionContext,
106+
) -> None:
107+
"""Test the serialization and deserialization of the message context on the AssistantAgent."""
108+
agent = CodeExecutorAgent(
109+
name="assistant",
110+
code_executor=LocalCommandLineCodeExecutor(),
111+
description="An assistant agent.",
112+
model_context=model_context_class,
113+
)
114+
115+
# Serialize the agent
116+
serialized_agent = agent.dump_component()
117+
# Deserialize the agent
118+
deserialized_agent = CodeExecutorAgent.load_component(serialized_agent)
119+
120+
# Check that the deserialized agent has the same model context as the original agent
121+
original_model_context = agent.model_context
122+
deserialized_model_context = deserialized_agent.model_context
123+
124+
assert isinstance(original_model_context, type(deserialized_model_context))
125+
assert isinstance(deserialized_model_context, type(original_model_context))
126+
assert original_model_context.dump_component() == deserialized_model_context.dump_component()

0 commit comments

Comments
 (0)