From dbd65c05a4deaac7b24197af0363f4363e82cc32 Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Tue, 22 Oct 2024 14:19:40 -0400 Subject: [PATCH 1/4] Add __version__ to new packages (#3881) --- .../autogen-agentchat/src/autogen_agentchat/__init__.py | 4 ++++ python/packages/autogen-core/src/autogen_core/__init__.py | 3 +++ python/packages/autogen-ext/src/autogen_ext/__init__.py | 3 +++ .../autogen-magentic-one/src/autogen_magentic_one/__init__.py | 4 ++++ 4 files changed, 14 insertions(+) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/__init__.py b/python/packages/autogen-agentchat/src/autogen_agentchat/__init__.py index 2891d4ce393f..39b7b1c7498b 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/__init__.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/__init__.py @@ -1,2 +1,6 @@ +import importlib.metadata + TRACE_LOGGER_NAME = "autogen_agentchat" EVENT_LOGGER_NAME = "autogen_agentchat.events" + +__version__ = importlib.metadata.version("autogen_agentchat") diff --git a/python/packages/autogen-core/src/autogen_core/__init__.py b/python/packages/autogen-core/src/autogen_core/__init__.py index e69de29bb2d1..9935d7ecf32e 100644 --- a/python/packages/autogen-core/src/autogen_core/__init__.py +++ b/python/packages/autogen-core/src/autogen_core/__init__.py @@ -0,0 +1,3 @@ +import importlib.metadata + +__version__ = importlib.metadata.version("autogen_core") diff --git a/python/packages/autogen-ext/src/autogen_ext/__init__.py b/python/packages/autogen-ext/src/autogen_ext/__init__.py index e69de29bb2d1..bd2c9ca453aa 100644 --- a/python/packages/autogen-ext/src/autogen_ext/__init__.py +++ b/python/packages/autogen-ext/src/autogen_ext/__init__.py @@ -0,0 +1,3 @@ +import importlib.metadata + +__version__ = importlib.metadata.version("autogen_ext") diff --git a/python/packages/autogen-magentic-one/src/autogen_magentic_one/__init__.py b/python/packages/autogen-magentic-one/src/autogen_magentic_one/__init__.py index e3a60a372fc5..7a98be30016d 100644 --- a/python/packages/autogen-magentic-one/src/autogen_magentic_one/__init__.py +++ b/python/packages/autogen-magentic-one/src/autogen_magentic_one/__init__.py @@ -1 +1,5 @@ +import importlib.metadata + ABOUT = "This is Magentic-One." + +__version__ = importlib.metadata.version("autogen_magentic_one") From 8a4930a9be337a6c35bdc0315b068b8e83814076 Mon Sep 17 00:00:00 2001 From: Eric Zhu Date: Tue, 22 Oct 2024 19:23:02 +0100 Subject: [PATCH 2/4] Refactor agentchat to separate base interfaces from implementations (#3877) --- .../src/autogen_agentchat/agents/__init__.py | 20 ----- .../agents/_code_executor_agent.py | 3 +- .../agents/_coding_assistant_agent.py | 3 +- .../agents/_tool_use_assistant_agent.py | 4 +- .../src/autogen_agentchat/base/__init__.py | 14 +++ .../{agents => base}/_base_chat_agent.py | 58 ++---------- .../src/autogen_agentchat/base/_base_task.py | 20 +++++ .../src/autogen_agentchat/base/_base_team.py | 10 +++ .../_base_termination.py} | 88 +----------------- .../logging/_console_log_handler.py | 2 +- .../src/autogen_agentchat/messages.py | 62 +++++++++++++ .../src/autogen_agentchat/teams/__init__.py | 3 +- .../src/autogen_agentchat/teams/_base_team.py | 17 ---- .../src/autogen_agentchat/teams/_events.py | 2 +- .../_group_chat/_base_chat_agent_container.py | 3 +- .../teams/_group_chat/_base_group_chat.py | 11 ++- .../_group_chat/_base_group_chat_manager.py | 2 +- .../_group_chat/_round_robin_group_chat.py | 3 +- .../teams/_group_chat/_selector_group_chat.py | 4 +- .../autogen_agentchat/teams/_terminations.py | 90 +++++++++++++++++++ .../tests/test_group_chat.py | 6 +- .../tests/test_termination_condition.py | 2 +- .../tutorial/agents.ipynb | 15 ++-- .../tutorial/selector-group-chat.ipynb | 8 +- .../agentchat-user-guide/tutorial/teams.ipynb | 4 +- .../tutorial/termination.ipynb | 8 +- 26 files changed, 246 insertions(+), 216 deletions(-) create mode 100644 python/packages/autogen-agentchat/src/autogen_agentchat/base/__init__.py rename python/packages/autogen-agentchat/src/autogen_agentchat/{agents => base}/_base_chat_agent.py (60%) create mode 100644 python/packages/autogen-agentchat/src/autogen_agentchat/base/_base_task.py create mode 100644 python/packages/autogen-agentchat/src/autogen_agentchat/base/_base_team.py rename python/packages/autogen-agentchat/src/autogen_agentchat/{teams/_termination.py => base/_base_termination.py} (61%) create mode 100644 python/packages/autogen-agentchat/src/autogen_agentchat/messages.py delete mode 100644 python/packages/autogen-agentchat/src/autogen_agentchat/teams/_base_team.py create mode 100644 python/packages/autogen-agentchat/src/autogen_agentchat/teams/_terminations.py diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/__init__.py b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/__init__.py index 21813ed4ed85..e5f1bd8b691b 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/__init__.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/__init__.py @@ -1,29 +1,9 @@ -from ._base_chat_agent import ( - BaseChatAgent, - BaseMessage, - BaseToolUseChatAgent, - ChatMessage, - MultiModalMessage, - StopMessage, - TextMessage, - ToolCallMessage, - ToolCallResultMessage, -) from ._code_executor_agent import CodeExecutorAgent from ._coding_assistant_agent import CodingAssistantAgent from ._tool_use_assistant_agent import ToolUseAssistantAgent __all__ = [ - "BaseChatAgent", - "BaseMessage", - "BaseToolUseChatAgent", - "ChatMessage", "CodeExecutorAgent", "CodingAssistantAgent", - "MultiModalMessage", - "StopMessage", - "TextMessage", - "ToolCallMessage", - "ToolCallResultMessage", "ToolUseAssistantAgent", ] diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py index 3c695dc0b9e4..c38facbe5010 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py @@ -3,7 +3,8 @@ from autogen_core.base import CancellationToken from autogen_core.components.code_executor import CodeBlock, CodeExecutor, extract_markdown_code_blocks -from ._base_chat_agent import BaseChatAgent, ChatMessage, TextMessage +from ..base import BaseChatAgent +from ..messages import ChatMessage, TextMessage class CodeExecutorAgent(BaseChatAgent): diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_coding_assistant_agent.py b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_coding_assistant_agent.py index 36edd0bcd3d4..b93621519764 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_coding_assistant_agent.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_coding_assistant_agent.py @@ -9,7 +9,8 @@ UserMessage, ) -from ._base_chat_agent import BaseChatAgent, ChatMessage, MultiModalMessage, StopMessage, TextMessage +from ..base import BaseChatAgent +from ..messages import ChatMessage, MultiModalMessage, StopMessage, TextMessage class CodingAssistantAgent(BaseChatAgent): diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_tool_use_assistant_agent.py b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_tool_use_assistant_agent.py index 0453abc593ea..bb6ace764726 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_tool_use_assistant_agent.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_tool_use_assistant_agent.py @@ -12,8 +12,8 @@ ) from autogen_core.components.tools import Tool -from ._base_chat_agent import ( - BaseToolUseChatAgent, +from ..base import BaseToolUseChatAgent +from ..messages import ( ChatMessage, MultiModalMessage, StopMessage, diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/base/__init__.py b/python/packages/autogen-agentchat/src/autogen_agentchat/base/__init__.py new file mode 100644 index 000000000000..3b942afbdbae --- /dev/null +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/base/__init__.py @@ -0,0 +1,14 @@ +from ._base_chat_agent import BaseChatAgent, BaseToolUseChatAgent +from ._base_task import TaskResult, TaskRunner +from ._base_team import Team +from ._base_termination import TerminatedException, TerminationCondition + +__all__ = [ + "BaseChatAgent", + "BaseToolUseChatAgent", + "Team", + "TerminatedException", + "TerminationCondition", + "TaskResult", + "TaskRunner", +] diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_base_chat_agent.py b/python/packages/autogen-agentchat/src/autogen_agentchat/base/_base_chat_agent.py similarity index 60% rename from python/packages/autogen-agentchat/src/autogen_agentchat/agents/_base_chat_agent.py rename to python/packages/autogen-agentchat/src/autogen_agentchat/base/_base_chat_agent.py index b52745968747..184e9061a35e 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_base_chat_agent.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/base/_base_chat_agent.py @@ -2,59 +2,13 @@ from typing import List, Sequence from autogen_core.base import CancellationToken -from autogen_core.components import FunctionCall, Image -from autogen_core.components.models import FunctionExecutionResult from autogen_core.components.tools import Tool -from pydantic import BaseModel +from ..messages import ChatMessage +from ._base_task import TaskResult, TaskRunner -class BaseMessage(BaseModel): - """A base message.""" - source: str - """The name of the agent that sent this message.""" - - -class TextMessage(BaseMessage): - """A text message.""" - - content: str - """The content of the message.""" - - -class MultiModalMessage(BaseMessage): - """A multimodal message.""" - - content: List[str | Image] - """The content of the message.""" - - -class ToolCallMessage(BaseMessage): - """A message containing a list of function calls.""" - - content: List[FunctionCall] - """The list of function calls.""" - - -class ToolCallResultMessage(BaseMessage): - """A message containing the results of function calls.""" - - content: List[FunctionExecutionResult] - """The list of function execution results.""" - - -class StopMessage(BaseMessage): - """A message requesting stop of a conversation.""" - - content: str - """The content for the stop message.""" - - -ChatMessage = TextMessage | MultiModalMessage | StopMessage | ToolCallMessage | ToolCallResultMessage -"""A message used by agents in a team.""" - - -class BaseChatAgent(ABC): +class BaseChatAgent(TaskRunner, ABC): """Base class for a chat agent that can participant in a team.""" def __init__(self, name: str, description: str) -> None: @@ -81,6 +35,12 @@ async def on_messages(self, messages: Sequence[ChatMessage], cancellation_token: """Handle incoming messages and return a response message.""" ... + async def run( + self, task: str, *, source: str = "user", cancellation_token: CancellationToken | None = None + ) -> TaskResult: + # TODO: Implement this method. + raise NotImplementedError + class BaseToolUseChatAgent(BaseChatAgent): """Base class for a chat agent that can use tools. diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/base/_base_task.py b/python/packages/autogen-agentchat/src/autogen_agentchat/base/_base_task.py new file mode 100644 index 000000000000..103721557796 --- /dev/null +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/base/_base_task.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass +from typing import Protocol, Sequence + +from ..messages import ChatMessage + + +@dataclass +class TaskResult: + """Result of running a task.""" + + messages: Sequence[ChatMessage] + """Messages produced by the task.""" + + +class TaskRunner(Protocol): + """A task runner.""" + + async def run(self, task: str) -> TaskResult: + """Run the task.""" + ... diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/base/_base_team.py b/python/packages/autogen-agentchat/src/autogen_agentchat/base/_base_team.py new file mode 100644 index 000000000000..5c3677fdc1ef --- /dev/null +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/base/_base_team.py @@ -0,0 +1,10 @@ +from typing import Protocol + +from ._base_task import TaskResult, TaskRunner +from ._base_termination import TerminationCondition + + +class Team(TaskRunner, Protocol): + async def run(self, task: str, *, termination_condition: TerminationCondition | None = None) -> TaskResult: + """Run the team on a given task until the termination condition is met.""" + ... diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_termination.py b/python/packages/autogen-agentchat/src/autogen_agentchat/base/_base_termination.py similarity index 61% rename from python/packages/autogen-agentchat/src/autogen_agentchat/teams/_termination.py rename to python/packages/autogen-agentchat/src/autogen_agentchat/base/_base_termination.py index 78e20a93bbbb..0d0a056eab4c 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_termination.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/base/_base_termination.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod from typing import List, Sequence -from ..agents import ChatMessage, MultiModalMessage, StopMessage, TextMessage +from ..messages import ChatMessage, StopMessage class TerminatedException(BaseException): ... @@ -127,89 +127,3 @@ async def __call__(self, messages: Sequence[ChatMessage]) -> StopMessage | None: async def reset(self) -> None: for condition in self._conditions: await condition.reset() - - -class StopMessageTermination(TerminationCondition): - """Terminate the conversation if a StopMessage is received.""" - - def __init__(self) -> None: - self._terminated = False - - @property - def terminated(self) -> bool: - return self._terminated - - async def __call__(self, messages: Sequence[ChatMessage]) -> StopMessage | None: - if self._terminated: - raise TerminatedException("Termination condition has already been reached") - for message in messages: - if isinstance(message, StopMessage): - self._terminated = True - return StopMessage(content="Stop message received", source="StopMessageTermination") - return None - - async def reset(self) -> None: - self._terminated = False - - -class MaxMessageTermination(TerminationCondition): - """Terminate the conversation after a maximum number of messages have been exchanged. - - Args: - max_messages: The maximum number of messages allowed in the conversation. - """ - - def __init__(self, max_messages: int) -> None: - self._max_messages = max_messages - self._message_count = 0 - - @property - def terminated(self) -> bool: - return self._message_count >= self._max_messages - - async def __call__(self, messages: Sequence[ChatMessage]) -> StopMessage | None: - if self.terminated: - raise TerminatedException("Termination condition has already been reached") - self._message_count += len(messages) - if self._message_count >= self._max_messages: - return StopMessage( - content=f"Maximal number of messages {self._max_messages} reached, current message count: {self._message_count}", - source="MaxMessageTermination", - ) - return None - - async def reset(self) -> None: - self._message_count = 0 - - -class TextMentionTermination(TerminationCondition): - """Terminate the conversation if a specific text is mentioned. - - Args: - text: The text to look for in the messages. - """ - - def __init__(self, text: str) -> None: - self._text = text - self._terminated = False - - @property - def terminated(self) -> bool: - return self._terminated - - async def __call__(self, messages: Sequence[ChatMessage]) -> StopMessage | None: - if self._terminated: - raise TerminatedException("Termination condition has already been reached") - for message in messages: - if isinstance(message, TextMessage | StopMessage) and self._text in message.content: - self._terminated = True - return StopMessage(content=f"Text '{self._text}' mentioned", source="TextMentionTermination") - elif isinstance(message, MultiModalMessage): - for item in message.content: - if isinstance(item, str) and self._text in item: - self._terminated = True - return StopMessage(content=f"Text '{self._text}' mentioned", source="TextMentionTermination") - return None - - async def reset(self) -> None: - self._terminated = False diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/logging/_console_log_handler.py b/python/packages/autogen-agentchat/src/autogen_agentchat/logging/_console_log_handler.py index 5ff7c689a587..d0fb4ab08a11 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/logging/_console_log_handler.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/logging/_console_log_handler.py @@ -3,7 +3,7 @@ import sys from datetime import datetime -from ..agents import ChatMessage, StopMessage, TextMessage +from ..messages import ChatMessage, StopMessage, TextMessage from ..teams._events import ( ContentPublishEvent, SelectSpeakerEvent, diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/messages.py b/python/packages/autogen-agentchat/src/autogen_agentchat/messages.py new file mode 100644 index 000000000000..6aac22248d50 --- /dev/null +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/messages.py @@ -0,0 +1,62 @@ +from typing import List + +from autogen_core.components import FunctionCall, Image +from autogen_core.components.models import FunctionExecutionResult +from pydantic import BaseModel + + +class BaseMessage(BaseModel): + """A base message.""" + + source: str + """The name of the agent that sent this message.""" + + +class TextMessage(BaseMessage): + """A text message.""" + + content: str + """The content of the message.""" + + +class MultiModalMessage(BaseMessage): + """A multimodal message.""" + + content: List[str | Image] + """The content of the message.""" + + +class ToolCallMessage(BaseMessage): + """A message containing a list of function calls.""" + + content: List[FunctionCall] + """The list of function calls.""" + + +class ToolCallResultMessage(BaseMessage): + """A message containing the results of function calls.""" + + content: List[FunctionExecutionResult] + """The list of function execution results.""" + + +class StopMessage(BaseMessage): + """A message requesting stop of a conversation.""" + + content: str + """The content for the stop message.""" + + +ChatMessage = TextMessage | MultiModalMessage | StopMessage | ToolCallMessage | ToolCallResultMessage +"""A message used by agents in a team.""" + + +__all__ = [ + "BaseMessage", + "TextMessage", + "MultiModalMessage", + "ToolCallMessage", + "ToolCallResultMessage", + "StopMessage", + "ChatMessage", +] diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/__init__.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/__init__.py index f1a79cff7153..e305a9e5c995 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/__init__.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/__init__.py @@ -1,9 +1,8 @@ from ._group_chat._round_robin_group_chat import RoundRobinGroupChat from ._group_chat._selector_group_chat import SelectorGroupChat -from ._termination import MaxMessageTermination, StopMessageTermination, TerminationCondition, TextMentionTermination +from ._terminations import MaxMessageTermination, StopMessageTermination, TextMentionTermination __all__ = [ - "TerminationCondition", "MaxMessageTermination", "TextMentionTermination", "StopMessageTermination", diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_base_team.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_base_team.py deleted file mode 100644 index 312524b8c7c2..000000000000 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_base_team.py +++ /dev/null @@ -1,17 +0,0 @@ -from dataclasses import dataclass -from typing import List, Protocol - -from ..agents import ChatMessage -from ._termination import TerminationCondition - - -@dataclass -class TeamRunResult: - messages: List[ChatMessage] - """The messages generated by the team.""" - - -class BaseTeam(Protocol): - async def run(self, task: str, *, termination_condition: TerminationCondition | None = None) -> TeamRunResult: - """Run the team on a given task until the termination condition is met.""" - ... diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_events.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_events.py index acdc9bdbd0b6..5bfeff417841 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_events.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_events.py @@ -1,7 +1,7 @@ from autogen_core.base import AgentId from pydantic import BaseModel, ConfigDict -from ..agents import MultiModalMessage, StopMessage, TextMessage, ToolCallMessage, ToolCallResultMessage +from ..messages import MultiModalMessage, StopMessage, TextMessage, ToolCallMessage, ToolCallResultMessage class ContentPublishEvent(BaseModel): diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_chat_agent_container.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_chat_agent_container.py index 3ec378f73c52..275e505d6d3a 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_chat_agent_container.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_chat_agent_container.py @@ -8,7 +8,8 @@ from autogen_core.components.tool_agent import ToolException from ... import EVENT_LOGGER_NAME -from ...agents import BaseChatAgent, MultiModalMessage, StopMessage, TextMessage, ToolCallMessage, ToolCallResultMessage +from ...base import BaseChatAgent +from ...messages import MultiModalMessage, StopMessage, TextMessage, ToolCallMessage, ToolCallResultMessage from .._events import ContentPublishEvent, ContentRequestEvent, ToolCallEvent, ToolCallResultEvent from ._sequential_routed_agent import SequentialRoutedAgent 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 633fc0f85242..08f44a1af665 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 @@ -8,15 +8,14 @@ from autogen_core.components.tool_agent import ToolAgent from autogen_core.components.tools import Tool -from ...agents import BaseChatAgent, BaseToolUseChatAgent, ChatMessage, TextMessage -from .._base_team import BaseTeam, TeamRunResult +from ...base import BaseChatAgent, BaseToolUseChatAgent, TaskResult, Team, TerminationCondition +from ...messages import ChatMessage, TextMessage from .._events import ContentPublishEvent, ContentRequestEvent -from .._termination import TerminationCondition from ._base_chat_agent_container import BaseChatAgentContainer from ._base_group_chat_manager import BaseGroupChatManager -class BaseGroupChat(BaseTeam, ABC): +class BaseGroupChat(Team, ABC): """The base class for group chat teams. To implement a group chat team, first create a subclass of :class:`BaseGroupChatManager` and then @@ -69,7 +68,7 @@ def _factory() -> ToolAgent: return _factory - async def run(self, task: str, *, termination_condition: TerminationCondition | None = None) -> TeamRunResult: + async def run(self, task: str, *, termination_condition: TerminationCondition | None = None) -> TaskResult: """Run the team and return the result.""" # Create intervention handler for termination. @@ -170,4 +169,4 @@ async def collect_group_chat_messages( await runtime.stop_when_idle() # Return the result. - return TeamRunResult(messages=group_chat_messages) + return TaskResult(messages=group_chat_messages) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat_manager.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat_manager.py index 38170053abdc..5f59e9e631d7 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat_manager.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat_manager.py @@ -6,8 +6,8 @@ from autogen_core.components import event from ... import EVENT_LOGGER_NAME +from ...base import TerminationCondition from .._events import ContentPublishEvent, ContentRequestEvent, TerminationEvent -from .._termination import TerminationCondition from ._sequential_routed_agent import SequentialRoutedAgent event_logger = logging.getLogger(EVENT_LOGGER_NAME) 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 6ebacaac34d9..daab986027ed 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,8 +1,7 @@ from typing import Callable, List -from ...agents import BaseChatAgent +from ...base import BaseChatAgent, TerminationCondition from .._events import ContentPublishEvent -from .._termination import TerminationCondition from ._base_group_chat import BaseGroupChat from ._base_group_chat_manager import 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 473b92f324f1..f000cbd69dc2 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 @@ -5,9 +5,9 @@ from autogen_core.components.models import ChatCompletionClient, SystemMessage from ... import EVENT_LOGGER_NAME, TRACE_LOGGER_NAME -from ...agents import BaseChatAgent, MultiModalMessage, StopMessage, TextMessage +from ...base import BaseChatAgent, TerminationCondition +from ...messages import MultiModalMessage, StopMessage, TextMessage from .._events import ContentPublishEvent, SelectSpeakerEvent -from .._termination import TerminationCondition from ._base_group_chat import BaseGroupChat from ._base_group_chat_manager import BaseGroupChatManager diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_terminations.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_terminations.py new file mode 100644 index 000000000000..191e14f8566c --- /dev/null +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_terminations.py @@ -0,0 +1,90 @@ +from typing import Sequence + +from ..base import TerminatedException, TerminationCondition +from ..messages import ChatMessage, MultiModalMessage, StopMessage, TextMessage + + +class StopMessageTermination(TerminationCondition): + """Terminate the conversation if a StopMessage is received.""" + + def __init__(self) -> None: + self._terminated = False + + @property + def terminated(self) -> bool: + return self._terminated + + async def __call__(self, messages: Sequence[ChatMessage]) -> StopMessage | None: + if self._terminated: + raise TerminatedException("Termination condition has already been reached") + for message in messages: + if isinstance(message, StopMessage): + self._terminated = True + return StopMessage(content="Stop message received", source="StopMessageTermination") + return None + + async def reset(self) -> None: + self._terminated = False + + +class MaxMessageTermination(TerminationCondition): + """Terminate the conversation after a maximum number of messages have been exchanged. + + Args: + max_messages: The maximum number of messages allowed in the conversation. + """ + + def __init__(self, max_messages: int) -> None: + self._max_messages = max_messages + self._message_count = 0 + + @property + def terminated(self) -> bool: + return self._message_count >= self._max_messages + + async def __call__(self, messages: Sequence[ChatMessage]) -> StopMessage | None: + if self.terminated: + raise TerminatedException("Termination condition has already been reached") + self._message_count += len(messages) + if self._message_count >= self._max_messages: + return StopMessage( + content=f"Maximal number of messages {self._max_messages} reached, current message count: {self._message_count}", + source="MaxMessageTermination", + ) + return None + + async def reset(self) -> None: + self._message_count = 0 + + +class TextMentionTermination(TerminationCondition): + """Terminate the conversation if a specific text is mentioned. + + Args: + text: The text to look for in the messages. + """ + + def __init__(self, text: str) -> None: + self._text = text + self._terminated = False + + @property + def terminated(self) -> bool: + return self._terminated + + async def __call__(self, messages: Sequence[ChatMessage]) -> StopMessage | None: + if self._terminated: + raise TerminatedException("Termination condition has already been reached") + for message in messages: + if isinstance(message, TextMessage | StopMessage) and self._text in message.content: + self._terminated = True + return StopMessage(content=f"Text '{self._text}' mentioned", source="TextMentionTermination") + elif isinstance(message, MultiModalMessage): + for item in message.content: + if isinstance(item, str) and self._text in item: + self._terminated = True + return StopMessage(content=f"Text '{self._text}' mentioned", source="TextMentionTermination") + return None + + async def reset(self) -> None: + self._terminated = False diff --git a/python/packages/autogen-agentchat/tests/test_group_chat.py b/python/packages/autogen-agentchat/tests/test_group_chat.py index 21fb2ad4082c..54e759663389 100644 --- a/python/packages/autogen-agentchat/tests/test_group_chat.py +++ b/python/packages/autogen-agentchat/tests/test_group_chat.py @@ -7,15 +7,13 @@ import pytest from autogen_agentchat import EVENT_LOGGER_NAME from autogen_agentchat.agents import ( - BaseChatAgent, - ChatMessage, CodeExecutorAgent, CodingAssistantAgent, - StopMessage, - TextMessage, ToolUseAssistantAgent, ) +from autogen_agentchat.base import BaseChatAgent from autogen_agentchat.logging import FileLogHandler +from autogen_agentchat.messages import ChatMessage, StopMessage, TextMessage from autogen_agentchat.teams import ( RoundRobinGroupChat, SelectorGroupChat, diff --git a/python/packages/autogen-agentchat/tests/test_termination_condition.py b/python/packages/autogen-agentchat/tests/test_termination_condition.py index fe850542f264..c3f575d34adb 100644 --- a/python/packages/autogen-agentchat/tests/test_termination_condition.py +++ b/python/packages/autogen-agentchat/tests/test_termination_condition.py @@ -1,5 +1,5 @@ import pytest -from autogen_agentchat.agents import StopMessage, TextMessage +from autogen_agentchat.messages import StopMessage, TextMessage from autogen_agentchat.teams import MaxMessageTermination, StopMessageTermination, TextMentionTermination diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/agents.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/agents.ipynb index 649c90dc3985..55fc8c203cab 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/agents.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/agents.ipynb @@ -16,7 +16,7 @@ "\n", "AgentChat provides a set of preset Agents, each with variations in how an agent might respond to received messages. \n", "\n", - "Each agent inherits from a {py:class}`~autogen_agentchat.agents.BaseChatAgent` class with a few generic properties:\n", + "Each agent inherits from a {py:class}`~autogen_agentchat.base.BaseChatAgent` class with a few generic properties:\n", "\n", "- `name`: The name of the agent. This is used by the team to uniquely identify the agent. It should be unique within the team.\n", "- `description`: The description of the agent. This is used by the team to make decisions about which agents to use. The description should detail the agent's capabilities and how to interact with it.\n", @@ -30,7 +30,7 @@ "To learn more about the runtime in `autogen-core`, see the [autogen-core documentation on agents and runtime](../../core-user-guide/framework/agent-and-agent-runtime.ipynb).\n", "```\n", "\n", - "Each agent also implements an {py:meth}`~autogen_agentchat.agents.BaseChatAgent.on_messages` method that defines the behavior of the agent in response to a message.\n", + "Each agent also implements an {py:meth}`~autogen_agentchat.base.BaseChatAgent.on_messages` method that defines the behavior of the agent in response to a message.\n", "\n", "\n", "To begin, let us import the required classes and set up a model client that will be used by agents.\n" @@ -45,8 +45,9 @@ "import logging\n", "\n", "from autogen_agentchat import EVENT_LOGGER_NAME\n", - "from autogen_agentchat.agents import CodingAssistantAgent, TextMessage, ToolUseAssistantAgent\n", + "from autogen_agentchat.agents import CodingAssistantAgent, ToolUseAssistantAgent\n", "from autogen_agentchat.logging import ConsoleLogHandler\n", + "from autogen_agentchat.messages import TextMessage\n", "from autogen_agentchat.teams import MaxMessageTermination, RoundRobinGroupChat, SelectorGroupChat\n", "from autogen_core.base import CancellationToken\n", "from autogen_core.components.models import OpenAIChatCompletionClient\n", @@ -250,8 +251,8 @@ "import asyncio\n", "from typing import Sequence\n", "\n", - "from autogen_agentchat.agents import (\n", - " BaseChatAgent,\n", + "from autogen_agentchat.base import BaseChatAgent\n", + "from autogen_agentchat.messages import (\n", " ChatMessage,\n", " StopMessage,\n", " TextMessage,\n", @@ -298,7 +299,7 @@ ], "metadata": { "kernelspec": { - "display_name": "agnext", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -312,7 +313,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.11.5" } }, "nbformat": 4, diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/selector-group-chat.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/selector-group-chat.ipynb index 7636f14d9240..399fb5a4de2a 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/selector-group-chat.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/selector-group-chat.ipynb @@ -41,13 +41,11 @@ "from typing import Sequence\n", "\n", "from autogen_agentchat.agents import (\n", - " BaseChatAgent,\n", - " ChatMessage,\n", " CodingAssistantAgent,\n", - " StopMessage,\n", - " TextMessage,\n", " ToolUseAssistantAgent,\n", ")\n", + "from autogen_agentchat.base import BaseChatAgent\n", + "from autogen_agentchat.messages import ChatMessage, StopMessage, TextMessage\n", "from autogen_agentchat.teams import SelectorGroupChat, StopMessageTermination\n", "from autogen_core.base import CancellationToken\n", "from autogen_core.components.tools import FunctionTool\n", @@ -270,7 +268,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.6" + "version": "3.11.5" } }, "nbformat": 4, diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/teams.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/teams.ipynb index ce30aee61cb1..d957a90c52f2 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/teams.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/teams.ipynb @@ -194,7 +194,7 @@ ], "metadata": { "kernelspec": { - "display_name": "agnext", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -208,7 +208,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.11.5" } }, "nbformat": 4, diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/termination.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/termination.ipynb index bdb1dccff3f3..444e56b2b695 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/termination.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/tutorial/termination.ipynb @@ -9,7 +9,7 @@ "\n", "In the previous section, we explored how to define agents, and organize them into teams that can solve tasks by communicating (a conversation). However, conversations can go on forever, and in many cases, we need to know _when_ to stop them. This is the role of the termination condition.\n", "\n", - "AgentChat supports several termination condition by providing a base `TerminationCondition` class and several implementations that inherit from it.\n", + "AgentChat supports several termination condition by providing a base {py:class}`~autogen_agentchat.base.TerminationCondition` class and several implementations that inherit from it.\n", "\n", "A termination condition is a callable that takes a sequence of ChatMessage objects since the last time the condition was called, and returns a StopMessage if the conversation should be terminated, or None otherwise. Once a termination condition has been reached, it must be reset before it can be used again.\n", "\n", @@ -66,7 +66,7 @@ "source": [ "## MaxMessageTermination \n", "\n", - "The simplest termination condition is the `MaxMessageTermination` condition, which terminates the conversation after a fixed number of messages. \n" + "The simplest termination condition is the {py:class}`~autogen_agentchat.teams.MaxMessageTermination` condition, which terminates the conversation after a fixed number of messages. \n" ] }, { @@ -184,7 +184,7 @@ ], "metadata": { "kernelspec": { - "display_name": "agnext", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -198,7 +198,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.11.5" } }, "nbformat": 4, From bfa0b3b94fd0c9de2cc9c369815737558e0a8a4d Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Tue, 22 Oct 2024 15:32:03 -0400 Subject: [PATCH 3/4] Automate removing the awaiting-op-response label (#3888) --- .github/workflows/issue-user-responded.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/issue-user-responded.yml diff --git a/.github/workflows/issue-user-responded.yml b/.github/workflows/issue-user-responded.yml new file mode 100644 index 000000000000..3a055ef99beb --- /dev/null +++ b/.github/workflows/issue-user-responded.yml @@ -0,0 +1,17 @@ +name: Remove awaiting-op-response label if op responded +on: + issue_comment: + types: [created] +jobs: + label_issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - run: gh issue edit "$NUMBER" --remove-label "$LABELS" + if: ${{ github.event.comment.user.login == github.event.issue.user.login && contains(github.event.issue.labels.*.name, 'awaiting-op-response') }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} + LABELS: awaiting-op-response From 5391804cfe0c780c2f39b5ab089f38f105aa55e4 Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Tue, 22 Oct 2024 15:40:06 -0400 Subject: [PATCH 4/4] Add pull-requests permission (#3889) --- .github/workflows/issue-user-responded.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/issue-user-responded.yml b/.github/workflows/issue-user-responded.yml index 3a055ef99beb..793bf4168902 100644 --- a/.github/workflows/issue-user-responded.yml +++ b/.github/workflows/issue-user-responded.yml @@ -7,6 +7,7 @@ jobs: runs-on: ubuntu-latest permissions: issues: write + pull-requests: write steps: - run: gh issue edit "$NUMBER" --remove-label "$LABELS" if: ${{ github.event.comment.user.login == github.event.issue.user.login && contains(github.event.issue.labels.*.name, 'awaiting-op-response') }}