Skip to content

Commit ec5da1e

Browse files
Copilotekzhu
andauthored
Fix structured logging serialization data loss with SerializeAsAny annotations (#6889)
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: ekzhu <[email protected]> Co-authored-by: Eric Zhu <[email protected]>
1 parent 9cb067e commit ec5da1e

File tree

4 files changed

+95
-10
lines changed

4 files changed

+95
-10
lines changed

python/packages/autogen-agentchat/src/autogen_agentchat/base/_chat_agent.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import Any, AsyncGenerator, Mapping, Sequence
44

55
from autogen_core import CancellationToken, ComponentBase
6-
from pydantic import BaseModel
6+
from pydantic import BaseModel, SerializeAsAny
77

88
from ..messages import BaseAgentEvent, BaseChatMessage
99
from ._task import TaskRunner
@@ -13,10 +13,10 @@
1313
class Response:
1414
"""A response from calling :meth:`ChatAgent.on_messages`."""
1515

16-
chat_message: BaseChatMessage
16+
chat_message: SerializeAsAny[BaseChatMessage]
1717
"""A chat message produced by the agent as the response."""
1818

19-
inner_messages: Sequence[BaseAgentEvent | BaseChatMessage] | None = None
19+
inner_messages: Sequence[SerializeAsAny[BaseAgentEvent | BaseChatMessage]] | None = None
2020
"""Inner messages produced by the agent, they can be :class:`BaseAgentEvent`
2121
or :class:`BaseChatMessage`."""
2222

python/packages/autogen-agentchat/src/autogen_agentchat/base/_task.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
from typing import AsyncGenerator, Protocol, Sequence
22

33
from autogen_core import CancellationToken
4-
from pydantic import BaseModel
4+
from pydantic import BaseModel, SerializeAsAny
55

66
from ..messages import BaseAgentEvent, BaseChatMessage
77

88

99
class TaskResult(BaseModel):
1010
"""Result of running a task."""
1111

12-
messages: Sequence[BaseAgentEvent | BaseChatMessage]
12+
messages: Sequence[SerializeAsAny[BaseAgentEvent | BaseChatMessage]]
1313
"""Messages produced by the task."""
1414

1515
stop_reason: str | None = None

python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_events.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import traceback
22
from typing import List
33

4-
from pydantic import BaseModel
4+
from pydantic import BaseModel, SerializeAsAny
55

66
from ...base import Response, TaskResult
77
from ...messages import BaseAgentEvent, BaseChatMessage, StopMessage
@@ -38,7 +38,7 @@ def __str__(self) -> str:
3838
class GroupChatStart(BaseModel):
3939
"""A request to start a group chat."""
4040

41-
messages: List[BaseChatMessage] | None = None
41+
messages: List[SerializeAsAny[BaseChatMessage]] | None = None
4242
"""An optional list of messages to start the group chat."""
4343

4444
output_task_messages: bool = True
@@ -48,7 +48,7 @@ class GroupChatStart(BaseModel):
4848
class GroupChatAgentResponse(BaseModel):
4949
"""A response published to a group chat."""
5050

51-
response: Response
51+
response: SerializeAsAny[Response]
5252
"""The response from an agent."""
5353

5454
name: str
@@ -58,7 +58,7 @@ class GroupChatAgentResponse(BaseModel):
5858
class GroupChatTeamResponse(BaseModel):
5959
"""A response published to a group chat from a team."""
6060

61-
result: TaskResult
61+
result: SerializeAsAny[TaskResult]
6262
"""The result from a team."""
6363

6464
name: str
@@ -74,7 +74,7 @@ class GroupChatRequestPublish(BaseModel):
7474
class GroupChatMessage(BaseModel):
7575
"""A message from a group chat."""
7676

77-
message: BaseAgentEvent | BaseChatMessage
77+
message: SerializeAsAny[BaseAgentEvent | BaseChatMessage]
7878
"""The message that was published."""
7979

8080

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import json
2+
3+
from autogen_agentchat.base import Response, TaskResult
4+
from autogen_agentchat.messages import TextMessage
5+
from autogen_agentchat.teams._group_chat._events import (
6+
GroupChatAgentResponse,
7+
GroupChatMessage,
8+
GroupChatStart,
9+
GroupChatTeamResponse,
10+
)
11+
12+
13+
def test_group_chat_message_preserves_subclass_data() -> None:
14+
"""Test that GroupChatMessage preserves TextMessage subclass fields."""
15+
# Create a TextMessage with subclass-specific fields
16+
text_msg = TextMessage(
17+
content="Hello, world!",
18+
source="TestAgent",
19+
)
20+
21+
# Wrap in GroupChatMessage
22+
group_msg = GroupChatMessage(message=text_msg)
23+
24+
# Serialize and verify subclass fields are preserved
25+
json_data = group_msg.model_dump_json()
26+
parsed = json.loads(json_data)
27+
28+
# The critical test: subclass fields should be preserved
29+
assert "content" in parsed["message"], "TextMessage content field should be preserved"
30+
assert "type" in parsed["message"], "TextMessage type field should be preserved"
31+
assert parsed["message"]["content"] == "Hello, world!"
32+
assert parsed["message"]["type"] == "TextMessage"
33+
34+
35+
def test_group_chat_start_preserves_message_list_data() -> None:
36+
"""Test that GroupChatStart preserves subclass data in message lists."""
37+
text_msg1 = TextMessage(content="First message", source="Agent1")
38+
text_msg2 = TextMessage(content="Second message", source="Agent2")
39+
40+
group_start = GroupChatStart(messages=[text_msg1, text_msg2])
41+
42+
json_data = group_start.model_dump_json()
43+
parsed = json.loads(json_data)
44+
45+
# Check both messages preserve subclass data
46+
assert "content" in parsed["messages"][0]
47+
assert "content" in parsed["messages"][1]
48+
assert parsed["messages"][0]["content"] == "First message"
49+
assert parsed["messages"][1]["content"] == "Second message"
50+
51+
52+
def test_group_chat_agent_response_preserves_dataclass_fields() -> None:
53+
"""Test that GroupChatAgentResponse preserves data in Response dataclass fields."""
54+
text_msg = TextMessage(content="Response message", source="ResponseAgent")
55+
inner_text_msg = TextMessage(content="Inner message", source="InnerAgent")
56+
response = Response(chat_message=text_msg, inner_messages=[inner_text_msg])
57+
58+
group_response = GroupChatAgentResponse(response=response, name="TestAgent")
59+
60+
json_data = group_response.model_dump_json()
61+
parsed = json.loads(json_data)
62+
63+
# Verify dataclass field preserves subclass data
64+
assert "content" in parsed["response"]["chat_message"]
65+
assert "type" in parsed["response"]["chat_message"]
66+
assert parsed["response"]["chat_message"]["content"] == "Response message"
67+
inner_msgs = parsed["response"]["inner_messages"]
68+
assert len(inner_msgs) == 1
69+
assert "content" in inner_msgs[0]
70+
assert inner_msgs[0]["content"] == "Inner message"
71+
72+
73+
def test_group_chat_team_response_preserves_nested_data() -> None:
74+
"""Test that GroupChatTeamResponse preserves deeply nested subclass data."""
75+
text_msg = TextMessage(content="Nested message", source="NestedAgent")
76+
task_result = TaskResult(messages=[text_msg])
77+
78+
team_response = GroupChatTeamResponse(result=task_result, name="TestTeam")
79+
80+
json_data = team_response.model_dump_json()
81+
parsed = json.loads(json_data)
82+
83+
# Verify deeply nested subclass data is preserved
84+
assert "content" in parsed["result"]["messages"][0]
85+
assert parsed["result"]["messages"][0]["content"] == "Nested message"

0 commit comments

Comments
 (0)