|
- import json
- import uuid
- from datetime import datetime, timezone
- from typing import List
-
- import pytest
- from autogen_agentchat.messages import (
- AgentEvent,
- ChatMessage,
- HandoffMessage,
- MessageFactory,
- ModelClientStreamingChunkEvent,
- MultiModalMessage,
- StopMessage,
- StructuredMessage,
- StructuredMessageFactory,
- TextMessage,
- ToolCallExecutionEvent,
- ToolCallRequestEvent,
- )
- from autogen_core import FunctionCall
- from autogen_core.models import FunctionExecutionResult
- from pydantic import BaseModel
-
-
- class TestContent(BaseModel):
- """Test content model."""
-
- field1: str
- field2: int
-
-
- def test_structured_message() -> None:
- # Create a structured message with the test content
- message = StructuredMessage[TestContent](
- source="test_agent",
- content=TestContent(field1="test", field2=42),
- )
-
- # Check that the message type is correct
- assert message.type == "StructuredMessage[TestContent]" # type: ignore[comparison-overlap]
-
- # Check that the content is of the correct type
- assert isinstance(message.content, TestContent)
-
- # Check that the content fields are set correctly
- assert message.content.field1 == "test"
- assert message.content.field2 == 42
-
- # Check that model_dump works correctly
- dumped_message = message.model_dump()
- assert dumped_message["source"] == "test_agent"
- assert dumped_message["content"]["field1"] == "test"
- assert dumped_message["content"]["field2"] == 42
- assert dumped_message["type"] == "StructuredMessage[TestContent]"
-
-
- def test_structured_message_component() -> None:
- # Create a structured message with the test content
- format_string = "this is a string {field1} and this is an int {field2}"
- s_m = StructuredMessageFactory(input_model=TestContent, format_string=format_string)
- config = s_m.dump_component()
- s_m_dyn = StructuredMessageFactory.load_component(config)
- message = s_m_dyn.StructuredMessage(
- source="test_agent", content=s_m_dyn.ContentModel(field1="test", field2=42), format_string=s_m_dyn.format_string
- )
-
- assert isinstance(message.content, s_m_dyn.ContentModel)
- assert not isinstance(message.content, TestContent)
- assert message.content.field1 == "test" # type: ignore[attr-defined]
- assert message.content.field2 == 42 # type: ignore[attr-defined]
-
- dumped_message = message.model_dump()
- assert dumped_message["source"] == "test_agent"
- assert dumped_message["content"]["field1"] == "test"
- assert dumped_message["content"]["field2"] == 42
- assert message.to_model_text() == format_string.format(field1="test", field2=42)
-
-
- def test_message_factory() -> None:
- factory = MessageFactory()
-
- # Text message data
- text_data = {
- "type": "TextMessage",
- "source": "test_agent",
- "content": "Hello, world!",
- }
-
- # Create a TextMessage instance
- text_message = factory.create(text_data)
- assert isinstance(text_message, TextMessage)
- assert text_message.source == "test_agent"
- assert text_message.content == "Hello, world!"
- assert text_message.type == "TextMessage" # type: ignore[comparison-overlap]
-
- # Handoff message data
- handoff_data = {
- "type": "HandoffMessage",
- "source": "test_agent",
- "content": "handoff to another agent",
- "target": "target_agent",
- }
-
- # Create a HandoffMessage instance
- handoff_message = factory.create(handoff_data)
- assert isinstance(handoff_message, HandoffMessage)
- assert handoff_message.source == "test_agent"
- assert handoff_message.content == "handoff to another agent"
- assert handoff_message.target == "target_agent"
- assert handoff_message.type == "HandoffMessage" # type: ignore[comparison-overlap]
-
- # Structured message data
- structured_data = {
- "type": "StructuredMessage[TestContent]",
- "source": "test_agent",
- "content": {
- "field1": "test",
- "field2": 42,
- },
- }
- # Create a StructuredMessage instance -- this will fail because the type
- # is not registered in the factory.
- with pytest.raises(ValueError):
- structured_message = factory.create(structured_data)
- # Register the StructuredMessage type in the factory
- factory.register(StructuredMessage[TestContent])
- # Create a StructuredMessage instance
- structured_message = factory.create(structured_data)
- assert isinstance(structured_message, StructuredMessage)
- assert isinstance(structured_message.content, TestContent) # type: ignore[reportUnkownMemberType]
- assert structured_message.source == "test_agent"
- assert structured_message.content.field1 == "test"
- assert structured_message.content.field2 == 42
- assert structured_message.type == "StructuredMessage[TestContent]" # type: ignore[comparison-overlap]
-
- sm_factory = StructuredMessageFactory(input_model=TestContent, format_string=None, content_model_name="TestContent")
- config = sm_factory.dump_component()
- config.config["content_model_name"] = "DynamicTestContent"
- sm_factory_dynamic = StructuredMessageFactory.load_component(config)
-
- factory.register(sm_factory_dynamic.StructuredMessage)
- msg = sm_factory_dynamic.StructuredMessage(
- content=sm_factory_dynamic.ContentModel(field1="static", field2=123), source="static_agent"
- )
- restored = factory.create(msg.dump())
- assert isinstance(restored, StructuredMessage)
- assert isinstance(restored.content, sm_factory_dynamic.ContentModel) # type: ignore[reportUnkownMemberType]
- assert restored.source == "static_agent"
- assert restored.content.field1 == "static" # type: ignore[attr-defined]
- assert restored.content.field2 == 123 # type: ignore[attr-defined]
-
-
- class TestContainer(BaseModel):
- chat_messages: List[ChatMessage]
- agent_events: List[AgentEvent]
-
-
- def test_union_types() -> None:
- # Create a few messages.
- chat_messages: List[ChatMessage] = [
- TextMessage(source="user", content="Hello!"),
- MultiModalMessage(source="user", content=["Hello!", "World!"]),
- HandoffMessage(source="user", content="handoff to another agent", target="target_agent"),
- StopMessage(source="user", content="stop"),
- ]
-
- # Create a few agent events.
- agent_events: List[AgentEvent] = [
- ModelClientStreamingChunkEvent(source="user", content="Hello!"),
- ToolCallRequestEvent(
- content=[
- FunctionCall(id="1", name="test_function", arguments=json.dumps({"arg1": "value1", "arg2": "value2"}))
- ],
- source="user",
- ),
- ToolCallExecutionEvent(
- content=[FunctionExecutionResult(call_id="1", content="result", name="test")], source="user"
- ),
- ]
-
- # Create a container with the messages.
- container = TestContainer(chat_messages=chat_messages, agent_events=agent_events)
-
- # Dump the container to JSON.
- data = container.model_dump()
-
- # Load the container from JSON.
- loaded_container = TestContainer.model_validate(data)
- assert loaded_container.chat_messages == chat_messages
- assert loaded_container.agent_events == agent_events
-
-
- def test_message_id_field() -> None:
- """Test that messages have unique ID fields automatically generated."""
- # Test BaseChatMessage subclass (TextMessage)
- message1 = TextMessage(source="test_agent", content="Hello, world!")
- message2 = TextMessage(source="test_agent", content="Hello, world!")
-
- # Check that IDs are present and unique
- assert hasattr(message1, "id")
- assert hasattr(message2, "id")
- assert message1.id != message2.id
- assert isinstance(message1.id, str)
- assert isinstance(message2.id, str)
-
- # Check that IDs are valid UUIDs
- try:
- uuid.UUID(message1.id)
- uuid.UUID(message2.id)
- except ValueError:
- pytest.fail("Generated IDs are not valid UUIDs")
-
- # Test BaseAgentEvent subclass (ModelClientStreamingChunkEvent)
- event1 = ModelClientStreamingChunkEvent(source="test_agent", content="chunk1")
- event2 = ModelClientStreamingChunkEvent(source="test_agent", content="chunk2")
-
- # Check that IDs are present and unique
- assert hasattr(event1, "id")
- assert hasattr(event2, "id")
- assert event1.id != event2.id
- assert isinstance(event1.id, str)
- assert isinstance(event2.id, str)
-
- # Check that IDs are valid UUIDs
- try:
- uuid.UUID(event1.id)
- uuid.UUID(event2.id)
- except ValueError:
- pytest.fail("Generated IDs are not valid UUIDs")
-
-
- def test_custom_message_id() -> None:
- """Test that custom IDs can be provided."""
- custom_id = "custom-message-id-123"
- message = TextMessage(id=custom_id, source="test_agent", content="Hello, world!")
-
- assert message.id == custom_id
-
- custom_event_id = "custom-event-id-456"
- event = ModelClientStreamingChunkEvent(id=custom_event_id, source="test_agent", content="chunk")
-
- assert event.id == custom_event_id
-
-
- def test_streaming_chunk_full_message_id() -> None:
- """Test the full_message_id field in ModelClientStreamingChunkEvent."""
- # Test without full_message_id
- chunk1 = ModelClientStreamingChunkEvent(source="test_agent", content="chunk1")
- assert chunk1.full_message_id is None
-
- # Test with full_message_id
- full_msg_id = "full-message-123"
- chunk2 = ModelClientStreamingChunkEvent(source="test_agent", content="chunk2", full_message_id=full_msg_id)
- assert chunk2.full_message_id == full_msg_id
-
- # Test that chunk has its own ID separate from full_message_id
- assert chunk2.id != chunk2.full_message_id
- assert isinstance(chunk2.id, str)
-
- # Verify chunk ID is a valid UUID
- try:
- uuid.UUID(chunk2.id)
- except ValueError:
- pytest.fail("Chunk ID is not a valid UUID")
-
-
- def test_message_serialization_with_id() -> None:
- """Test that messages with IDs serialize and deserialize correctly."""
- # Create a message with auto-generated ID
- original_message = TextMessage(source="test_agent", content="Hello, world!")
- original_id = original_message.id
-
- # Serialize to dict
- message_data = original_message.model_dump()
- assert "id" in message_data
- assert message_data["id"] == original_id
-
- # Deserialize from dict
- restored_message = TextMessage.model_validate(message_data)
- assert restored_message.id == original_id
- assert restored_message.source == "test_agent"
- assert restored_message.content == "Hello, world!"
-
- # Test with streaming chunk event
- original_chunk = ModelClientStreamingChunkEvent(
- source="test_agent", content="chunk", full_message_id="full-msg-123"
- )
- original_chunk_id = original_chunk.id
-
- # Serialize to dict
- chunk_data = original_chunk.model_dump()
- assert "id" in chunk_data
- assert "full_message_id" in chunk_data
- assert chunk_data["id"] == original_chunk_id
- assert chunk_data["full_message_id"] == "full-msg-123"
-
- # Deserialize from dict
- restored_chunk = ModelClientStreamingChunkEvent.model_validate(chunk_data)
- assert restored_chunk.id == original_chunk_id
- assert restored_chunk.full_message_id == "full-msg-123"
- assert restored_chunk.content == "chunk"
-
-
- def test_datetime_serialization_in_messages() -> None:
- """Test that datetime objects in messages are properly serialized to JSON-compatible format.
-
- This test validates the fix for issue #6793 where datetime objects in message
- created_at fields caused JSON serialization errors when saving team state.
- """
- # Create a specific datetime for testing
- test_datetime = datetime(2023, 12, 25, 10, 30, 45, 123456, timezone.utc)
-
- # Test BaseChatMessage subclass with datetime
- chat_message = TextMessage(source="test_agent", content="Hello, world!", created_at=test_datetime)
-
- # Test that dump() returns JSON-serializable data
- chat_message_data = chat_message.dump()
-
- # Verify that the datetime is converted to a string in ISO format
- assert isinstance(chat_message_data["created_at"], str)
- # Pydantic JSON mode converts UTC timezone to 'Z' format instead of '+00:00'
- expected_iso = test_datetime.isoformat().replace("+00:00", "Z")
- assert chat_message_data["created_at"] == expected_iso
-
- # Verify that the dumped data is JSON serializable
- json_string = json.dumps(chat_message_data)
- assert isinstance(json_string, str)
-
- # Test round-trip serialization (dump -> load)
- restored_chat_message = TextMessage.load(chat_message_data)
- assert restored_chat_message.source == "test_agent"
- assert restored_chat_message.content == "Hello, world!"
- assert restored_chat_message.created_at == test_datetime
-
- # Test BaseAgentEvent subclass with datetime
- agent_event = ModelClientStreamingChunkEvent(source="test_agent", content="chunk", created_at=test_datetime)
-
- # Test that dump() returns JSON-serializable data
- agent_event_data = agent_event.dump()
-
- # Verify that the datetime is converted to a string in ISO format
- assert isinstance(agent_event_data["created_at"], str)
- assert agent_event_data["created_at"] == expected_iso
-
- # Verify that the dumped data is JSON serializable
- json_string = json.dumps(agent_event_data)
- assert isinstance(json_string, str)
-
- # Test round-trip serialization (dump -> load)
- restored_agent_event = ModelClientStreamingChunkEvent.load(agent_event_data)
- assert restored_agent_event.source == "test_agent"
- assert restored_agent_event.content == "chunk"
- assert restored_agent_event.created_at == test_datetime
-
- # Test with auto-generated datetime (default created_at)
- auto_message = TextMessage(source="test_agent", content="Auto datetime test")
- auto_message_data = auto_message.dump()
-
- # Verify datetime is serialized as string
- assert isinstance(auto_message_data["created_at"], str)
-
- # Verify JSON serialization works without errors
- json.dumps(auto_message_data)
-
- # Test round-trip with auto-generated datetime
- restored_auto_message = TextMessage.load(auto_message_data)
- assert restored_auto_message.created_at == auto_message.created_at
|