From 7a2d53e95e0ba18549fd05fcefd4941ce24f2bf3 Mon Sep 17 00:00:00 2001 From: Victor Dibia Date: Wed, 15 Jan 2025 16:28:49 -0800 Subject: [PATCH 1/8] initial pass on making group chats declarative --- .../teams/_group_chat/_base_group_chat.py | 6 ++- .../_magentic_one/_magentic_one_group_chat.py | 47 +++++++++++++++++- .../_group_chat/_round_robin_group_chat.py | 33 ++++++++++++- .../teams/_group_chat/_selector_group_chat.py | 49 ++++++++++++++++++- .../teams/_group_chat/_swarm_group_chat.py | 33 +++++++++++-- .../tests/test_group_chat.py | 15 ++++++ 6 files changed, 173 insertions(+), 10 deletions(-) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py index d27865e0a4ab..43af3c79b56b 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py @@ -14,8 +14,10 @@ MessageContext, SingleThreadedAgentRuntime, TypeSubscription, + ComponentBase ) from autogen_core._closure_agent import ClosureContext +from pydantic import BaseModel from ... import EVENT_LOGGER_NAME from ...base import ChatAgent, TaskResult, Team, TerminationCondition @@ -28,13 +30,15 @@ event_logger = logging.getLogger(EVENT_LOGGER_NAME) -class BaseGroupChat(Team, ABC): +class BaseGroupChat(Team, ABC, ComponentBase[BaseModel]): """The base class for group chat teams. To implement a group chat team, first create a subclass of :class:`BaseGroupChatManager` and then create a subclass of :class:`BaseGroupChat` that uses the group chat manager. """ + component_type = "team" + def __init__( self, participants: List[ChatAgent], diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py index f09904fb3322..3d65a856666e 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py @@ -1,8 +1,10 @@ import logging from typing import Callable, List +from typing_extensions import Self +from pydantic import BaseModel from autogen_core.models import ChatCompletionClient - +from autogen_core import Component, ComponentModel from .... import EVENT_LOGGER_NAME, TRACE_LOGGER_NAME from ....base import ChatAgent, TerminationCondition from .._base_group_chat import BaseGroupChat @@ -13,7 +15,18 @@ event_logger = logging.getLogger(EVENT_LOGGER_NAME) -class MagenticOneGroupChat(BaseGroupChat): + +class MagenticOneGroupChatConfig(BaseModel): + """The declarative configuration for a MagenticOneGroupChat.""" + + participants: List[ComponentModel] + model_client: ComponentModel + termination_condition: ComponentModel | None + max_turns: int | None + max_stalls: int + final_answer_prompt: str + +class MagenticOneGroupChat(BaseGroupChat, Component[MagenticOneGroupChatConfig]): """A team that runs a group chat with participants managed by the MagenticOneOrchestrator. The orchestrator handles the conversation flow, ensuring that the task is completed @@ -73,6 +86,9 @@ async def main() -> None: } """ + component_config_schema = MagenticOneGroupChatConfig + component_provider_override = "autogen_agentchat.teams.MagenticOneGroupChat" + def __init__( self, participants: List[ChatAgent], @@ -117,3 +133,30 @@ def _create_group_chat_manager_factory( self._final_answer_prompt, termination_condition, ) + + + def _to_config(self) -> MagenticOneGroupChatConfig: + participants= [] # [p.dump_component() for p in self.participants], + termination_condition = self._termination_condition.dump_component() if self._termination_condition else None + return MagenticOneGroupChatConfig( + participants=participants, + model_client=self._model_client.dump_component(), + termination_condition=termination_condition, + max_turns=self._max_turns, + max_stalls=self._max_stalls, + final_answer_prompt=self._final_answer_prompt, + ) + + @classmethod + def _from_config(cls, config: MagenticOneGroupChatConfig) -> Self: + participants = [BaseGroupChat.load_component(participant) for participant in config.participants] + model_client = config.model_client.load_component() + termination_condition = config.termination_condition.load_component() if config.termination_condition else None + return cls( + participants, + model_client, + termination_condition=termination_condition, + max_turns=config.max_turns, + max_stalls=config.max_stalls, + final_answer_prompt=config.final_answer_prompt, + ) \ No newline at end of file diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py index d6901f04c988..28af7794851e 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py @@ -1,11 +1,14 @@ from typing import Any, Callable, List, Mapping +from pydantic import BaseModel + from ...base import ChatAgent, TerminationCondition from ...messages import AgentEvent, ChatMessage from ...state import RoundRobinManagerState from ._base_group_chat import BaseGroupChat from ._base_group_chat_manager import BaseGroupChatManager - +from autogen_core import ComponentModel, Component +from typing_extensions import Self class RoundRobinGroupChatManager(BaseGroupChatManager): """A group chat manager that selects the next speaker in a round-robin fashion.""" @@ -61,7 +64,14 @@ async def select_speaker(self, thread: List[AgentEvent | ChatMessage]) -> str: return current_speaker -class RoundRobinGroupChat(BaseGroupChat): +class RoundRobinGroupChatConfig(BaseModel): + """The declarative configuration RoundRobinGroupChat.""" + + participants: List[ComponentModel] + termination_condition: ComponentModel | None + max_turns: int | None + +class RoundRobinGroupChat(BaseGroupChat, Component[RoundRobinGroupChatConfig]): """A team that runs a group chat with participants taking turns in a round-robin fashion to publish a message to all. @@ -133,6 +143,9 @@ async def main() -> None: asyncio.run(main()) """ + component_config_schema = RoundRobinGroupChatConfig + component_provider_override = "autogen_agentchat.teams.RoundRobinGroupChat" + def __init__( self, participants: List[ChatAgent], @@ -166,3 +179,19 @@ def _factory() -> RoundRobinGroupChatManager: ) return _factory + + def _to_config(self) -> RoundRobinGroupChatConfig: + # participants = [participant.dump_component() for participant in self._participants] + participants = [] + termination_condition = self._termination_condition.dump_component() if self._termination_condition else None + return RoundRobinGroupChatConfig( + participants=participants, + termination_condition=termination_condition, + max_turns=self._max_turns, + ) + + @classmethod + def _from_config(cls, config: RoundRobinGroupChatConfig) -> Self: + participants = [BaseGroupChat.load_component(participant) for participant in config.participants] + termination_condition = TerminationCondition.load_component(config.termination_condition) if config.termination_condition else None + return cls(participants, termination_condition=termination_condition, max_turns=config.max_turns) \ No newline at end of file diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py index dcc399ee2972..2463b0b3a97b 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py @@ -1,8 +1,10 @@ import logging import re from typing import Any, Callable, Dict, List, Mapping, Sequence - +from pydantic import BaseModel +from typing_extensions import Self from autogen_core.models import ChatCompletionClient, SystemMessage +from autogen_core import Component, ComponentModel from ... import TRACE_LOGGER_NAME from ...base import ChatAgent, TerminationCondition @@ -183,8 +185,18 @@ def _mentioned_agents(self, message_content: str, agent_names: List[str]) -> Dic mentions[name] = count return mentions +class SelectorGroupChatConfig(BaseModel): + """The declarative configuration for SelectorGroupChat.""" + + participants: List[ComponentModel] + model_client: ComponentModel + termination_condition: ComponentModel | None + max_turns: int | None + selector_prompt: str + allow_repeated_speaker: bool + # selector_func: ComponentModel | None -class SelectorGroupChat(BaseGroupChat): +class SelectorGroupChat(BaseGroupChat, Component[SelectorGroupChatConfig]): """A group chat team that have participants takes turn to publish a message to all, using a ChatCompletion model to select the next speaker after each message. @@ -321,6 +333,9 @@ def selector_func(messages: Sequence[AgentEvent | ChatMessage]) -> str | None: asyncio.run(main()) """ + component_config_schema = SelectorGroupChatConfig + component_provider_override = "autogen_agentchat.teams.SelectorGroupChat" + def __init__( self, participants: List[ChatAgent], @@ -381,3 +396,33 @@ def _create_group_chat_manager_factory( self._allow_repeated_speaker, self._selector_func, ) + + + def _to_config(self) -> SelectorGroupChatConfig: + return SelectorGroupChatConfig( + participants= [] , # [p.dump_component() for p in self.participants], + model_client=self._model_client.dump_component(), + termination_condition=self.termination_condition.dump_component() if self.termination_condition else None, + max_turns=self.max_turns, + selector_prompt=self._selector_prompt, + allow_repeated_speaker=self._allow_repeated_speaker, + # selector_func=self._selector_func.dump_component() if self._selector_func else None, + ) + + @classmethod + + def _from_config(cls, config: SelectorGroupChatConfig) -> Self: + return cls( + participants = [BaseGroupChat.load_component(participant) for participant in config.participants], + model_client=ChatCompletionClient.load_component(config.model_client), + termination_condition=TerminationCondition.load_component(config.termination_condition) + if config.termination_condition + else None, + max_turns=config.max_turns, + selector_prompt=config.selector_prompt, + allow_repeated_speaker=config.allow_repeated_speaker, + # selector_func=ComponentLoader.load_component(config.selector_func, Callable[[Sequence[AgentEvent | ChatMessage]], str | None]) + # if config.selector_func + # else None, + ) + diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py index a31a693e086f..238faf7cd0e7 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py @@ -1,11 +1,13 @@ from typing import Any, Callable, List, Mapping +from pydantic import BaseModel + from ...base import ChatAgent, TerminationCondition from ...messages import AgentEvent, ChatMessage, HandoffMessage from ...state import SwarmManagerState from ._base_group_chat import BaseGroupChat from ._base_group_chat_manager import BaseGroupChatManager - +from autogen_core import ComponentModel, Component class SwarmGroupChatManager(BaseGroupChatManager): """A group chat manager that selects the next speaker based on handoff message only.""" @@ -91,8 +93,14 @@ async def load_state(self, state: Mapping[str, Any]) -> None: self._current_turn = swarm_state.current_turn self._current_speaker = swarm_state.current_speaker +class SwarmConfig(BaseModel): + """The declarative configuration for Swarm.""" + + participants: List[ComponentModel] + termination_condition: ComponentModel | None + max_turns: int | None -class Swarm(BaseGroupChat): +class Swarm(BaseGroupChat, Component[SwarmConfig]): """A group chat team that selects the next speaker based on handoff message only. The first participant in the list of participants is the initial speaker. @@ -180,6 +188,9 @@ async def main() -> None: asyncio.run(main()) """ + component_config_schema = SwarmConfig + component_provider_override = "autogen_agentchat.teams.Swarm" + def __init__( self, participants: List[ChatAgent], @@ -216,4 +227,20 @@ def _factory() -> SwarmGroupChatManager: max_turns, ) - return _factory + return _factory + + + def _to_config(self) -> SwarmConfig: + participants = [] # [participant.dump_component() for participant in self._participants] + termination_condition = self._termination_condition.dump_component() if self._termination_condition else None + return SwarmConfig( + participants=participants, + termination_condition=termination_condition, + max_turns=self._max_turns, + ) + + @classmethod + def _from_config(cls, config: SwarmConfig) -> "Swarm": + participants = [BaseGroupChat.load_component(participant) for participant in config.participants] + termination_condition = TerminationCondition.load_component(config.termination_condition) if config.termination_condition else None + return cls(participants, termination_condition=termination_condition, max_turns=config.max_turns) diff --git a/python/packages/autogen-agentchat/tests/test_group_chat.py b/python/packages/autogen-agentchat/tests/test_group_chat.py index 25194d6049af..0813e7ccb8b0 100644 --- a/python/packages/autogen-agentchat/tests/test_group_chat.py +++ b/python/packages/autogen-agentchat/tests/test_group_chat.py @@ -1082,3 +1082,18 @@ async def test_round_robin_group_chat_with_message_list() -> None: # Test with empty message list with pytest.raises(ValueError, match="Task list cannot be empty"): await team.run(task=[]) + + +@pytest.mark.asyncio +async def test_declarative_groupchats() -> None: + # Create a simple team with echo agents + agent1 = _EchoAgent("Agent1", "First agent") + agent2 = _EchoAgent("Agent2", "Second agent") + termination = MaxMessageTermination(4) # Stop after 4 messages + round_robin_team = RoundRobinGroupChat([agent1, agent2], termination_condition=termination) + + round_robin_team_config = round_robin_team.dump_component() + assert round_robin_team_config.provider == "autogen_agentchat.teams.RoundRobinGroupChat" + + round_robin_team_loaded = RoundRobinGroupChat.load_component(round_robin_team_config) + assert round_robin_team_loaded.component_type == "team" \ No newline at end of file From 9d753afcb30205de813996e9b44872d1ee07e5e3 Mon Sep 17 00:00:00 2001 From: Victor Dibia Date: Wed, 15 Jan 2025 21:33:25 -0800 Subject: [PATCH 2/8] update group chat tests --- .../tests/test_group_chat.py | 68 ++++++++++++++++--- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/python/packages/autogen-agentchat/tests/test_group_chat.py b/python/packages/autogen-agentchat/tests/test_group_chat.py index 0813e7ccb8b0..66eec1ad7f92 100644 --- a/python/packages/autogen-agentchat/tests/test_group_chat.py +++ b/python/packages/autogen-agentchat/tests/test_group_chat.py @@ -28,6 +28,7 @@ RoundRobinGroupChat, SelectorGroupChat, Swarm, + MagenticOneGroupChat ) from autogen_agentchat.teams._group_chat._round_robin_group_chat import RoundRobinGroupChatManager from autogen_agentchat.teams._group_chat._selector_group_chat import SelectorGroupChatManager @@ -1085,15 +1086,66 @@ async def test_round_robin_group_chat_with_message_list() -> None: @pytest.mark.asyncio -async def test_declarative_groupchats() -> None: - # Create a simple team with echo agents +async def test_declarative_groupchats_with_config() -> None: + # Create basic agents and components for testing agent1 = _EchoAgent("Agent1", "First agent") agent2 = _EchoAgent("Agent2", "Second agent") - termination = MaxMessageTermination(4) # Stop after 4 messages - round_robin_team = RoundRobinGroupChat([agent1, agent2], termination_condition=termination) + termination = MaxMessageTermination(4) + model_client = ReplayChatCompletionClient(["test"]) - round_robin_team_config = round_robin_team.dump_component() - assert round_robin_team_config.provider == "autogen_agentchat.teams.RoundRobinGroupChat" + # Test round robin - verify config is preserved + round_robin = RoundRobinGroupChat( + participants=[agent1, agent2], + termination_condition=termination, + max_turns=5 + ) + config = round_robin.dump_component() + loaded = RoundRobinGroupChat.load_component(config) + assert loaded.dump_component() == config - round_robin_team_loaded = RoundRobinGroupChat.load_component(round_robin_team_config) - assert round_robin_team_loaded.component_type == "team" \ No newline at end of file + # Test selector group chat - verify config is preserved + selector_prompt = "Custom selector prompt with {roles}, {participants}, {history}" + selector = SelectorGroupChat( + participants=[agent1, agent2], + model_client=model_client, + termination_condition=termination, + max_turns=10, + selector_prompt=selector_prompt, + allow_repeated_speaker=True + ) + config = selector.dump_component() + loaded = SelectorGroupChat.load_component(config) + assert loaded.dump_component() == config + + # Test swarm with handoff termination + handoff_termination = HandoffTermination(target="Agent2") + swarm = Swarm( + participants=[agent1, agent2], + termination_condition=handoff_termination, + max_turns=5 + ) + config = swarm.dump_component() + loaded = Swarm.load_component(config) + assert loaded.dump_component() == config + + # Test MagenticOne with custom parameters + magentic = MagenticOneGroupChat( + participants=[agent1], + model_client=model_client, + max_turns=15, + max_stalls=5, + final_answer_prompt="Custom prompt" + ) + config = magentic.dump_component() + loaded = MagenticOneGroupChat.load_component(config) + assert loaded.dump_component() == config + + # Verify component types are correctly set for each + for team in [loaded, selector, swarm, magentic]: + assert team.component_type == "team" + + # Verify provider strings are correctly set + assert round_robin.dump_component().provider == "autogen_agentchat.teams.RoundRobinGroupChat" + assert selector.dump_component().provider == "autogen_agentchat.teams.SelectorGroupChat" + assert swarm.dump_component().provider == "autogen_agentchat.teams.Swarm" + assert magentic.dump_component().provider == "autogen_agentchat.teams.MagenticOneGroupChat" \ No newline at end of file From c9182647625d5270f6a5ef83ca7f56c5fb563f66 Mon Sep 17 00:00:00 2001 From: Victor Dibia Date: Sat, 18 Jan 2025 08:09:51 -0800 Subject: [PATCH 3/8] update impl to include participant serialization for all teams --- .../_magentic_one/_magentic_one_group_chat.py | 13 +++++++------ .../teams/_group_chat/_round_robin_group_chat.py | 11 ++++++----- .../teams/_group_chat/_selector_group_chat.py | 13 +++++++------ .../teams/_group_chat/_swarm_group_chat.py | 9 +++++---- .../autogen-agentchat/tests/test_group_chat.py | 11 ++++++++--- 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py index 3d65a856666e..25262bafd435 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py @@ -5,6 +5,7 @@ from autogen_core.models import ChatCompletionClient from autogen_core import Component, ComponentModel +from ....agents import BaseChatAgent from .... import EVENT_LOGGER_NAME, TRACE_LOGGER_NAME from ....base import ChatAgent, TerminationCondition from .._base_group_chat import BaseGroupChat @@ -21,8 +22,8 @@ class MagenticOneGroupChatConfig(BaseModel): participants: List[ComponentModel] model_client: ComponentModel - termination_condition: ComponentModel | None - max_turns: int | None + termination_condition: ComponentModel | None = None + max_turns: int | None = None max_stalls: int final_answer_prompt: str @@ -136,7 +137,7 @@ def _create_group_chat_manager_factory( def _to_config(self) -> MagenticOneGroupChatConfig: - participants= [] # [p.dump_component() for p in self.participants], + participants= [participant.dump_component() for participant in self._participants] termination_condition = self._termination_condition.dump_component() if self._termination_condition else None return MagenticOneGroupChatConfig( participants=participants, @@ -149,9 +150,9 @@ def _to_config(self) -> MagenticOneGroupChatConfig: @classmethod def _from_config(cls, config: MagenticOneGroupChatConfig) -> Self: - participants = [BaseGroupChat.load_component(participant) for participant in config.participants] - model_client = config.model_client.load_component() - termination_condition = config.termination_condition.load_component() if config.termination_condition else None + participants = [BaseChatAgent.load_component(participant) for participant in config.participants] + model_client = ChatCompletionClient.load_component(config.model_client) + termination_condition = TerminationCondition.load_component(config.termination_condition) if config.termination_condition else None return cls( participants, model_client, diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py index 28af7794851e..94ee22ed2966 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py @@ -10,6 +10,8 @@ from autogen_core import ComponentModel, Component from typing_extensions import Self +from ...agents import BaseChatAgent + class RoundRobinGroupChatManager(BaseGroupChatManager): """A group chat manager that selects the next speaker in a round-robin fashion.""" @@ -68,8 +70,8 @@ class RoundRobinGroupChatConfig(BaseModel): """The declarative configuration RoundRobinGroupChat.""" participants: List[ComponentModel] - termination_condition: ComponentModel | None - max_turns: int | None + termination_condition: ComponentModel | None = None + max_turns: int | None = None class RoundRobinGroupChat(BaseGroupChat, Component[RoundRobinGroupChatConfig]): """A team that runs a group chat with participants taking turns in a round-robin fashion @@ -181,8 +183,7 @@ def _factory() -> RoundRobinGroupChatManager: return _factory def _to_config(self) -> RoundRobinGroupChatConfig: - # participants = [participant.dump_component() for participant in self._participants] - participants = [] + participants = [participant.dump_component() for participant in self._participants] termination_condition = self._termination_condition.dump_component() if self._termination_condition else None return RoundRobinGroupChatConfig( participants=participants, @@ -192,6 +193,6 @@ def _to_config(self) -> RoundRobinGroupChatConfig: @classmethod def _from_config(cls, config: RoundRobinGroupChatConfig) -> Self: - participants = [BaseGroupChat.load_component(participant) for participant in config.participants] + participants = [BaseChatAgent.load_component(participant) for participant in config.participants] termination_condition = TerminationCondition.load_component(config.termination_condition) if config.termination_condition else None return cls(participants, termination_condition=termination_condition, max_turns=config.max_turns) \ No newline at end of file diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py index 2463b0b3a97b..16657d96e336 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py @@ -7,6 +7,7 @@ from autogen_core import Component, ComponentModel from ... import TRACE_LOGGER_NAME +from ...agents import BaseChatAgent from ...base import ChatAgent, TerminationCondition from ...messages import ( AgentEvent, @@ -190,8 +191,8 @@ class SelectorGroupChatConfig(BaseModel): participants: List[ComponentModel] model_client: ComponentModel - termination_condition: ComponentModel | None - max_turns: int | None + termination_condition: ComponentModel | None = None + max_turns: int | None = None selector_prompt: str allow_repeated_speaker: bool # selector_func: ComponentModel | None @@ -400,10 +401,10 @@ def _create_group_chat_manager_factory( def _to_config(self) -> SelectorGroupChatConfig: return SelectorGroupChatConfig( - participants= [] , # [p.dump_component() for p in self.participants], + participants = [participant.dump_component() for participant in self._participants] , model_client=self._model_client.dump_component(), - termination_condition=self.termination_condition.dump_component() if self.termination_condition else None, - max_turns=self.max_turns, + termination_condition=self._termination_condition.dump_component() if self._termination_condition else None, + max_turns=self._max_turns, selector_prompt=self._selector_prompt, allow_repeated_speaker=self._allow_repeated_speaker, # selector_func=self._selector_func.dump_component() if self._selector_func else None, @@ -413,7 +414,7 @@ def _to_config(self) -> SelectorGroupChatConfig: def _from_config(cls, config: SelectorGroupChatConfig) -> Self: return cls( - participants = [BaseGroupChat.load_component(participant) for participant in config.participants], + participants = [BaseChatAgent.load_component(participant) for participant in config.participants], model_client=ChatCompletionClient.load_component(config.model_client), termination_condition=TerminationCondition.load_component(config.termination_condition) if config.termination_condition diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py index 238faf7cd0e7..b8725a81e0f0 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py @@ -2,6 +2,7 @@ from pydantic import BaseModel +from ...agents import BaseChatAgent from ...base import ChatAgent, TerminationCondition from ...messages import AgentEvent, ChatMessage, HandoffMessage from ...state import SwarmManagerState @@ -97,8 +98,8 @@ class SwarmConfig(BaseModel): """The declarative configuration for Swarm.""" participants: List[ComponentModel] - termination_condition: ComponentModel | None - max_turns: int | None + termination_condition: ComponentModel | None = None + max_turns: int | None = None class Swarm(BaseGroupChat, Component[SwarmConfig]): """A group chat team that selects the next speaker based on handoff message only. @@ -231,7 +232,7 @@ def _factory() -> SwarmGroupChatManager: def _to_config(self) -> SwarmConfig: - participants = [] # [participant.dump_component() for participant in self._participants] + participants = [participant.dump_component() for participant in self._participants] termination_condition = self._termination_condition.dump_component() if self._termination_condition else None return SwarmConfig( participants=participants, @@ -241,6 +242,6 @@ def _to_config(self) -> SwarmConfig: @classmethod def _from_config(cls, config: SwarmConfig) -> "Swarm": - participants = [BaseGroupChat.load_component(participant) for participant in config.participants] + participants = [BaseChatAgent.load_component(participant) for participant in config.participants] termination_condition = TerminationCondition.load_component(config.termination_condition) if config.termination_condition else None return cls(participants, termination_condition=termination_condition, max_turns=config.max_turns) diff --git a/python/packages/autogen-agentchat/tests/test_group_chat.py b/python/packages/autogen-agentchat/tests/test_group_chat.py index 66eec1ad7f92..483bc75e83b7 100644 --- a/python/packages/autogen-agentchat/tests/test_group_chat.py +++ b/python/packages/autogen-agentchat/tests/test_group_chat.py @@ -1088,10 +1088,15 @@ async def test_round_robin_group_chat_with_message_list() -> None: @pytest.mark.asyncio async def test_declarative_groupchats_with_config() -> None: # Create basic agents and components for testing - agent1 = _EchoAgent("Agent1", "First agent") - agent2 = _EchoAgent("Agent2", "Second agent") + agent1 = AssistantAgent( + "agent_1", model_client=OpenAIChatCompletionClient(model="gpt-4o-2024-05-13", api_key=""), + handoffs=["agent_2"], + ) + agent2 = AssistantAgent( + "agent_2", model_client=OpenAIChatCompletionClient(model="gpt-4o-2024-05-13", api_key="") + ) termination = MaxMessageTermination(4) - model_client = ReplayChatCompletionClient(["test"]) + model_client = OpenAIChatCompletionClient(model="gpt-4o-2024-05-13", api_key="") # Test round robin - verify config is preserved round_robin = RoundRobinGroupChat( From 451757f959d69aba99e7f9bc4725415dd62eb4e4 Mon Sep 17 00:00:00 2001 From: Victor Dibia Date: Sat, 18 Jan 2025 20:25:29 -0800 Subject: [PATCH 4/8] v1 making soc declarative --- .../agents/_society_of_mind_agent.py | 41 +++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_society_of_mind_agent.py b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_society_of_mind_agent.py index 713b518bd7df..a6c5d6a4afbe 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_society_of_mind_agent.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_society_of_mind_agent.py @@ -1,11 +1,12 @@ from typing import Any, AsyncGenerator, List, Mapping, Sequence -from autogen_core import CancellationToken +from autogen_core import CancellationToken,Component, ComponentModel from autogen_core.models import ChatCompletionClient, LLMMessage, SystemMessage, UserMessage from autogen_agentchat.base import Response from autogen_agentchat.state import SocietyOfMindAgentState - +from typing_extensions import Self +from pydantic import BaseModel from ..base import TaskResult, Team from ..messages import ( AgentEvent, @@ -16,7 +17,16 @@ from ._base_chat_agent import BaseChatAgent -class SocietyOfMindAgent(BaseChatAgent): +class SocietyOfMindAgentConfig(BaseModel): + """The declarative configuration for a SocietyOfMindAgent.""" + + team: ComponentModel + model_client: ComponentModel + description: str + instruction: str + response_prompt: str + +class SocietyOfMindAgent(BaseChatAgent, Component[SocietyOfMindAgentConfig]): """An agent that uses an inner team of agents to generate responses. Each time the agent's :meth:`on_messages` or :meth:`on_messages_stream` @@ -74,6 +84,9 @@ async def main() -> None: asyncio.run(main()) """ + component_config_schema = SocietyOfMindAgentConfig + component_provider_override = "autogen_agentchat.agents.SocietyOfMindAgent" + DEFAULT_INSTRUCTION = "Earlier you were asked to fulfill a request. You and your team worked diligently to address that request. Here is a transcript of that conversation:" """str: The default instruction to use when generating a response using the inner team's messages. The instruction will be prepended to the inner team's @@ -173,3 +186,25 @@ async def save_state(self) -> Mapping[str, Any]: async def load_state(self, state: Mapping[str, Any]) -> None: society_of_mind_state = SocietyOfMindAgentState.model_validate(state) await self._team.load_state(society_of_mind_state.inner_team_state) + + + def _to_config(self) -> SocietyOfMindAgentConfig: + return SocietyOfMindAgentConfig( + team=self._team.dump_component(), + model_client=self._model_client.dump_component(), + description=self.description, + instruction=self._instruction, + response_prompt=self._response_prompt, + ) + + @classmethod + def _from_config(cls, config: SocietyOfMindAgentConfig) -> Self: + team = Team.load_component(config.team) + model_client = ChatCompletionClient.load_component(config.model_client) + return cls( + team=team, + model_client=model_client, + description=config.description, + instruction=config.instruction, + response_prompt=config.response_prompt, + ) \ No newline at end of file From 2b9196ee185316b5667f365ee8a0a0028cf2de4d Mon Sep 17 00:00:00 2001 From: Victor Dibia Date: Wed, 22 Jan 2025 14:16:00 -0800 Subject: [PATCH 5/8] update memory test --- python/packages/autogen-core/tests/test_memory.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/packages/autogen-core/tests/test_memory.py b/python/packages/autogen-core/tests/test_memory.py index ebe1866ed15d..5948cc014bc4 100644 --- a/python/packages/autogen-core/tests/test_memory.py +++ b/python/packages/autogen-core/tests/test_memory.py @@ -21,10 +21,10 @@ def test_memory_protocol_attributes() -> None: assert hasattr(Memory, "close") -def test_memory_protocol_runtime_checkable() -> None: - """Test that Memory protocol is properly runtime-checkable.""" +def test_memory_abc_implementation() -> None: + """Test that Memory ABC is properly implemented.""" - class ValidMemory: + class ValidMemory(Memory): @property def name(self) -> str: return "test" From a61c3cff93fa748f7124b7bdf8374c0f914cd2da Mon Sep 17 00:00:00 2001 From: Victor Dibia Date: Wed, 22 Jan 2025 16:15:09 -0800 Subject: [PATCH 6/8] update chatagent and team base classes --- .../agents/_society_of_mind_agent.py | 17 ++--- .../src/autogen_agentchat/base/_chat_agent.py | 7 ++- .../src/autogen_agentchat/base/_team.py | 6 +- .../teams/_group_chat/_base_group_chat.py | 2 +- .../_magentic_one/_magentic_one_group_chat.py | 20 +++--- .../_group_chat/_round_robin_group_chat.py | 12 ++-- .../teams/_group_chat/_selector_group_chat.py | 11 ++-- .../teams/_group_chat/_swarm_group_chat.py | 17 ++--- .../tests/test_group_chat.py | 62 +++++++------------ .../tests/test_society_of_mind_agent.py | 10 +++ 10 files changed, 87 insertions(+), 77 deletions(-) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_society_of_mind_agent.py b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_society_of_mind_agent.py index a6c5d6a4afbe..8f1a950e07a5 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_society_of_mind_agent.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_society_of_mind_agent.py @@ -1,6 +1,6 @@ from typing import Any, AsyncGenerator, List, Mapping, Sequence -from autogen_core import CancellationToken,Component, ComponentModel +from autogen_core import CancellationToken, Component, ComponentModel from autogen_core.models import ChatCompletionClient, LLMMessage, SystemMessage, UserMessage from autogen_agentchat.base import Response @@ -20,11 +20,13 @@ class SocietyOfMindAgentConfig(BaseModel): """The declarative configuration for a SocietyOfMindAgent.""" + name: str team: ComponentModel model_client: ComponentModel - description: str - instruction: str - response_prompt: str + description: str + instruction: str + response_prompt: str + class SocietyOfMindAgent(BaseChatAgent, Component[SocietyOfMindAgentConfig]): """An agent that uses an inner team of agents to generate responses. @@ -187,9 +189,9 @@ async def load_state(self, state: Mapping[str, Any]) -> None: society_of_mind_state = SocietyOfMindAgentState.model_validate(state) await self._team.load_state(society_of_mind_state.inner_team_state) - def _to_config(self) -> SocietyOfMindAgentConfig: return SocietyOfMindAgentConfig( + name=self.name, team=self._team.dump_component(), model_client=self._model_client.dump_component(), description=self.description, @@ -199,12 +201,13 @@ def _to_config(self) -> SocietyOfMindAgentConfig: @classmethod def _from_config(cls, config: SocietyOfMindAgentConfig) -> Self: - team = Team.load_component(config.team) model_client = ChatCompletionClient.load_component(config.model_client) + team = Team.load_component(config.team) return cls( + name=config.name, team=team, model_client=model_client, description=config.description, instruction=config.instruction, response_prompt=config.response_prompt, - ) \ No newline at end of file + ) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/base/_chat_agent.py b/python/packages/autogen-agentchat/src/autogen_agentchat/base/_chat_agent.py index 36a80efe019c..34f0f37ee6e2 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/base/_chat_agent.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/base/_chat_agent.py @@ -2,7 +2,8 @@ from dataclasses import dataclass from typing import Any, AsyncGenerator, Mapping, Sequence -from autogen_core import CancellationToken +from autogen_core import CancellationToken, ComponentBase +from pydantic import BaseModel from ..messages import AgentEvent, ChatMessage from ._task import TaskRunner @@ -20,9 +21,11 @@ class Response: or :class:`ChatMessage`.""" -class ChatAgent(ABC, TaskRunner): +class ChatAgent(ABC, TaskRunner, ComponentBase[BaseModel]): """Protocol for a chat agent.""" + component_type = "agent" + @property @abstractmethod def name(self) -> str: diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/base/_team.py b/python/packages/autogen-agentchat/src/autogen_agentchat/base/_team.py index 565ad225b866..d790130ae93d 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/base/_team.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/base/_team.py @@ -1,9 +1,13 @@ from typing import Any, Mapping from abc import ABC, abstractmethod from ._task import TaskRunner +from pydantic import BaseModel +from autogen_core import ComponentBase -class Team(ABC, TaskRunner): +class Team(ABC, TaskRunner, ComponentBase[BaseModel]): + component_type = "team" + @abstractmethod async def reset(self) -> None: """Reset the team and all its participants to its initial state.""" diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py index 43af3c79b56b..446c74ca14de 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py @@ -14,7 +14,7 @@ MessageContext, SingleThreadedAgentRuntime, TypeSubscription, - ComponentBase + ComponentBase, ) from autogen_core._closure_agent import ClosureContext from pydantic import BaseModel diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py index 25262bafd435..7aa44023fb94 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py @@ -1,11 +1,10 @@ import logging from typing import Callable, List from typing_extensions import Self -from pydantic import BaseModel +from pydantic import BaseModel from autogen_core.models import ChatCompletionClient from autogen_core import Component, ComponentModel -from ....agents import BaseChatAgent from .... import EVENT_LOGGER_NAME, TRACE_LOGGER_NAME from ....base import ChatAgent, TerminationCondition from .._base_group_chat import BaseGroupChat @@ -16,17 +15,17 @@ event_logger = logging.getLogger(EVENT_LOGGER_NAME) - class MagenticOneGroupChatConfig(BaseModel): """The declarative configuration for a MagenticOneGroupChat.""" - + participants: List[ComponentModel] - model_client: ComponentModel + model_client: ComponentModel termination_condition: ComponentModel | None = None max_turns: int | None = None max_stalls: int final_answer_prompt: str + class MagenticOneGroupChat(BaseGroupChat, Component[MagenticOneGroupChatConfig]): """A team that runs a group chat with participants managed by the MagenticOneOrchestrator. @@ -135,9 +134,8 @@ def _create_group_chat_manager_factory( termination_condition, ) - def _to_config(self) -> MagenticOneGroupChatConfig: - participants= [participant.dump_component() for participant in self._participants] + participants = [participant.dump_component() for participant in self._participants] termination_condition = self._termination_condition.dump_component() if self._termination_condition else None return MagenticOneGroupChatConfig( participants=participants, @@ -150,9 +148,11 @@ def _to_config(self) -> MagenticOneGroupChatConfig: @classmethod def _from_config(cls, config: MagenticOneGroupChatConfig) -> Self: - participants = [BaseChatAgent.load_component(participant) for participant in config.participants] + participants = [ChatAgent.load_component(participant) for participant in config.participants] model_client = ChatCompletionClient.load_component(config.model_client) - termination_condition = TerminationCondition.load_component(config.termination_condition) if config.termination_condition else None + termination_condition = ( + TerminationCondition.load_component(config.termination_condition) if config.termination_condition else None + ) return cls( participants, model_client, @@ -160,4 +160,4 @@ def _from_config(cls, config: MagenticOneGroupChatConfig) -> Self: max_turns=config.max_turns, max_stalls=config.max_stalls, final_answer_prompt=config.final_answer_prompt, - ) \ No newline at end of file + ) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py index 94ee22ed2966..83b6c5909381 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py @@ -10,7 +10,6 @@ from autogen_core import ComponentModel, Component from typing_extensions import Self -from ...agents import BaseChatAgent class RoundRobinGroupChatManager(BaseGroupChatManager): """A group chat manager that selects the next speaker in a round-robin fashion.""" @@ -73,6 +72,7 @@ class RoundRobinGroupChatConfig(BaseModel): termination_condition: ComponentModel | None = None max_turns: int | None = None + class RoundRobinGroupChat(BaseGroupChat, Component[RoundRobinGroupChatConfig]): """A team that runs a group chat with participants taking turns in a round-robin fashion to publish a message to all. @@ -183,7 +183,7 @@ def _factory() -> RoundRobinGroupChatManager: return _factory def _to_config(self) -> RoundRobinGroupChatConfig: - participants = [participant.dump_component() for participant in self._participants] + participants = [participant.dump_component() for participant in self._participants] termination_condition = self._termination_condition.dump_component() if self._termination_condition else None return RoundRobinGroupChatConfig( participants=participants, @@ -193,6 +193,8 @@ def _to_config(self) -> RoundRobinGroupChatConfig: @classmethod def _from_config(cls, config: RoundRobinGroupChatConfig) -> Self: - participants = [BaseChatAgent.load_component(participant) for participant in config.participants] - termination_condition = TerminationCondition.load_component(config.termination_condition) if config.termination_condition else None - return cls(participants, termination_condition=termination_condition, max_turns=config.max_turns) \ No newline at end of file + participants = [ChatAgent.load_component(participant) for participant in config.participants] + termination_condition = ( + TerminationCondition.load_component(config.termination_condition) if config.termination_condition else None + ) + return cls(participants, termination_condition=termination_condition, max_turns=config.max_turns) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py index 16657d96e336..0a3b7fd85eb5 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py @@ -186,6 +186,7 @@ def _mentioned_agents(self, message_content: str, agent_names: List[str]) -> Dic mentions[name] = count return mentions + class SelectorGroupChatConfig(BaseModel): """The declarative configuration for SelectorGroupChat.""" @@ -197,6 +198,7 @@ class SelectorGroupChatConfig(BaseModel): allow_repeated_speaker: bool # selector_func: ComponentModel | None + class SelectorGroupChat(BaseGroupChat, Component[SelectorGroupChatConfig]): """A group chat team that have participants takes turn to publish a message to all, using a ChatCompletion model to select the next speaker after each message. @@ -398,10 +400,9 @@ def _create_group_chat_manager_factory( self._selector_func, ) - def _to_config(self) -> SelectorGroupChatConfig: return SelectorGroupChatConfig( - participants = [participant.dump_component() for participant in self._participants] , + participants=[participant.dump_component() for participant in self._participants], model_client=self._model_client.dump_component(), termination_condition=self._termination_condition.dump_component() if self._termination_condition else None, max_turns=self._max_turns, @@ -411,10 +412,9 @@ def _to_config(self) -> SelectorGroupChatConfig: ) @classmethod - - def _from_config(cls, config: SelectorGroupChatConfig) -> Self: + def _from_config(cls, config: SelectorGroupChatConfig) -> Self: return cls( - participants = [BaseChatAgent.load_component(participant) for participant in config.participants], + participants=[BaseChatAgent.load_component(participant) for participant in config.participants], model_client=ChatCompletionClient.load_component(config.model_client), termination_condition=TerminationCondition.load_component(config.termination_condition) if config.termination_condition @@ -426,4 +426,3 @@ def _from_config(cls, config: SelectorGroupChatConfig) -> Self: # if config.selector_func # else None, ) - diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py index b8725a81e0f0..ec77cb8e3555 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py @@ -2,7 +2,6 @@ from pydantic import BaseModel -from ...agents import BaseChatAgent from ...base import ChatAgent, TerminationCondition from ...messages import AgentEvent, ChatMessage, HandoffMessage from ...state import SwarmManagerState @@ -10,6 +9,7 @@ from ._base_group_chat_manager import BaseGroupChatManager from autogen_core import ComponentModel, Component + class SwarmGroupChatManager(BaseGroupChatManager): """A group chat manager that selects the next speaker based on handoff message only.""" @@ -94,6 +94,7 @@ async def load_state(self, state: Mapping[str, Any]) -> None: self._current_turn = swarm_state.current_turn self._current_speaker = swarm_state.current_speaker + class SwarmConfig(BaseModel): """The declarative configuration for Swarm.""" @@ -101,6 +102,7 @@ class SwarmConfig(BaseModel): termination_condition: ComponentModel | None = None max_turns: int | None = None + class Swarm(BaseGroupChat, Component[SwarmConfig]): """A group chat team that selects the next speaker based on handoff message only. @@ -228,11 +230,10 @@ def _factory() -> SwarmGroupChatManager: max_turns, ) - return _factory - + return _factory def _to_config(self) -> SwarmConfig: - participants = [participant.dump_component() for participant in self._participants] + participants = [participant.dump_component() for participant in self._participants] termination_condition = self._termination_condition.dump_component() if self._termination_condition else None return SwarmConfig( participants=participants, @@ -240,8 +241,10 @@ def _to_config(self) -> SwarmConfig: max_turns=self._max_turns, ) - @classmethod + @classmethod def _from_config(cls, config: SwarmConfig) -> "Swarm": - participants = [BaseChatAgent.load_component(participant) for participant in config.participants] - termination_condition = TerminationCondition.load_component(config.termination_condition) if config.termination_condition else None + participants = [ChatAgent.load_component(participant) for participant in config.participants] + termination_condition = ( + TerminationCondition.load_component(config.termination_condition) if config.termination_condition else None + ) return cls(participants, termination_condition=termination_condition, max_turns=config.max_turns) diff --git a/python/packages/autogen-agentchat/tests/test_group_chat.py b/python/packages/autogen-agentchat/tests/test_group_chat.py index 157cffbb24de..3a402b4d07fd 100644 --- a/python/packages/autogen-agentchat/tests/test_group_chat.py +++ b/python/packages/autogen-agentchat/tests/test_group_chat.py @@ -24,12 +24,7 @@ ToolCallRequestEvent, ToolCallSummaryMessage, ) -from autogen_agentchat.teams import ( - RoundRobinGroupChat, - SelectorGroupChat, - Swarm, - MagenticOneGroupChat -) +from autogen_agentchat.teams import RoundRobinGroupChat, SelectorGroupChat, Swarm, MagenticOneGroupChat from autogen_agentchat.teams._group_chat._round_robin_group_chat import RoundRobinGroupChatManager from autogen_agentchat.teams._group_chat._selector_group_chat import SelectorGroupChatManager from autogen_agentchat.teams._group_chat._swarm_group_chat import SwarmGroupChatManager @@ -1225,22 +1220,17 @@ async def test_round_robin_group_chat_with_message_list() -> None: @pytest.mark.asyncio async def test_declarative_groupchats_with_config() -> None: # Create basic agents and components for testing - agent1 = AssistantAgent( - "agent_1", model_client=OpenAIChatCompletionClient(model="gpt-4o-2024-05-13", api_key=""), - handoffs=["agent_2"], - ) - agent2 = AssistantAgent( - "agent_2", model_client=OpenAIChatCompletionClient(model="gpt-4o-2024-05-13", api_key="") + agent1 = AssistantAgent( + "agent_1", + model_client=OpenAIChatCompletionClient(model="gpt-4o-2024-05-13", api_key=""), + handoffs=["agent_2"], ) + agent2 = AssistantAgent("agent_2", model_client=OpenAIChatCompletionClient(model="gpt-4o-2024-05-13", api_key="")) termination = MaxMessageTermination(4) model_client = OpenAIChatCompletionClient(model="gpt-4o-2024-05-13", api_key="") # Test round robin - verify config is preserved - round_robin = RoundRobinGroupChat( - participants=[agent1, agent2], - termination_condition=termination, - max_turns=5 - ) + round_robin = RoundRobinGroupChat(participants=[agent1, agent2], termination_condition=termination, max_turns=5) config = round_robin.dump_component() loaded = RoundRobinGroupChat.load_component(config) assert loaded.dump_component() == config @@ -1253,41 +1243,37 @@ async def test_declarative_groupchats_with_config() -> None: termination_condition=termination, max_turns=10, selector_prompt=selector_prompt, - allow_repeated_speaker=True + allow_repeated_speaker=True, ) - config = selector.dump_component() - loaded = SelectorGroupChat.load_component(config) - assert loaded.dump_component() == config - + selector_config = selector.dump_component() + selector_loaded = SelectorGroupChat.load_component(selector_config) + assert selector_loaded.dump_component() == selector_config + # Test swarm with handoff termination handoff_termination = HandoffTermination(target="Agent2") - swarm = Swarm( - participants=[agent1, agent2], - termination_condition=handoff_termination, - max_turns=5 - ) - config = swarm.dump_component() - loaded = Swarm.load_component(config) - assert loaded.dump_component() == config - + swarm = Swarm(participants=[agent1, agent2], termination_condition=handoff_termination, max_turns=5) + swarm_config = swarm.dump_component() + swarm_loaded = Swarm.load_component(swarm_config) + assert swarm_loaded.dump_component() == swarm_config + # Test MagenticOne with custom parameters magentic = MagenticOneGroupChat( participants=[agent1], model_client=model_client, max_turns=15, max_stalls=5, - final_answer_prompt="Custom prompt" + final_answer_prompt="Custom prompt", ) - config = magentic.dump_component() - loaded = MagenticOneGroupChat.load_component(config) - assert loaded.dump_component() == config + magentic_config = magentic.dump_component() + magentic_loaded = MagenticOneGroupChat.load_component(magentic_config) + assert magentic_loaded.dump_component() == magentic_config # Verify component types are correctly set for each for team in [loaded, selector, swarm, magentic]: assert team.component_type == "team" - + # Verify provider strings are correctly set assert round_robin.dump_component().provider == "autogen_agentchat.teams.RoundRobinGroupChat" assert selector.dump_component().provider == "autogen_agentchat.teams.SelectorGroupChat" - assert swarm.dump_component().provider == "autogen_agentchat.teams.Swarm" - assert magentic.dump_component().provider == "autogen_agentchat.teams.MagenticOneGroupChat" \ No newline at end of file + assert swarm.dump_component().provider == "autogen_agentchat.teams.Swarm" + assert magentic.dump_component().provider == "autogen_agentchat.teams.MagenticOneGroupChat" diff --git a/python/packages/autogen-agentchat/tests/test_society_of_mind_agent.py b/python/packages/autogen-agentchat/tests/test_society_of_mind_agent.py index 9bf4713d9c43..f71ba67d5d7a 100644 --- a/python/packages/autogen-agentchat/tests/test_society_of_mind_agent.py +++ b/python/packages/autogen-agentchat/tests/test_society_of_mind_agent.py @@ -89,3 +89,13 @@ async def test_society_of_mind_agent(monkeypatch: pytest.MonkeyPatch) -> None: await society_of_mind_agent2.load_state(state) state2 = await society_of_mind_agent2.save_state() assert state == state2 + + # Test serialization. + + soc_agent_config = society_of_mind_agent.dump_component() + assert soc_agent_config.provider == "autogen_agentchat.agents.SocietyOfMindAgent" + + # Test deserialization. + loaded_soc_agent = SocietyOfMindAgent.load_component(soc_agent_config) + assert isinstance(loaded_soc_agent, SocietyOfMindAgent) + assert loaded_soc_agent.name == "society_of_mind" From c0ad84494614e70e9d7ac9f5a32ab480e5c89cc1 Mon Sep 17 00:00:00 2001 From: Victor Dibia Date: Wed, 22 Jan 2025 16:21:38 -0800 Subject: [PATCH 7/8] update serialization doc notebook --- .../serialize-components.ipynb | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/serialize-components.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/serialize-components.ipynb index 5a3855f48080..aa30b97d6eeb 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/serialize-components.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/serialize-components.ipynb @@ -15,7 +15,7 @@ "\n", "We will be implementing declarative support for the following components:\n", "\n", - "- Termination conditions ✔️\n", + "- Termination conditions\n", "- Tools \n", "- Agents \n", "- Teams \n", @@ -145,6 +145,60 @@ "\n", "```" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Team Example\n", + "\n", + "In the example below, we will define a team in python, export this to a dictionary/json and also demonstrate how the team object can be loaded from the dictionary/json." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\"provider\":\"autogen_agentchat.teams.RoundRobinGroupChat\",\"component_type\":\"team\",\"version\":1,\"component_version\":1,\"description\":null,\"config\":{\"participants\":[{\"provider\":\"autogen_agentchat.agents.AssistantAgent\",\"component_type\":\"agent\",\"version\":1,\"component_version\":1,\"config\":{\"name\":\"assistant\",\"model_client\":{\"provider\":\"autogen_ext.models.openai.OpenAIChatCompletionClient\",\"component_type\":\"model\",\"version\":1,\"component_version\":1,\"config\":{\"model\":\"gpt-4o\"}},\"handoffs\":[{\"target\":\"flights_refunder\",\"description\":\"Handoff to flights_refunder.\",\"name\":\"transfer_to_flights_refunder\",\"message\":\"Transferred to flights_refunder, adopting the role of flights_refunder immediately.\"},{\"target\":\"user\",\"description\":\"Handoff to user.\",\"name\":\"transfer_to_user\",\"message\":\"Transferred to user, adopting the role of user immediately.\"}],\"model_context\":{\"provider\":\"autogen_core.model_context.UnboundedChatCompletionContext\",\"component_type\":\"chat_completion_context\",\"version\":1,\"component_version\":1,\"config\":{}},\"description\":\"An agent that provides assistance with ability to use tools.\",\"system_message\":\"Use tools to solve tasks.\",\"reflect_on_tool_use\":false,\"tool_call_summary_format\":\"{result}\"}}],\"termination_condition\":{\"provider\":\"autogen_agentchat.conditions.MaxMessageTermination\",\"component_type\":\"termination\",\"version\":1,\"component_version\":1,\"config\":{\"max_messages\":2}}}}\n" + ] + } + ], + "source": [ + "from autogen_agentchat.agents import AssistantAgent, UserProxyAgent\n", + "from autogen_agentchat.teams import RoundRobinGroupChat\n", + "from autogen_ext.models.openai import OpenAIChatCompletionClient\n", + "from autogen_agentchat.conditions import MaxMessageTermination\n", + "\n", + "# Create an agent that uses the OpenAI GPT-4o model.\n", + "model_client = OpenAIChatCompletionClient(\n", + " model=\"gpt-4o\",\n", + " # api_key=\"YOUR_API_KEY\",\n", + ")\n", + "agent = AssistantAgent(\n", + " name=\"assistant\",\n", + " model_client=model_client,\n", + " handoffs=[\"flights_refunder\", \"user\"],\n", + " # tools=[], # serializing tools is not yet supported\n", + " system_message=\"Use tools to solve tasks.\",\n", + ")\n", + "\n", + "team = RoundRobinGroupChat(participants=[agent], termination_condition=MaxMessageTermination(2))\n", + "\n", + "team_config = team.dump_component() # dump component\n", + "print(team_config.model_dump_json())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From 9764d1617ccf5d3d18e0f1bf3949afedf31f3dff Mon Sep 17 00:00:00 2001 From: Victor Dibia Date: Wed, 22 Jan 2025 18:36:23 -0800 Subject: [PATCH 8/8] fomating updates --- .../autogen_agentchat/agents/_society_of_mind_agent.py | 5 +++-- .../autogen-agentchat/src/autogen_agentchat/base/_team.py | 8 +++++--- .../teams/_group_chat/_base_group_chat.py | 2 +- .../_group_chat/_magentic_one/_magentic_one_group_chat.py | 7 ++++--- .../teams/_group_chat/_round_robin_group_chat.py | 4 ++-- .../teams/_group_chat/_selector_group_chat.py | 5 +++-- .../teams/_group_chat/_swarm_group_chat.py | 2 +- .../packages/autogen-agentchat/tests/test_group_chat.py | 2 +- .../agentchat-user-guide/serialize-components.ipynb | 2 +- .../autogen-core/src/autogen_core/memory/_base_memory.py | 2 +- python/packages/autogen-core/tests/test_memory.py | 1 + 11 files changed, 23 insertions(+), 17 deletions(-) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_society_of_mind_agent.py b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_society_of_mind_agent.py index 8f1a950e07a5..c43c472c2915 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_society_of_mind_agent.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_society_of_mind_agent.py @@ -2,11 +2,12 @@ from autogen_core import CancellationToken, Component, ComponentModel from autogen_core.models import ChatCompletionClient, LLMMessage, SystemMessage, UserMessage +from pydantic import BaseModel +from typing_extensions import Self from autogen_agentchat.base import Response from autogen_agentchat.state import SocietyOfMindAgentState -from typing_extensions import Self -from pydantic import BaseModel + from ..base import TaskResult, Team from ..messages import ( AgentEvent, diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/base/_team.py b/python/packages/autogen-agentchat/src/autogen_agentchat/base/_team.py index d790130ae93d..0d25edf6d26c 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/base/_team.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/base/_team.py @@ -1,8 +1,10 @@ -from typing import Any, Mapping from abc import ABC, abstractmethod -from ._task import TaskRunner -from pydantic import BaseModel +from typing import Any, Mapping + from autogen_core import ComponentBase +from pydantic import BaseModel + +from ._task import TaskRunner class Team(ABC, TaskRunner, ComponentBase[BaseModel]): diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py index 446c74ca14de..61e3783a80e5 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py @@ -11,10 +11,10 @@ AgentType, CancellationToken, ClosureAgent, + ComponentBase, MessageContext, SingleThreadedAgentRuntime, TypeSubscription, - ComponentBase, ) from autogen_core._closure_agent import ClosureContext from pydantic import BaseModel diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py index 7aa44023fb94..5ce7a71ebe21 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_magentic_one/_magentic_one_group_chat.py @@ -1,10 +1,11 @@ import logging from typing import Callable, List -from typing_extensions import Self -from pydantic import BaseModel -from autogen_core.models import ChatCompletionClient from autogen_core import Component, ComponentModel +from autogen_core.models import ChatCompletionClient +from pydantic import BaseModel +from typing_extensions import Self + from .... import EVENT_LOGGER_NAME, TRACE_LOGGER_NAME from ....base import ChatAgent, TerminationCondition from .._base_group_chat import BaseGroupChat diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py index 83b6c5909381..c7d34f0b3322 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_round_robin_group_chat.py @@ -1,14 +1,14 @@ from typing import Any, Callable, List, Mapping +from autogen_core import Component, ComponentModel from pydantic import BaseModel +from typing_extensions import Self from ...base import ChatAgent, TerminationCondition from ...messages import AgentEvent, ChatMessage from ...state import RoundRobinManagerState from ._base_group_chat import BaseGroupChat from ._base_group_chat_manager import BaseGroupChatManager -from autogen_core import ComponentModel, Component -from typing_extensions import Self class RoundRobinGroupChatManager(BaseGroupChatManager): diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py index 0a3b7fd85eb5..2cc3c02a645d 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py @@ -1,10 +1,11 @@ import logging import re from typing import Any, Callable, Dict, List, Mapping, Sequence + +from autogen_core import Component, ComponentModel +from autogen_core.models import ChatCompletionClient, SystemMessage from pydantic import BaseModel from typing_extensions import Self -from autogen_core.models import ChatCompletionClient, SystemMessage -from autogen_core import Component, ComponentModel from ... import TRACE_LOGGER_NAME from ...agents import BaseChatAgent diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py index ec77cb8e3555..0ca02420537b 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_swarm_group_chat.py @@ -1,5 +1,6 @@ from typing import Any, Callable, List, Mapping +from autogen_core import Component, ComponentModel from pydantic import BaseModel from ...base import ChatAgent, TerminationCondition @@ -7,7 +8,6 @@ from ...state import SwarmManagerState from ._base_group_chat import BaseGroupChat from ._base_group_chat_manager import BaseGroupChatManager -from autogen_core import ComponentModel, Component class SwarmGroupChatManager(BaseGroupChatManager): diff --git a/python/packages/autogen-agentchat/tests/test_group_chat.py b/python/packages/autogen-agentchat/tests/test_group_chat.py index 3a402b4d07fd..c04d7344029f 100644 --- a/python/packages/autogen-agentchat/tests/test_group_chat.py +++ b/python/packages/autogen-agentchat/tests/test_group_chat.py @@ -24,7 +24,7 @@ ToolCallRequestEvent, ToolCallSummaryMessage, ) -from autogen_agentchat.teams import RoundRobinGroupChat, SelectorGroupChat, Swarm, MagenticOneGroupChat +from autogen_agentchat.teams import MagenticOneGroupChat, RoundRobinGroupChat, SelectorGroupChat, Swarm from autogen_agentchat.teams._group_chat._round_robin_group_chat import RoundRobinGroupChatManager from autogen_agentchat.teams._group_chat._selector_group_chat import SelectorGroupChatManager from autogen_agentchat.teams._group_chat._swarm_group_chat import SwarmGroupChatManager diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/serialize-components.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/serialize-components.ipynb index aa30b97d6eeb..16aecc399999 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/serialize-components.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/serialize-components.ipynb @@ -170,9 +170,9 @@ ], "source": [ "from autogen_agentchat.agents import AssistantAgent, UserProxyAgent\n", + "from autogen_agentchat.conditions import MaxMessageTermination\n", "from autogen_agentchat.teams import RoundRobinGroupChat\n", "from autogen_ext.models.openai import OpenAIChatCompletionClient\n", - "from autogen_agentchat.conditions import MaxMessageTermination\n", "\n", "# Create an agent that uses the OpenAI GPT-4o model.\n", "model_client = OpenAIChatCompletionClient(\n", diff --git a/python/packages/autogen-core/src/autogen_core/memory/_base_memory.py b/python/packages/autogen-core/src/autogen_core/memory/_base_memory.py index bfccafdf0a9e..2ae79b4106bb 100644 --- a/python/packages/autogen-core/src/autogen_core/memory/_base_memory.py +++ b/python/packages/autogen-core/src/autogen_core/memory/_base_memory.py @@ -1,6 +1,6 @@ +from abc import ABC, abstractmethod from enum import Enum from typing import Any, Dict, List, Union -from abc import ABC, abstractmethod from pydantic import BaseModel, ConfigDict diff --git a/python/packages/autogen-core/tests/test_memory.py b/python/packages/autogen-core/tests/test_memory.py index 7d0f6b6ea5b6..04054e1b2250 100644 --- a/python/packages/autogen-core/tests/test_memory.py +++ b/python/packages/autogen-core/tests/test_memory.py @@ -1,4 +1,5 @@ from typing import Any + import pytest from autogen_core import CancellationToken from autogen_core.memory import (