From 64365b6835ffb18c0f2abde26d70989b6ef66300 Mon Sep 17 00:00:00 2001 From: Eric Zhu Date: Wed, 9 Oct 2024 09:26:13 -0700 Subject: [PATCH] Termination condition for agentchat teams (#3696) * Update PR link in blog post (#3602) * Update PR link in blog post * Update index.mdx * Create CI to tag issues with needs triage (#3605) * Update issue templates (#3610) * Update config.yml * Delete .github/ISSUE_TEMPLATE.md * Delete .github/ISSUE_TEMPLATE/general_issue.yml * Update feature_request.yml * Update feature_request.yml * Update feature_request.yml * Update feature_request.yml * Update bug_report.yml * Update .github/ISSUE_TEMPLATE/bug_report.yml Co-authored-by: Eric Zhu * Update .github/ISSUE_TEMPLATE/config.yml Co-authored-by: Eric Zhu * Update bug_report.yml * Update config.yml --------- Co-authored-by: Eric Zhu * termination condition * Termination condition * termination condition in group chat manager * Update module import * Fix logging * Clean up * Fix doc string --------- Co-authored-by: Jack Gerrits --- .github/ISSUE_TEMPLATE.md | 57 --- .github/ISSUE_TEMPLATE/bug_report.yml | 80 ++-- .github/ISSUE_TEMPLATE/config.yml | 4 + .github/ISSUE_TEMPLATE/feature_request.yml | 26 +- .github/ISSUE_TEMPLATE/general_issue.yml | 41 -- .github/workflows/issue-needs-triage.yml | 18 + README.md | 6 +- .../agents/_tool_use_assistant_agent.py | 2 +- .../src/autogen_agentchat/teams/__init__.py | 9 + .../src/autogen_agentchat/teams/_base_team.py | 6 +- .../src/autogen_agentchat/teams/_events.py | 12 + .../src/autogen_agentchat/teams/_logging.py | 102 ++--- .../autogen_agentchat/teams/_termination.py | 215 ++++++++++ .../teams/group_chat/__init__.py | 4 - .../teams/group_chat/_base_group_chat.py | 14 +- .../group_chat/_base_group_chat_manager.py | 32 +- .../group_chat/_round_robin_group_chat.py | 18 +- .../teams/group_chat/_selector_group_chat.py | 9 +- .../tests/test_group_chat.py | 31 +- .../tests/test_termination_condition.py | 126 ++++++ .../examples/company-research.ipynb | 22 +- .../examples/literature-review.ipynb | 17 +- .../examples/travel-planning.ipynb | 8 +- .../guides/code-execution.ipynb | 9 +- .../guides/selector-group-chat.ipynb | 227 +++------- .../guides/tool_use.ipynb | 405 ++++++++---------- .../agentchat-user-guide/stocksnippet.md | 6 +- .../index.mdx | 103 +++++ 28 files changed, 936 insertions(+), 673 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md delete mode 100644 .github/ISSUE_TEMPLATE/general_issue.yml create mode 100644 .github/workflows/issue-needs-triage.yml create mode 100644 python/packages/autogen-agentchat/src/autogen_agentchat/teams/_termination.py create mode 100644 python/packages/autogen-agentchat/tests/test_termination_condition.py create mode 100644 website/blog/2024-10-02-new-autogen-architecture-preview/index.mdx diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index b3b0d0daeed6..000000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,57 +0,0 @@ -### Description - - -### Environment -- AutoGen version: -- Python version: -- Operating System: - -### Steps to Reproduce (for bugs) - - -1. Step 1 -2. Step 2 -3. ... - -### Expected Behavior - - -### Actual Behavior - - -### Screenshots / Logs (if applicable) - - -### Additional Information - - -### Possible Solution (if you have one) - - -### Is this a Bug or Feature Request? - - -### Priority - - -### Difficulty - - -### Any related issues? - - -### Any relevant discussions? - - -### Checklist - -- [ ] I have searched for similar issues and didn't find any duplicates. -- [ ] I have provided a clear and concise description of the issue. -- [ ] I have included the necessary environment details. -- [ ] I have outlined the steps to reproduce the issue. -- [ ] I have included any relevant logs or screenshots. -- [ ] I have indicated whether this is a bug or a feature request. -- [ ] I have set the priority and difficulty levels. - -### Additional Comments - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 434226b3e884..090fa6cc5939 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,53 +1,55 @@ name: Bug Report -description: File a bug report -title: "[Bug]: " +description: Report a bug labels: ["bug"] body: - type: textarea - id: description attributes: - label: Describe the bug - description: A clear and concise description of what the bug is. - placeholder: What went wrong? + label: What happened? + description: Please provide as much information as possible, this helps us address the issue. + validations: + required: true - type: textarea - id: reproduce attributes: - label: Steps to reproduce - description: | - Steps to reproduce the behavior: - - 1. Step 1 - 2. Step 2 - 3. ... - 4. See error - placeholder: How can we replicate the issue? + label: What did you expect to happen? + validations: + required: true - type: textarea - id: modelused attributes: - label: Model Used - description: A description of the model that was used when the error was encountered + label: How can we reproduce it (as minimally and precisely as possible)? + description: Please provide steps to reproduce. Provide code that can be run if possible. + validations: + required: true + - type: input + attributes: + label: AutoGen version + description: What version or commit of the library was used + validations: + required: true + - type: dropdown + attributes: + label: Which package was this bug in + options: + - Core + - AgentChat + - Extensions + - AutoGen Studio + - Magentic One + - AutoGen Bench + - Other + validations: + required: true + - type: input + attributes: + label: Model used + description: If a model was used, please describe it here, indicating whether it is a local model or a cloud-hosted model placeholder: gpt-4, mistral-7B etc - - type: textarea - id: expected_behavior + - type: input attributes: - label: Expected Behavior - description: A clear and concise description of what you expected to happen. - placeholder: What should have happened? - - type: textarea - id: screenshots + label: Python version + - type: input attributes: - label: Screenshots and logs - description: If applicable, add screenshots and logs to help explain your problem. - placeholder: Add screenshots here + label: Operating system - type: textarea - id: additional_information - attributes: - label: Additional Information - description: | - - AutoGen Version: - - Operating System: - - Python Version: - - Related Issues: - - Any other relevant information. - placeholder: Any additional details + attributes: + label: Any additional info you think would be helpful for fixing this bug diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 0086358db1eb..76afcbcc5f87 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1,5 @@ blank_issues_enabled: true +contact_links: + - name: Questions or general help 💬 + url: https://github.com/microsoft/autogen/discussions + about: Please ask and answer questions here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index e8a63df7a6e2..57f360761a76 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,26 +1,18 @@ name: Feature Request -description: File a feature request +description: Request a new feature or enhancement labels: ["enhancement"] -title: "[Feature Request]: " body: - type: textarea - id: problem_description attributes: - label: Is your feature request related to a problem? Please describe. - description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - placeholder: What problem are you trying to solve? + label: What feature would you like to be added? + description: Please describe the desired feature. Be descriptive, provide examples and if possible, provide a proposed solution. + validations: + required: true - type: textarea - id: solution_description attributes: - label: Describe the solution you'd like - description: A clear and concise description of what you want to happen. - placeholder: How do you envision the solution? - - - type: textarea - id: additional_context - attributes: - label: Additional context - description: Add any other context or screenshots about the feature request here. - placeholder: Any additional information + label: Why is this needed? + description: Why is it important that this feature is implemented? What problem or need does it solve? + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/general_issue.yml b/.github/ISSUE_TEMPLATE/general_issue.yml deleted file mode 100644 index b585f4642f44..000000000000 --- a/.github/ISSUE_TEMPLATE/general_issue.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: General Issue -description: File a general issue -title: "[Issue]: " -labels: [] - -body: - - type: textarea - id: description - attributes: - label: Describe the issue - description: A clear and concise description of what the issue is. - placeholder: What went wrong? - - type: textarea - id: reproduce - attributes: - label: Steps to reproduce - description: | - Steps to reproduce the behavior: - - 1. Step 1 - 2. Step 2 - 3. ... - 4. See error - placeholder: How can we replicate the issue? - - type: textarea - id: screenshots - attributes: - label: Screenshots and logs - description: If applicable, add screenshots and logs to help explain your problem. - placeholder: Add screenshots here - - type: textarea - id: additional_information - attributes: - label: Additional Information - description: | - - AutoGen Version: - - Operating System: - - Python Version: - - Related Issues: - - Any other relevant information. - placeholder: Any additional details diff --git a/.github/workflows/issue-needs-triage.yml b/.github/workflows/issue-needs-triage.yml new file mode 100644 index 000000000000..59cb3479c808 --- /dev/null +++ b/.github/workflows/issue-needs-triage.yml @@ -0,0 +1,18 @@ +name: Label issues with needs-triage +on: + issues: + types: + - reopened + - opened +jobs: + label_issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - run: gh issue edit "$NUMBER" --add-label "$LABELS" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} + LABELS: needs-triage diff --git a/README.md b/README.md index 87528d3bb309..9f5c2ed460e2 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ and running on your machine. ```python from autogen_agentchat.agents import CodeExecutorAgent, CodingAssistantAgent -from autogen_agentchat.teams.group_chat import RoundRobinGroupChat +from autogen_agentchat.teams import RoundRobinGroupChat, StopMessageTermination from autogen_core.components.code_executor import DockerCommandLineCodeExecutor from autogen_core.components.models import OpenAIChatCompletionClient @@ -118,9 +118,9 @@ async with DockerCommandLineCodeExecutor(work_dir="coding") as code_executor: ) group_chat = RoundRobinGroupChat([coding_assistant_agent, code_executor_agent]) result = await group_chat.run( - task="Create a plot of NVDIA and TSLA stock returns YTD from 2024-01-01 and save it to 'nvidia_tesla_2024_ytd.png'." + task="Create a plot of NVDIA and TSLA stock returns YTD from 2024-01-01 and save it to 'nvidia_tesla_2024_ytd.png'.", + termination_condition=StopMessageTermination(), ) - print(result) ``` ### C# 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 ecf02d16d3ed..0453abc593ea 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 @@ -36,7 +36,7 @@ def __init__( registered_tools: List[Tool], *, description: str = "An agent that provides assistance with ability to use tools.", - system_message: str = "You are a helpful AI assistant. Solve tasks using your tools.", + system_message: str = "You are a helpful AI assistant. Solve tasks using your tools. Reply with 'TERMINATE' when the task has been completed.", ): super().__init__(name=name, description=description, registered_tools=registered_tools) self._model_client = model_client 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 92fc1b98bfd2..c4e6bfd14cb4 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/__init__.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/__init__.py @@ -1,8 +1,17 @@ from ._logging import EVENT_LOGGER_NAME, TRACE_LOGGER_NAME, ConsoleLogHandler, FileLogHandler +from ._termination import MaxMessageTermination, StopMessageTermination, TerminationCondition, TextMentionTermination +from .group_chat._round_robin_group_chat import RoundRobinGroupChat +from .group_chat._selector_group_chat import SelectorGroupChat __all__ = [ "TRACE_LOGGER_NAME", "EVENT_LOGGER_NAME", "ConsoleLogHandler", "FileLogHandler", + "TerminationCondition", + "MaxMessageTermination", + "TextMentionTermination", + "StopMessageTermination", + "RoundRobinGroupChat", + "SelectorGroupChat", ] 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 index 470e6caabbaf..351193ae71e4 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_base_team.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_base_team.py @@ -4,6 +4,7 @@ from ..agents import ChatMessage from ._logging import EVENT_LOGGER_NAME, ConsoleLogHandler +from ._termination import TerminationCondition logger = logging.getLogger(EVENT_LOGGER_NAME) logger.setLevel(logging.INFO) @@ -14,9 +15,10 @@ @dataclass class TeamRunResult: messages: List[ChatMessage] + """The messages generated by the team.""" class BaseTeam(Protocol): - async def run(self, task: str) -> TeamRunResult: - """Run the team and return the result.""" + 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 36268ebdd1fa..acdc9bdbd0b6 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_events.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_events.py @@ -61,3 +61,15 @@ class SelectSpeakerEvent(BaseModel): """The agent ID that selected the speaker.""" model_config = ConfigDict(arbitrary_types_allowed=True) + + +class TerminationEvent(BaseModel): + """An event for terminating a conversation.""" + + agent_message: StopMessage + """The stop message that terminates the conversation.""" + + source: AgentId + """The agent ID that triggered the termination.""" + + model_config = ConfigDict(arbitrary_types_allowed=True) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_logging.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_logging.py index d0c3b65b5bc6..680a0cfb5ad6 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_logging.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_logging.py @@ -3,100 +3,72 @@ import sys from dataclasses import asdict, is_dataclass from datetime import datetime -from typing import Any, Dict, List, Union +from typing import Any -from autogen_core.base import AgentId -from autogen_core.components import FunctionCall, Image -from autogen_core.components.models import FunctionExecutionResult - -from ..agents import ChatMessage, MultiModalMessage, StopMessage, TextMessage, ToolCallMessage, ToolCallResultMessage -from ._events import ContentPublishEvent, SelectSpeakerEvent, ToolCallEvent, ToolCallResultEvent +from ..agents import ChatMessage, StopMessage, TextMessage +from ._events import ContentPublishEvent, SelectSpeakerEvent, TerminationEvent, ToolCallEvent, ToolCallResultEvent TRACE_LOGGER_NAME = "autogen_agentchat" EVENT_LOGGER_NAME = "autogen_agentchat.events" -ContentType = Union[str, List[Union[str, Image]], List[FunctionCall], List[FunctionExecutionResult]] - -class BaseLogHandler(logging.Handler): - def serialize_content( - self, - content: Union[ContentType, ChatMessage], - ) -> Union[List[Any], Dict[str, Any], str]: - if isinstance(content, (str, list)): - return content - elif isinstance(content, (TextMessage, MultiModalMessage, ToolCallMessage, ToolCallResultMessage, StopMessage)): - return asdict(content) - elif isinstance(content, Image): - return {"type": "image", "data": content.data_uri} - elif isinstance(content, FunctionCall): - return {"type": "function_call", "name": content.name, "arguments": content.arguments} - elif isinstance(content, FunctionExecutionResult): - return {"type": "function_execution_result", "content": content.content} - return str(content) +class ConsoleLogHandler(logging.Handler): @staticmethod - def json_serializer(obj: Any) -> Any: - if is_dataclass(obj) and not isinstance(obj, type): - return asdict(obj) - elif isinstance(obj, type): - return str(obj) - return str(obj) - - -class ConsoleLogHandler(BaseLogHandler): - def _format_chat_message( - self, - *, - source_agent_id: AgentId | None, - message: ChatMessage, - timestamp: str, - ) -> str: - body = f"{self.serialize_content(message.content)}" - if source_agent_id is None: - console_message = f"\n{'-'*75} \n" f"\033[91m[{timestamp}]:\033[0m\n" f"\n{body}" + def serialize_chat_message(message: ChatMessage) -> str: + if isinstance(message, TextMessage | StopMessage): + return message.content else: - # Display the source agent type rather than agent ID for better readability. - # Also in AgentChat the agent type is unique for each agent. - console_message = f"\n{'-'*75} \n" f"\033[91m[{timestamp}], {source_agent_id.type}:\033[0m\n" f"\n{body}" - return console_message + d = message.model_dump() + assert "content" in d + return json.dumps(d["content"], indent=2) def emit(self, record: logging.LogRecord) -> None: ts = datetime.fromtimestamp(record.created).isoformat() if isinstance(record.msg, ContentPublishEvent): - sys.stdout.write( - self._format_chat_message( - source_agent_id=record.msg.source, - message=record.msg.agent_message, - timestamp=ts, + if record.msg.source is None: + sys.stdout.write( + f"\n{'-'*75} \n" + f"\033[91m[{ts}]:\033[0m\n" + f"\n{self.serialize_chat_message(record.msg.agent_message)}" + ) + else: + sys.stdout.write( + f"\n{'-'*75} \n" + f"\033[91m[{ts}], {record.msg.source.type}:\033[0m\n" + f"\n{self.serialize_chat_message(record.msg.agent_message)}" ) - ) sys.stdout.flush() elif isinstance(record.msg, ToolCallEvent): sys.stdout.write( f"\n{'-'*75} \n" f"\033[91m[{ts}], Tool Call:\033[0m\n" - f"\n{self.serialize_content(record.msg.agent_message)}" + f"\n{self.serialize_chat_message(record.msg.agent_message)}" ) sys.stdout.flush() elif isinstance(record.msg, ToolCallResultEvent): sys.stdout.write( f"\n{'-'*75} \n" f"\033[91m[{ts}], Tool Call Result:\033[0m\n" - f"\n{self.serialize_content(record.msg.agent_message)}" + f"\n{self.serialize_chat_message(record.msg.agent_message)}" ) sys.stdout.flush() elif isinstance(record.msg, SelectSpeakerEvent): + sys.stdout.write( + f"\n{'-'*75} \n" f"\033[91m[{ts}], Selected Next Speaker:\033[0m\n" f"\n{record.msg.selected_speaker}" + ) + sys.stdout.flush() + elif isinstance(record.msg, TerminationEvent): sys.stdout.write( f"\n{'-'*75} \n" - f"\033[91m[{ts}], {record.msg.source.type}:\033[0m\n" - f"\nSelected next speaker: {record.msg.selected_speaker}" + f"\033[91m[{ts}], Termination:\033[0m\n" + f"\n{self.serialize_chat_message(record.msg.agent_message)}" ) sys.stdout.flush() else: raise ValueError(f"Unexpected log record: {record.msg}") -class FileLogHandler(BaseLogHandler): +class FileLogHandler(logging.Handler): def __init__(self, filename: str) -> None: super().__init__() self.filename = filename @@ -104,12 +76,12 @@ def __init__(self, filename: str) -> None: def emit(self, record: logging.LogRecord) -> None: ts = datetime.fromtimestamp(record.created).isoformat() - if isinstance(record.msg, ContentPublishEvent | ToolCallEvent | ToolCallResultEvent): + if isinstance(record.msg, ContentPublishEvent | ToolCallEvent | ToolCallResultEvent | TerminationEvent): log_entry = json.dumps( { "timestamp": ts, "source": record.msg.source, - "agent_message": self.serialize_content(record.msg.agent_message), + "agent_message": record.msg.agent_message.model_dump(), "type": record.msg.__class__.__name__, }, default=self.json_serializer, @@ -140,3 +112,11 @@ def emit(self, record: logging.LogRecord) -> None: def close(self) -> None: self.file_handler.close() super().close() + + @staticmethod + def json_serializer(obj: Any) -> Any: + if is_dataclass(obj) and not isinstance(obj, type): + return asdict(obj) + elif isinstance(obj, type): + return str(obj) + return str(obj) diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_termination.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_termination.py new file mode 100644 index 000000000000..78e20a93bbbb --- /dev/null +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/_termination.py @@ -0,0 +1,215 @@ +import asyncio +from abc import ABC, abstractmethod +from typing import List, Sequence + +from ..agents import ChatMessage, MultiModalMessage, StopMessage, TextMessage + + +class TerminatedException(BaseException): ... + + +class TerminationCondition(ABC): + """A stateful condition that determines when a conversation should be terminated. + + 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. + + Termination conditions can be combined using the AND and OR operators. + + Example: + + .. code-block:: python + + from autogen_agentchat.teams import MaxTurnsTermination, TextMentionTermination + + # Terminate the conversation after 10 turns or if the text "TERMINATE" is mentioned. + cond1 = MaxTurnsTermination(10) | TextMentionTermination("TERMINATE") + + # Terminate the conversation after 10 turns and if the text "TERMINATE" is mentioned. + cond2 = MaxTurnsTermination(10) & TextMentionTermination("TERMINATE") + + ... + + # Reset the termination condition. + await cond1.reset() + await cond2.reset() + """ + + @property + @abstractmethod + def terminated(self) -> bool: + """Check if the termination condition has been reached""" + ... + + @abstractmethod + async def __call__(self, messages: Sequence[ChatMessage]) -> StopMessage | None: + """Check if the conversation should be terminated based on the messages received + since the last time the condition was called. + Return a StopMessage if the conversation should be terminated, or None otherwise. + + Args: + messages: The messages received since the last time the condition was called. + + Returns: + StopMessage | None: A StopMessage if the conversation should be terminated, or None otherwise. + + Raises: + TerminatedException: If the termination condition has already been reached.""" + ... + + @abstractmethod + async def reset(self) -> None: + """Reset the termination condition.""" + ... + + def __and__(self, other: "TerminationCondition") -> "TerminationCondition": + """Combine two termination conditions with an AND operation.""" + return _AndTerminationCondition(self, other) + + def __or__(self, other: "TerminationCondition") -> "TerminationCondition": + """Combine two termination conditions with an OR operation.""" + return _OrTerminationCondition(self, other) + + +class _AndTerminationCondition(TerminationCondition): + def __init__(self, *conditions: TerminationCondition) -> None: + self._conditions = conditions + self._stop_messages: List[StopMessage] = [] + + @property + def terminated(self) -> bool: + return all(condition.terminated for condition in self._conditions) + + async def __call__(self, messages: Sequence[ChatMessage]) -> StopMessage | None: + if self.terminated: + raise TerminatedException("Termination condition has already been reached.") + # Check all remaining conditions. + stop_messages = await asyncio.gather( + *[condition(messages) for condition in self._conditions if not condition.terminated] + ) + # Collect stop messages. + for stop_message in stop_messages: + if stop_message is not None: + self._stop_messages.append(stop_message) + if any(stop_message is None for stop_message in stop_messages): + # If any remaining condition has not reached termination, it is not terminated. + return None + content = ", ".join(stop_message.content for stop_message in self._stop_messages) + source = ", ".join(stop_message.source for stop_message in self._stop_messages) + return StopMessage(content=content, source=source) + + async def reset(self) -> None: + for condition in self._conditions: + await condition.reset() + self._stop_messages.clear() + + +class _OrTerminationCondition(TerminationCondition): + def __init__(self, *conditions: TerminationCondition) -> None: + self._conditions = conditions + + @property + def terminated(self) -> bool: + return any(condition.terminated for condition in self._conditions) + + async def __call__(self, messages: Sequence[ChatMessage]) -> StopMessage | None: + if self.terminated: + raise RuntimeError("Termination condition has already been reached") + stop_messages = await asyncio.gather(*[condition(messages) for condition in self._conditions]) + if any(stop_message is not None for stop_message in stop_messages): + content = ", ".join(stop_message.content for stop_message in stop_messages if stop_message is not None) + source = ", ".join(stop_message.source for stop_message in stop_messages if stop_message is not None) + return StopMessage(content=content, source=source) + return 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/teams/group_chat/__init__.py b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/group_chat/__init__.py index 45b6f6bed6a4..e69de29bb2d1 100644 --- a/python/packages/autogen-agentchat/src/autogen_agentchat/teams/group_chat/__init__.py +++ b/python/packages/autogen-agentchat/src/autogen_agentchat/teams/group_chat/__init__.py @@ -1,4 +0,0 @@ -from ._round_robin_group_chat import RoundRobinGroupChat -from ._selector_group_chat import SelectorGroupChat - -__all__ = ["RoundRobinGroupChat", "SelectorGroupChat"] 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 30cad5adb038..633fc0f85242 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,11 +8,10 @@ from autogen_core.components.tool_agent import ToolAgent from autogen_core.components.tools import Tool -from autogen_agentchat.agents._base_chat_agent import ChatMessage - -from ...agents import BaseChatAgent, BaseToolUseChatAgent, TextMessage +from ...agents import BaseChatAgent, BaseToolUseChatAgent, ChatMessage, TextMessage from .._base_team import BaseTeam, TeamRunResult from .._events import ContentPublishEvent, ContentRequestEvent +from .._termination import TerminationCondition from ._base_chat_agent_container import BaseChatAgentContainer from ._base_group_chat_manager import BaseGroupChatManager @@ -45,6 +44,7 @@ def _create_group_chat_manager_factory( group_topic_type: str, participant_topic_types: List[str], participant_descriptions: List[str], + termination_condition: TerminationCondition | None, ) -> Callable[[], BaseGroupChatManager]: ... def _create_participant_factory( @@ -69,8 +69,10 @@ def _factory() -> ToolAgent: return _factory - async def run(self, task: str) -> TeamRunResult: + async def run(self, task: str, *, termination_condition: TerminationCondition | None = None) -> TeamRunResult: """Run the team and return the result.""" + # Create intervention handler for termination. + # Create the runtime. runtime = SingleThreadedAgentRuntime() @@ -122,6 +124,7 @@ async def run(self, task: str) -> TeamRunResult: group_topic_type=group_topic_type, participant_topic_types=participant_topic_types, participant_descriptions=participant_descriptions, + termination_condition=termination_condition, ), ) # Add subscriptions for the group chat manager. @@ -147,7 +150,7 @@ async def collect_group_chat_messages( type="collect_group_chat_messages", closure=collect_group_chat_messages, subscriptions=lambda: [ - TypeSubscription(topic_type=group_topic_type, agent_type="collect_group_chat_messages") + TypeSubscription(topic_type=group_topic_type, agent_type="collect_group_chat_messages"), ], ) @@ -166,4 +169,5 @@ async def collect_group_chat_messages( # Wait for the runtime to stop. await runtime.stop_when_idle() + # Return the result. return TeamRunResult(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 9ad2d2fd25b2..19d29e008fc7 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 @@ -5,9 +5,9 @@ from autogen_core.base import MessageContext, TopicId from autogen_core.components import event -from ...agents import StopMessage, TextMessage -from .._events import ContentPublishEvent, ContentRequestEvent +from .._events import ContentPublishEvent, ContentRequestEvent, TerminationEvent from .._logging import EVENT_LOGGER_NAME +from .._termination import TerminationCondition from ._sequential_routed_agent import SequentialRoutedAgent event_logger = logging.getLogger(EVENT_LOGGER_NAME) @@ -29,6 +29,7 @@ class BaseGroupChatManager(SequentialRoutedAgent, ABC): group_topic_type (str): The topic type of the group chat. participant_topic_types (List[str]): The topic types of the participants. participant_descriptions (List[str]): The descriptions of the participants + termination_condition (TerminationCondition, optional): The termination condition for the group chat. Defaults to None. Raises: ValueError: If the number of participant topic types, agent types, and descriptions are not the same. @@ -40,6 +41,7 @@ def __init__( group_topic_type: str, participant_topic_types: List[str], participant_descriptions: List[str], + termination_condition: TerminationCondition | None = None, ): super().__init__(description="Group chat manager") self._parent_topic_type = parent_topic_type @@ -57,6 +59,7 @@ def __init__( self._participant_topic_types = participant_topic_types self._participant_descriptions = participant_descriptions self._message_thread: List[ContentPublishEvent] = [] + self._termination_condition = termination_condition @event async def handle_content_publish(self, message: ContentPublishEvent, ctx: MessageContext) -> None: @@ -74,24 +77,25 @@ async def handle_content_publish(self, message: ContentPublishEvent, ctx: Messag # Process event from parent. if ctx.topic_id.type == self._parent_topic_type: self._message_thread.append(message) - await self.publish_message(message, topic_id=group_chat_topic_id) + await self.publish_message( + ContentPublishEvent(agent_message=message.agent_message, source=self.id), topic_id=group_chat_topic_id + ) return # Process event from the group chat this agent manages. assert ctx.topic_id.type == self._group_topic_type self._message_thread.append(message) - # If the message is a stop message, publish the last message as a TextMessage to the parent topic. - # TODO: custom handling the final message. - if isinstance(message.agent_message, StopMessage): - parent_topic_id = TopicId(type=self._parent_topic_type, source=ctx.topic_id.source) - await self.publish_message( - ContentPublishEvent( - agent_message=TextMessage(content=message.agent_message.content, source=self.metadata["type"]) - ), - topic_id=parent_topic_id, - ) - return + # Check if the conversation should be terminated. + if self._termination_condition is not None: + stop_message = await self._termination_condition([message.agent_message]) + if stop_message is not None: + event_logger.info(TerminationEvent(agent_message=stop_message, source=self.id)) + # Reset the termination condition. + await self._termination_condition.reset() + # Stop the group chat. + # TODO: this should be different if the group chat is nested. + return # Select a speaker to continue the conversation. speaker_topic_type = await self.select_speaker(self._message_thread) 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 b953e54607b0..6ebacaac34d9 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 @@ -2,6 +2,7 @@ from ...agents import BaseChatAgent from .._events import ContentPublishEvent +from .._termination import TerminationCondition from ._base_group_chat import BaseGroupChat from ._base_group_chat_manager import BaseGroupChatManager @@ -15,12 +16,14 @@ def __init__( group_topic_type: str, participant_topic_types: List[str], participant_descriptions: List[str], + termination_condition: TerminationCondition | None, ) -> None: super().__init__( parent_topic_type, group_topic_type, participant_topic_types, participant_descriptions, + termination_condition, ) self._next_speaker_index = 0 @@ -51,23 +54,23 @@ class RoundRobinGroupChat(BaseGroupChat): .. code-block:: python from autogen_agentchat.agents import ToolUseAssistantAgent - from autogen_agentchat.teams.group_chat import RoundRobinGroupChat + from autogen_agentchat.teams import RoundRobinGroupChat, StopMessageTermination assistant = ToolUseAssistantAgent("Assistant", model_client=..., registered_tools=...) team = RoundRobinGroupChat([assistant]) - await team.run("What's the weather in New York?") + await team.run("What's the weather in New York?", termination_condition=StopMessageTermination()) A team with multiple participants: .. code-block:: python from autogen_agentchat.agents import CodingAssistantAgent, CodeExecutorAgent - from autogen_agentchat.teams.group_chat import RoundRobinGroupChat + from autogen_agentchat.teams import RoundRobinGroupChat, StopMessageTermination coding_assistant = CodingAssistantAgent("Coding_Assistant", model_client=...) executor_agent = CodeExecutorAgent("Code_Executor", code_executor=...) team = RoundRobinGroupChat([coding_assistant, executor_agent]) - await team.run("Write a program that prints 'Hello, world!'") + await team.run("Write a program that prints 'Hello, world!'", termination_condition=StopMessageTermination()) """ @@ -80,10 +83,15 @@ def _create_group_chat_manager_factory( group_topic_type: str, participant_topic_types: List[str], participant_descriptions: List[str], + termination_condition: TerminationCondition | None, ) -> Callable[[], RoundRobinGroupChatManager]: def _factory() -> RoundRobinGroupChatManager: return RoundRobinGroupChatManager( - parent_topic_type, group_topic_type, participant_topic_types, participant_descriptions + parent_topic_type, + group_topic_type, + participant_topic_types, + participant_descriptions, + termination_condition, ) return _factory 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 66724b445277..461c7882056f 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 ...agents import BaseChatAgent, MultiModalMessage, StopMessage, TextMessage from .._events import ContentPublishEvent, SelectSpeakerEvent from .._logging import EVENT_LOGGER_NAME, TRACE_LOGGER_NAME +from .._termination import TerminationCondition from ._base_group_chat import BaseGroupChat from ._base_group_chat_manager import BaseGroupChatManager @@ -24,6 +25,7 @@ def __init__( group_topic_type: str, participant_topic_types: List[str], participant_descriptions: List[str], + termination_condition: TerminationCondition | None, model_client: ChatCompletionClient, selector_prompt: str, allow_repeated_speaker: bool, @@ -33,6 +35,7 @@ def __init__( group_topic_type, participant_topic_types, participant_descriptions, + termination_condition, ) self._model_client = model_client self._selector_prompt = selector_prompt @@ -164,13 +167,13 @@ class SelectorGroupChat(BaseGroupChat): .. code-block:: python from autogen_agentchat.agents import ToolUseAssistantAgent - from autogen_agentchat.teams.group_chat import SelectorGroupChat + from autogen_agentchat.teams import SelectorGroupChat, StopMessageTermination travel_advisor = ToolUseAssistantAgent("Travel_Advisor", model_client=..., registered_tools=...) hotel_agent = ToolUseAssistantAgent("Hotel_Agent", model_client=..., registered_tools=...) flight_agent = ToolUseAssistantAgent("Flight_Agent", model_client=..., registered_tools=...) team = SelectorGroupChat([travel_advisor, hotel_agent, flight_agent], model_client=...) - await team.run("Book a 3-day trip to new york.") + await team.run("Book a 3-day trip to new york.", termination_condition=StopMessageTermination()) """ def __init__( @@ -209,12 +212,14 @@ def _create_group_chat_manager_factory( group_topic_type: str, participant_topic_types: List[str], participant_descriptions: List[str], + termination_condition: TerminationCondition | None, ) -> Callable[[], BaseGroupChatManager]: return lambda: SelectorGroupChatManager( parent_topic_type, group_topic_type, participant_topic_types, participant_descriptions, + termination_condition, self._model_client, self._selector_prompt, self._allow_repeated_speaker, diff --git a/python/packages/autogen-agentchat/tests/test_group_chat.py b/python/packages/autogen-agentchat/tests/test_group_chat.py index 7fabe8253436..455ea09a1282 100644 --- a/python/packages/autogen-agentchat/tests/test_group_chat.py +++ b/python/packages/autogen-agentchat/tests/test_group_chat.py @@ -1,5 +1,6 @@ import asyncio import json +import logging import tempfile from typing import Any, AsyncGenerator, List, Sequence @@ -13,7 +14,13 @@ TextMessage, ToolUseAssistantAgent, ) -from autogen_agentchat.teams.group_chat import RoundRobinGroupChat, SelectorGroupChat +from autogen_agentchat.teams import ( + EVENT_LOGGER_NAME, + FileLogHandler, + RoundRobinGroupChat, + SelectorGroupChat, + StopMessageTermination, +) from autogen_core.base import CancellationToken from autogen_core.components import FunctionCall from autogen_core.components.code_executor import LocalCommandLineCodeExecutor @@ -26,6 +33,10 @@ from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall, Function from openai.types.completion_usage import CompletionUsage +logger = logging.getLogger(EVENT_LOGGER_NAME) +logger.setLevel(logging.DEBUG) +logger.addHandler(FileLogHandler("test_group_chat.log")) + class _MockChatCompletion: def __init__(self, chat_completions: List[ChatCompletion]) -> None: @@ -119,7 +130,9 @@ async def test_round_robin_group_chat(monkeypatch: pytest.MonkeyPatch) -> None: "coding_assistant", model_client=OpenAIChatCompletionClient(model=model, api_key="") ) team = RoundRobinGroupChat(participants=[coding_assistant_agent, code_executor_agent]) - result = await team.run("Write a program that prints 'Hello, world!'") + result = await team.run( + "Write a program that prints 'Hello, world!'", termination_condition=StopMessageTermination() + ) expected_messages = [ "Write a program that prints 'Hello, world!'", 'Here is the program\n ```python\nprint("Hello, world!")\n```', @@ -200,7 +213,7 @@ async def test_round_robin_group_chat_with_tools(monkeypatch: pytest.MonkeyPatch ) echo_agent = _EchoAgent("echo_agent", description="echo agent") team = RoundRobinGroupChat(participants=[tool_use_agent, echo_agent]) - await team.run("Write a program that prints 'Hello, world!'") + await team.run("Write a program that prints 'Hello, world!'", termination_condition=StopMessageTermination()) context = tool_use_agent._model_context # pyright: ignore assert context[0].content == "Write a program that prints 'Hello, world!'" assert isinstance(context[1].content, list) @@ -279,7 +292,9 @@ async def test_selector_group_chat(monkeypatch: pytest.MonkeyPatch) -> None: participants=[agent1, agent2, agent3], model_client=OpenAIChatCompletionClient(model=model, api_key=""), ) - result = await team.run("Write a program that prints 'Hello, world!'") + result = await team.run( + "Write a program that prints 'Hello, world!'", termination_condition=StopMessageTermination() + ) assert len(result.messages) == 6 assert result.messages[0].content == "Write a program that prints 'Hello, world!'" assert result.messages[1].source == "agent3" @@ -313,7 +328,9 @@ async def test_selector_group_chat_two_speakers(monkeypatch: pytest.MonkeyPatch) participants=[agent1, agent2], model_client=OpenAIChatCompletionClient(model=model, api_key=""), ) - result = await team.run("Write a program that prints 'Hello, world!'") + result = await team.run( + "Write a program that prints 'Hello, world!'", termination_condition=StopMessageTermination() + ) assert len(result.messages) == 5 assert result.messages[0].content == "Write a program that prints 'Hello, world!'" assert result.messages[1].source == "agent2" @@ -369,7 +386,9 @@ async def test_selector_group_chat_two_speakers_allow_repeated(monkeypatch: pyte model_client=OpenAIChatCompletionClient(model=model, api_key=""), allow_repeated_speaker=True, ) - result = await team.run("Write a program that prints 'Hello, world!'") + result = await team.run( + "Write a program that prints 'Hello, world!'", termination_condition=StopMessageTermination() + ) assert len(result.messages) == 4 assert result.messages[0].content == "Write a program that prints 'Hello, world!'" assert result.messages[1].source == "agent2" diff --git a/python/packages/autogen-agentchat/tests/test_termination_condition.py b/python/packages/autogen-agentchat/tests/test_termination_condition.py new file mode 100644 index 000000000000..fe850542f264 --- /dev/null +++ b/python/packages/autogen-agentchat/tests/test_termination_condition.py @@ -0,0 +1,126 @@ +import pytest +from autogen_agentchat.agents import StopMessage, TextMessage +from autogen_agentchat.teams import MaxMessageTermination, StopMessageTermination, TextMentionTermination + + +@pytest.mark.asyncio +async def test_stop_message_termination() -> None: + termination = StopMessageTermination() + assert await termination([]) is None + await termination.reset() + assert await termination([TextMessage(content="Hello", source="user")]) is None + await termination.reset() + assert await termination([StopMessage(content="Stop", source="user")]) is not None + await termination.reset() + assert ( + await termination([TextMessage(content="Hello", source="user"), TextMessage(content="World", source="agent")]) + is None + ) + await termination.reset() + assert ( + await termination([TextMessage(content="Hello", source="user"), StopMessage(content="Stop", source="user")]) + is not None + ) + + +@pytest.mark.asyncio +async def test_max_message_termination() -> None: + termination = MaxMessageTermination(2) + assert await termination([]) is None + await termination.reset() + assert await termination([TextMessage(content="Hello", source="user")]) is None + await termination.reset() + assert ( + await termination([TextMessage(content="Hello", source="user"), TextMessage(content="World", source="agent")]) + is not None + ) + + +@pytest.mark.asyncio +async def test_mention_termination() -> None: + termination = TextMentionTermination("stop") + assert await termination([]) is None + await termination.reset() + assert await termination([TextMessage(content="Hello", source="user")]) is None + await termination.reset() + assert await termination([TextMessage(content="stop", source="user")]) is not None + await termination.reset() + assert ( + await termination([TextMessage(content="Hello", source="user"), TextMessage(content="stop", source="user")]) + is not None + ) + + +@pytest.mark.asyncio +async def test_and_termination() -> None: + termination = MaxMessageTermination(2) & TextMentionTermination("stop") + assert await termination([]) is None + await termination.reset() + assert await termination([TextMessage(content="Hello", source="user")]) is None + await termination.reset() + assert ( + await termination([TextMessage(content="Hello", source="user"), TextMessage(content="World", source="agent")]) + is None + ) + await termination.reset() + assert ( + await termination([TextMessage(content="Hello", source="user"), TextMessage(content="stop", source="user")]) + is not None + ) + + +@pytest.mark.asyncio +async def test_or_termination() -> None: + termination = MaxMessageTermination(3) | TextMentionTermination("stop") + assert await termination([]) is None + await termination.reset() + assert await termination([TextMessage(content="Hello", source="user")]) is None + await termination.reset() + assert ( + await termination([TextMessage(content="Hello", source="user"), TextMessage(content="World", source="agent")]) + is None + ) + await termination.reset() + assert ( + await termination([TextMessage(content="Hello", source="user"), TextMessage(content="stop", source="user")]) + is not None + ) + await termination.reset() + assert ( + await termination([TextMessage(content="Hello", source="user"), TextMessage(content="Hello", source="user")]) + is None + ) + await termination.reset() + assert ( + await termination( + [ + TextMessage(content="Hello", source="user"), + TextMessage(content="Hello", source="user"), + TextMessage(content="Hello", source="user"), + ] + ) + is not None + ) + await termination.reset() + assert ( + await termination( + [ + TextMessage(content="Hello", source="user"), + TextMessage(content="Hello", source="user"), + TextMessage(content="stop", source="user"), + ] + ) + is not None + ) + await termination.reset() + assert ( + await termination( + [ + TextMessage(content="Hello", source="user"), + TextMessage(content="Hello", source="user"), + TextMessage(content="Hello", source="user"), + TextMessage(content="stop", source="user"), + ] + ) + is not None + ) diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/examples/company-research.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/examples/company-research.ipynb index 1e364983f784..6d051f472406 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/examples/company-research.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/examples/company-research.ipynb @@ -23,7 +23,7 @@ "outputs": [], "source": [ "from autogen_agentchat.agents import CodingAssistantAgent, ToolUseAssistantAgent\n", - "from autogen_agentchat.teams.group_chat import RoundRobinGroupChat\n", + "from autogen_agentchat.teams import RoundRobinGroupChat, StopMessageTermination\n", "from autogen_core.components.models import OpenAIChatCompletionClient\n", "from autogen_core.components.tools import FunctionTool" ] @@ -63,10 +63,11 @@ "\n", "def google_search(query: str, num_results: int = 2, max_chars: int = 500) -> list: # type: ignore[type-arg]\n", " import os\n", + " import time\n", + "\n", " import requests\n", - " from dotenv import load_dotenv\n", " from bs4 import BeautifulSoup\n", - " import time\n", + " from dotenv import load_dotenv\n", "\n", " load_dotenv()\n", "\n", @@ -115,13 +116,14 @@ "\n", "\n", "def analyze_stock(ticker: str) -> dict: # type: ignore[type-arg]\n", - " import yfinance as yf\n", - " import matplotlib.pyplot as plt\n", + " import os\n", " from datetime import datetime, timedelta\n", + "\n", + " import matplotlib.pyplot as plt\n", " import numpy as np\n", - " from pytz import timezone # type: ignore\n", " import pandas as pd\n", - " import os\n", + " import yfinance as yf\n", + " from pytz import timezone # type: ignore\n", "\n", " stock = yf.Ticker(ticker)\n", "\n", @@ -397,14 +399,14 @@ } ], "source": [ - "result = await team.run(\"Write a financial report on American airlines\")\n", + "result = await team.run(\"Write a financial report on American airlines\", termination_condition=StopMessageTermination())\n", "print(result)" ] } ], "metadata": { "kernelspec": { - "display_name": "agnext", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -418,7 +420,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.6" } }, "nbformat": 4, diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/examples/literature-review.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/examples/literature-review.ipynb index 7395cac25b91..b28f523f7401 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/examples/literature-review.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/examples/literature-review.ipynb @@ -23,7 +23,7 @@ "outputs": [], "source": [ "from autogen_agentchat.agents import CodingAssistantAgent, ToolUseAssistantAgent\n", - "from autogen_agentchat.teams.group_chat import RoundRobinGroupChat\n", + "from autogen_agentchat.teams import RoundRobinGroupChat, StopMessageTermination\n", "from autogen_core.components.models import OpenAIChatCompletionClient\n", "from autogen_core.components.tools import FunctionTool" ] @@ -55,10 +55,11 @@ "source": [ "def google_search(query: str, num_results: int = 2, max_chars: int = 500) -> list: # type: ignore[type-arg]\n", " import os\n", + " import time\n", + "\n", " import requests\n", - " from dotenv import load_dotenv\n", " from bs4 import BeautifulSoup\n", - " import time\n", + " from dotenv import load_dotenv\n", "\n", " load_dotenv()\n", "\n", @@ -328,14 +329,16 @@ "\n", "team = RoundRobinGroupChat(participants=[google_search_agent, arxiv_search_agent, report_agent])\n", "\n", - "result = await team.run(task=\"Write a literature review on no code tools for building multi agent ai systems\")\n", - "result" + "result = await team.run(\n", + " task=\"Write a literature review on no code tools for building multi agent ai systems\",\n", + " termination_condition=StopMessageTermination(),\n", + ")" ] } ], "metadata": { "kernelspec": { - "display_name": "agnext", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -349,7 +352,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.6" } }, "nbformat": 4, diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/examples/travel-planning.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/examples/travel-planning.ipynb index 3ff954b25565..da6775400579 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/examples/travel-planning.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/examples/travel-planning.ipynb @@ -18,7 +18,7 @@ "outputs": [], "source": [ "from autogen_agentchat.agents import CodingAssistantAgent\n", - "from autogen_agentchat.teams.group_chat import RoundRobinGroupChat\n", + "from autogen_agentchat.teams import RoundRobinGroupChat, StopMessageTermination\n", "from autogen_core.components.models import OpenAIChatCompletionClient" ] }, @@ -194,14 +194,14 @@ ], "source": [ "group_chat = RoundRobinGroupChat([planner_agent, local_agent, language_agent, travel_summary_agent])\n", - "result = await group_chat.run(task=\"Plan a 3 day trip to Nepal.\")\n", + "result = await group_chat.run(task=\"Plan a 3 day trip to Nepal.\", termination_condition=StopMessageTermination())\n", "print(result)" ] } ], "metadata": { "kernelspec": { - "display_name": "agnext", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -215,7 +215,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.6" } }, "nbformat": 4, diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/code-execution.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/code-execution.ipynb index 845a70cabe9b..a9334e2b0df3 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/code-execution.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/code-execution.ipynb @@ -314,7 +314,7 @@ ], "source": [ "from autogen_agentchat.agents import CodeExecutorAgent, CodingAssistantAgent\n", - "from autogen_agentchat.teams.group_chat import RoundRobinGroupChat\n", + "from autogen_agentchat.teams import RoundRobinGroupChat, StopMessageTermination\n", "from autogen_core.components.code_executor import DockerCommandLineCodeExecutor\n", "from autogen_core.components.models import OpenAIChatCompletionClient\n", "\n", @@ -325,7 +325,8 @@ " )\n", " group_chat = RoundRobinGroupChat([coding_assistant_agent, code_executor_agent])\n", " result = await group_chat.run(\n", - " task=\"Create a plot of NVDIA and TSLA stock returns YTD from 2024-01-01 and save it to 'nvidia_tesla_2024_ytd.png'.\"\n", + " task=\"Create a plot of NVDIA and TSLA stock returns YTD from 2024-01-01 and save it to 'nvidia_tesla_2024_ytd.png'.\",\n", + " termination_condition=StopMessageTermination(),\n", " )\n", " print(result)" ] @@ -356,7 +357,7 @@ ], "metadata": { "kernelspec": { - "display_name": "agnext", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -370,7 +371,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.6" } }, "nbformat": 4, diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/selector-group-chat.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/selector-group-chat.ipynb index cc8bd6c26aaf..ed2a4bf8ecd6 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/selector-group-chat.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/selector-group-chat.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -24,7 +24,7 @@ " TextMessage,\n", " ToolUseAssistantAgent,\n", ")\n", - "from autogen_agentchat.teams.group_chat import SelectorGroupChat\n", + "from autogen_agentchat.teams import SelectorGroupChat, StopMessageTermination\n", "from autogen_core.base import CancellationToken\n", "from autogen_core.components.models import OpenAIChatCompletionClient\n", "from autogen_core.components.tools import FunctionTool" @@ -32,7 +32,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -49,7 +49,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -69,7 +69,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -78,206 +78,113 @@ "text": [ "\n", "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:10:50.523469]:\u001b[0m\n", + "\u001b[91m[2024-10-08T20:35:30.283450]:\u001b[0m\n", "\n", - "Help user plan a trip and book a flight.\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:10:51.234858], group_chat_manager:\u001b[0m\n", + "Help user plan a trip and book a flight." + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "\n", - "Selected next speaker: User\n", "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:10:55.437051], User:\u001b[0m\n", + "\u001b[91m[2024-10-08T20:35:48.275743], User:\u001b[0m\n", "\n", "\n", "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:10:55.957366], group_chat_manager:\u001b[0m\n", + "\u001b[91m[2024-10-08T20:35:50.795496], TravelAssistant:\u001b[0m\n", "\n", - "Selected next speaker: TravelAssistant\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:10:58.291558], TravelAssistant:\u001b[0m\n", - "\n", - "Sure! I can help you plan your trip and provide information on booking a flight. Could you please provide me with the following details?\n", - "\n", - "1. Your departure city.\n", - "2. Your destination.\n", - "3. Travel dates (departure and return).\n", - "4. Number of travelers and their ages.\n", - "5. Any specific preferences or activities you would like to include in your trip?\n", + "I'd be happy to help you plan your trip! To get started, could you please provide me with the following details:\n", "\n", - "Once I have that information, we can get started!\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:10:58.827503], group_chat_manager:\u001b[0m\n", - "\n", - "Selected next speaker: User\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:11:07.996036], User:\u001b[0m\n", + "1. Your departure city and the destination city.\n", + "2. Your travel dates (departure and return).\n", + "3. The number of travelers and their ages (if any children are involved).\n", + "4. Your budget for flights and accommodations, if you have one in mind.\n", + "5. Any specific activities or attractions you're interested in at the destination.\n", "\n", - "Going to toronto from new york \n", + "Once I have this information, I can help you find the best options!\n", "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:11:08.623692], group_chat_manager:\u001b[0m\n", + "\u001b[91m[2024-10-08T20:35:59.701486], User:\u001b[0m\n", "\n", - "Selected next speaker: TravelAssistant\n", + "Traveling to toronto from new york\n", "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:11:10.605232], TravelAssistant:\u001b[0m\n", + "\u001b[91m[2024-10-08T20:36:02.325330], TravelAssistant:\u001b[0m\n", "\n", - "Great! Here are a few more details I need to help you plan your trip:\n", + "Great choice! Toronto is a vibrant city with a lot to offer. Now, could you please provide the following additional details to help me assist you better?\n", "\n", - "1. **Departure dates:** When do you plan to leave New York and when will you return?\n", - "2. **Number of travelers:** How many people will be traveling with you, and what are their ages?\n", - "3. **Preferences:** Do you have any specific preferences for flight times or activities in Toronto? \n", + "1. What are your travel dates (departure and return)?\n", + "2. How many travelers will be going, and what are their ages?\n", + "3. Do you have a budget for the flight and accommodations?\n", + "4. Are there any specific activities or attractions you’re interested in while in Toronto?\n", "\n", - "Once I have this information, I can assist you further!\n", + "Once I have this information, I can help you find the best flights and suggestions for your trip!\n", "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:11:11.070495], group_chat_manager:\u001b[0m\n", + "\u001b[91m[2024-10-08T20:36:20.633004], User:\u001b[0m\n", "\n", - "Selected next speaker: User\n", + "leaving on december 7 and returning on 12\n", "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:11:26.768126], User:\u001b[0m\n", + "\u001b[91m[2024-10-08T20:36:23.202871], TravelAssistant:\u001b[0m\n", "\n", - "leaving on december 12 and returning on 17, 2024\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:11:27.365051], group_chat_manager:\u001b[0m\n", - "\n", - "Selected next speaker: TravelAssistant\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:11:30.335893], TravelAssistant:\u001b[0m\n", - "\n", - "Thank you for the details! Here’s a summary of the trip so far:\n", + "Thank you for the details! Here's what I have so far:\n", "\n", "- **Departure City:** New York\n", - "- **Destination:** Toronto\n", - "- **Departure Date:** December 12, 2024\n", - "- **Return Date:** December 17, 2024\n", + "- **Destination City:** Toronto\n", + "- **Departure Date:** December 7\n", + "- **Return Date:** December 12\n", "\n", - "Now, could you please provide the following additional information?\n", + "Now, could you please provide:\n", "\n", - "1. **Number of travelers and their ages:** How many people will be traveling with you?\n", - "2. **Preferences for flights:** Any preferences for morning, afternoon, or evening flights?\n", - "3. **Activities:** Any specific activities or attractions you’d like to prioritize in Toronto?\n", + "1. The number of travelers and their ages.\n", + "2. Your budget for flights and accommodations (if applicable).\n", + "3. Any specific activities or attractions you're interested in while in Toronto.\n", "\n", - "With this information, I can assist you in finding suitable flights and offer activity suggestions!\n", + "This will help me provide more tailored options for your trip!\n", "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:11:30.822862], group_chat_manager:\u001b[0m\n", + "\u001b[91m[2024-10-08T20:36:38.096554], User:\u001b[0m\n", "\n", - "Selected next speaker: User\n", + "just myself one adult\n", "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:11:39.547965], User:\u001b[0m\n", + "\u001b[91m[2024-10-08T20:36:40.307824], FlightBroker:\u001b[0m\n", "\n", - "just myself\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:11:40.110527], group_chat_manager:\u001b[0m\n", - "\n", - "Selected next speaker: FlightBroker\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:11:45.764773], FlightBroker:\u001b[0m\n", - "\n", - "Thank you for the information! Here’s what I have so far for your trip:\n", + "Thanks for the information! Here's what I have:\n", "\n", "- **Departure City:** New York\n", - "- **Destination:** Toronto\n", - "- **Departure Date:** December 12, 2024\n", - "- **Return Date:** December 17, 2024\n", - "- **Number of Travelers:** 1 (yourself)\n", - "\n", - "Now, do you have any preferences for flight times (morning, afternoon, or evening)? Additionally, are there any specific activities or attractions you would like to include in your trip to Toronto? \n", + "- **Destination City:** Toronto\n", + "- **Departure Date:** December 7\n", + "- **Return Date:** December 12\n", + "- **Number of Travelers:** 1 Adult\n", "\n", - "Once I have that, I can start searching for flights!\n", + "Could you let me know if you have a budget for flights and accommodations? Additionally, are there any specific activities or attractions you're interested in while in Toronto? This will help me provide the best options for your trip!\n", "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:11:46.363784], group_chat_manager:\u001b[0m\n", + "\u001b[91m[2024-10-08T20:36:45.875280], User:\u001b[0m\n", "\n", - "Selected next speaker: User\n", + "that's it\n", "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:11:59.508971], User:\u001b[0m\n", + "\u001b[91m[2024-10-08T20:36:50.925624], FlightBroker:\u001b[0m\n", "\n", - "any time is fine\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:12:00.072654], group_chat_manager:\u001b[0m\n", + "Your flights have been successfully booked! Here are the details:\n", "\n", - "Selected next speaker: FlightBroker\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:12:01.847222], FlightBroker:\u001b[0m\n", + "- **Departure:** New York to Toronto\n", + " - **Flight:** AL21\n", + " - **Date:** December 7, 2023\n", "\n", - "[FunctionCall(id='call_lpuPUlo9k3p4VeX0h6jO8yJg', arguments='{\"start\":\"New York\",\"destination\":\"Toronto\",\"date\":\"2024-12-12\"}', name='flight_search')]\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:12:01.848179], tool_agent_for_FlightBroker:\u001b[0m\n", + "- **Return:** Toronto to New York\n", + " - **Flight:** AL21\n", + " - **Date:** December 12, 2023\n", "\n", - "[FunctionExecutionResult(content='AC24 from New York to Toronto on 2024-12-12 is $500\\nUA23 from New York to Toronto on 2024-12-12 is $450\\nAL21 from New York to Toronto on 2024-12-12 is $400', call_id='call_lpuPUlo9k3p4VeX0h6jO8yJg')]\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:12:03.512522], FlightBroker:\u001b[0m\n", - "\n", - "[FunctionCall(id='call_dxxmiR6hVBL9QuneJGNnleR0', arguments='{\"start\":\"Toronto\",\"destination\":\"New York\",\"date\":\"2024-12-17\"}', name='flight_search')]\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:12:03.513405], tool_agent_for_FlightBroker:\u001b[0m\n", - "\n", - "[FunctionExecutionResult(content='AC24 from Toronto to New York on 2024-12-17 is $500\\nUA23 from Toronto to New York on 2024-12-17 is $450\\nAL21 from Toronto to New York on 2024-12-17 is $400', call_id='call_dxxmiR6hVBL9QuneJGNnleR0')]\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:12:07.337638], FlightBroker:\u001b[0m\n", - "\n", - "I found some flight options for your trip from New York to Toronto and back. Here are the details:\n", - "\n", - "### Departure: New York to Toronto on December 12, 2024\n", - "1. **Flight AC24**\n", - " - Price: $500\n", - "2. **Flight UA23**\n", - " - Price: $450\n", - "3. **Flight AL21**\n", - " - Price: $400\n", - "\n", - "### Return: Toronto to New York on December 17, 2024\n", - "1. **Flight AC24**\n", - " - Price: $500\n", - "2. **Flight UA23**\n", - " - Price: $450\n", - "3. **Flight AL21**\n", - " - Price: $400\n", - "\n", - "Would you like to book any of these flights? If so, please let me know which flight you'd prefer for both the departure and return!\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:12:07.870903], group_chat_manager:\u001b[0m\n", - "\n", - "Selected next speaker: User\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:12:22.910467], User:\u001b[0m\n", - "\n", - "Book the AC flight leaving and UA flight returning\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:12:23.450871], group_chat_manager:\u001b[0m\n", - "\n", - "Selected next speaker: FlightBroker\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:12:27.273108], FlightBroker:\u001b[0m\n", - "\n", - "[FunctionCall(id='call_wqkaIBdYjWklWG0GQkYz7FZ0', arguments='{\"flight\": \"AC24\", \"date\": \"2024-12-12\"}', name='flight_booking'), FunctionCall(id='call_QZKtPHpbq2QzNi5y6OgfTcsd', arguments='{\"flight\": \"UA23\", \"date\": \"2024-12-17\"}', name='flight_booking')]\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:12:27.274111], tool_agent_for_FlightBroker:\u001b[0m\n", - "\n", - "[FunctionExecutionResult(content='Booked flight AC24 on 2024-12-12', call_id='call_wqkaIBdYjWklWG0GQkYz7FZ0'), FunctionExecutionResult(content='Booked flight UA23 on 2024-12-17', call_id='call_QZKtPHpbq2QzNi5y6OgfTcsd')]\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:12:29.585911], FlightBroker:\u001b[0m\n", - "\n", - "Your flights have been successfully booked!\n", - "\n", - "- **Departure Flight:** AC24 from New York to Toronto on December 12, 2024.\n", - "- **Return Flight:** UA23 from Toronto to New York on December 17, 2024.\n", - "\n", - "If you need any further assistance with your trip or have any questions about activities in Toronto, feel free to ask! Safe travels!\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:12:30.242282], group_chat_manager:\u001b[0m\n", - "\n", - "Selected next speaker: User\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T10:12:39.715695], User:\u001b[0m\n", + "If you need help with accommodations, activities, or anything else for your trip, feel free to let me know! \n", "\n", - "User has terminated the conversation." + "TERMINATE" ] }, { "data": { "text/plain": [ - "TeamRunResult(messages=[TextMessage(source='user', content='Help user plan a trip and book a flight.'), TextMessage(source='User', content=''), TextMessage(source='TravelAssistant', content='Sure! I can help you plan your trip and provide information on booking a flight. Could you please provide me with the following details?\\n\\n1. Your departure city.\\n2. Your destination.\\n3. Travel dates (departure and return).\\n4. Number of travelers and their ages.\\n5. Any specific preferences or activities you would like to include in your trip?\\n\\nOnce I have that information, we can get started!'), TextMessage(source='User', content='Going to toronto from new york '), TextMessage(source='TravelAssistant', content='Great! Here are a few more details I need to help you plan your trip:\\n\\n1. **Departure dates:** When do you plan to leave New York and when will you return?\\n2. **Number of travelers:** How many people will be traveling with you, and what are their ages?\\n3. **Preferences:** Do you have any specific preferences for flight times or activities in Toronto? \\n\\nOnce I have this information, I can assist you further!'), TextMessage(source='User', content='leaving on december 12 and returning on 17, 2024'), TextMessage(source='TravelAssistant', content='Thank you for the details! Here’s a summary of the trip so far:\\n\\n- **Departure City:** New York\\n- **Destination:** Toronto\\n- **Departure Date:** December 12, 2024\\n- **Return Date:** December 17, 2024\\n\\nNow, could you please provide the following additional information?\\n\\n1. **Number of travelers and their ages:** How many people will be traveling with you?\\n2. **Preferences for flights:** Any preferences for morning, afternoon, or evening flights?\\n3. **Activities:** Any specific activities or attractions you’d like to prioritize in Toronto?\\n\\nWith this information, I can assist you in finding suitable flights and offer activity suggestions!'), TextMessage(source='User', content='just myself'), TextMessage(source='FlightBroker', content='Thank you for the information! Here’s what I have so far for your trip:\\n\\n- **Departure City:** New York\\n- **Destination:** Toronto\\n- **Departure Date:** December 12, 2024\\n- **Return Date:** December 17, 2024\\n- **Number of Travelers:** 1 (yourself)\\n\\nNow, do you have any preferences for flight times (morning, afternoon, or evening)? Additionally, are there any specific activities or attractions you would like to include in your trip to Toronto? \\n\\nOnce I have that, I can start searching for flights!'), TextMessage(source='User', content='any time is fine'), TextMessage(source='FlightBroker', content=\"I found some flight options for your trip from New York to Toronto and back. Here are the details:\\n\\n### Departure: New York to Toronto on December 12, 2024\\n1. **Flight AC24**\\n - Price: $500\\n2. **Flight UA23**\\n - Price: $450\\n3. **Flight AL21**\\n - Price: $400\\n\\n### Return: Toronto to New York on December 17, 2024\\n1. **Flight AC24**\\n - Price: $500\\n2. **Flight UA23**\\n - Price: $450\\n3. **Flight AL21**\\n - Price: $400\\n\\nWould you like to book any of these flights? If so, please let me know which flight you'd prefer for both the departure and return!\"), TextMessage(source='User', content='Book the AC flight leaving and UA flight returning'), TextMessage(source='FlightBroker', content='Your flights have been successfully booked!\\n\\n- **Departure Flight:** AC24 from New York to Toronto on December 12, 2024.\\n- **Return Flight:** UA23 from Toronto to New York on December 17, 2024.\\n\\nIf you need any further assistance with your trip or have any questions about activities in Toronto, feel free to ask! Safe travels!'), StopMessage(source='User', content='User has terminated the conversation.')])" + "TeamRunResult(messages=[TextMessage(source='user', content='Help user plan a trip and book a flight.'), TextMessage(source='User', content=''), TextMessage(source='TravelAssistant', content=\"I'd be happy to help you plan your trip! To get started, could you please provide me with the following details:\\n\\n1. Your departure city and the destination city.\\n2. Your travel dates (departure and return).\\n3. The number of travelers and their ages (if any children are involved).\\n4. Your budget for flights and accommodations, if you have one in mind.\\n5. Any specific activities or attractions you're interested in at the destination.\\n\\nOnce I have this information, I can help you find the best options!\"), TextMessage(source='User', content='Traveling to toronto from new york'), TextMessage(source='TravelAssistant', content='Great choice! Toronto is a vibrant city with a lot to offer. Now, could you please provide the following additional details to help me assist you better?\\n\\n1. What are your travel dates (departure and return)?\\n2. How many travelers will be going, and what are their ages?\\n3. Do you have a budget for the flight and accommodations?\\n4. Are there any specific activities or attractions you’re interested in while in Toronto?\\n\\nOnce I have this information, I can help you find the best flights and suggestions for your trip!'), TextMessage(source='User', content='leaving on december 7 and returning on 12'), TextMessage(source='TravelAssistant', content=\"Thank you for the details! Here's what I have so far:\\n\\n- **Departure City:** New York\\n- **Destination City:** Toronto\\n- **Departure Date:** December 7\\n- **Return Date:** December 12\\n\\nNow, could you please provide:\\n\\n1. The number of travelers and their ages.\\n2. Your budget for flights and accommodations (if applicable).\\n3. Any specific activities or attractions you're interested in while in Toronto.\\n\\nThis will help me provide more tailored options for your trip!\"), TextMessage(source='User', content='just myself one adult'), TextMessage(source='FlightBroker', content=\"Thanks for the information! Here's what I have:\\n\\n- **Departure City:** New York\\n- **Destination City:** Toronto\\n- **Departure Date:** December 7\\n- **Return Date:** December 12\\n- **Number of Travelers:** 1 Adult\\n\\nCould you let me know if you have a budget for flights and accommodations? Additionally, are there any specific activities or attractions you're interested in while in Toronto? This will help me provide the best options for your trip!\"), TextMessage(source='User', content=\"that's it\"), StopMessage(source='FlightBroker', content='Your flights have been successfully booked! Here are the details:\\n\\n- **Departure:** New York to Toronto\\n - **Flight:** AL21\\n - **Date:** December 7, 2023\\n\\n- **Return:** Toronto to New York\\n - **Flight:** AL21\\n - **Date:** December 12, 2023\\n\\nIf you need help with accommodations, activities, or anything else for your trip, feel free to let me know! \\n\\nTERMINATE'), StopMessage(source='StopMessageTermination', content='Stop message received')])" ] }, - "execution_count": 16, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -302,7 +209,7 @@ "team = SelectorGroupChat(\n", " [user_proxy, flight_broker, travel_assistant], model_client=OpenAIChatCompletionClient(model=\"gpt-4o-mini\")\n", ")\n", - "await team.run(\"Help user plan a trip and book a flight.\")" + "await team.run(\"Help user plan a trip and book a flight.\", termination_condition=StopMessageTermination())" ] } ], diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/tool_use.ipynb b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/tool_use.ipynb index 73ebeaa37abd..a1156e008db9 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/tool_use.ipynb +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/guides/tool_use.ipynb @@ -1,236 +1,185 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Tool Use\n", - "\n", - "The `AgentChat` api provides a `ToolUseAssistantAgent` with presets for adding tools that the agent can call as part of it's response. \n", - "\n", - ":::{note}\n", - "\n", - "The example presented here is a work in progress 🚧. Also, tool uses here assumed the `model_client` used by the agent supports tool calling. \n", - "::: " - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from autogen_agentchat.agents import ToolUseAssistantAgent\n", - "from autogen_agentchat.teams.group_chat import RoundRobinGroupChat\n", - "from autogen_core.components.models import OpenAIChatCompletionClient\n", - "from autogen_core.components.tools import FunctionTool" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In AgentChat, a Tool is a function wrapped in the `FunctionTool` class exported from `autogen_core.components.tools`. " - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "async def get_weather(city: str) -> str:\n", - " return f\"The weather in {city} is 72 degrees and Sunny.\"\n", - "\n", - "\n", - "get_weather_tool = FunctionTool(get_weather, description=\"Get the weather for a city\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, agents that use tools are defined in the following manner. \n", - "\n", - "- An agent is instantiated based on the `ToolUseAssistantAgent` class in AgentChat. The agent is aware of the tools it can use by passing a `tools_schema` attribute to the class, which is passed to the `model_client` when the agent generates a response.\n", - "- An agent Team is defined that takes a list of `tools`. Effectively, the `ToolUseAssistantAgent` can generate messages that call tools, and the team is responsible executing those tool calls and returning the results." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T09:50:13.202461]:\u001b[0m\n", - "\n", - "What's the weather in New York?\n", - "From: user\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T09:50:14.090696], Weather_Assistant:\u001b[0m\n", - "\n", - "[FunctionCall(id='call_wqkaIBdYjWklWG0GQkYz7FZ0', arguments='{\"city\":\"New York\"}', name='get_weather')]\n", - "From: Weather_Assistant\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T09:50:14.092050], tool_agent_for_Weather_Assistant:\u001b[0m\n", - "\n", - "[FunctionExecutionResult(content='The weather in New York is 72 degrees and Sunny.', call_id='call_wqkaIBdYjWklWG0GQkYz7FZ0')]\n", - "From: tool_agent_for_Weather_Assistant\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T09:50:14.714470], Weather_Assistant:\u001b[0m\n", - "\n", - "The weather in New York is 72 degrees and sunny. \n", - "\n", - "TERMINATE\n", - "From: Weather_Assistant" - ] - } - ], - "source": [ - "assistant = ToolUseAssistantAgent(\n", - " \"Weather_Assistant\",\n", - " model_client=OpenAIChatCompletionClient(model=\"gpt-4o-mini\"),\n", - " registered_tools=[get_weather_tool],\n", - ")\n", - "team = RoundRobinGroupChat([assistant])\n", - "result = await team.run(\"What's the weather in New York?\")\n", - "# print(result)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Using Langchain Tools \n", - "\n", - "AutoGen also provides direct support for tools from LangChain via the `autogen_ext` package.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "# pip install langchain, langchain-community, wikipedia , autogen-ext\n", - "\n", - "from autogen_ext.tools.langchain import LangChainToolAdapter\n", - "from langchain.tools import WikipediaQueryRun\n", - "from langchain_community.utilities import WikipediaAPIWrapper\n", - "\n", - "api_wrapper = WikipediaAPIWrapper(top_k_results=1, doc_content_chars_max=100)\n", - "tool = WikipediaQueryRun(api_wrapper=api_wrapper)\n", - "\n", - "langchain_wikipedia_tool = LangChainToolAdapter(tool)" - ] - }, + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tool Use\n", + "\n", + "The `AgentChat` api provides a `ToolUseAssistantAgent` with presets for adding tools that the agent can call as part of it's response. \n", + "\n", + ":::{note}\n", + "\n", + "The example presented here is a work in progress 🚧. Also, tool uses here assumed the `model_client` used by the agent supports tool calling. \n", + "::: " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from autogen_agentchat.agents import ToolUseAssistantAgent\n", + "from autogen_agentchat.teams import EVENT_LOGGER_NAME, RoundRobinGroupChat, StopMessageTermination\n", + "from autogen_core.components.models import OpenAIChatCompletionClient\n", + "from autogen_core.components.tools import FunctionTool" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In AgentChat, a Tool is a function wrapped in the `FunctionTool` class exported from `autogen_core.components.tools`. " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "async def get_weather(city: str) -> str:\n", + " return f\"The weather in {city} is 72 degrees and Sunny.\"\n", + "\n", + "\n", + "get_weather_tool = FunctionTool(get_weather, description=\"Get the weather for a city\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, agents that use tools are defined in the following manner. \n", + "\n", + "- An agent is instantiated based on the `ToolUseAssistantAgent` class in AgentChat. The agent is aware of the tools it can use by passing a `tools_schema` attribute to the class, which is passed to the `model_client` when the agent generates a response.\n", + "- An agent Team is defined that takes a list of `tools`. Effectively, the `ToolUseAssistantAgent` can generate messages that call tools, and the team is responsible executing those tool calls and returning the results." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T09:51:36.869317]:\u001b[0m\n", - "\n", - "Who is the receipient of the 2023 Nobel Prize in Physics?\n", - "From: user" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T09:51:37.856066], WikiPedia_Assistant:\u001b[0m\n", - "\n", - "[FunctionCall(id='call_bdLqS1msbHCy5IMGYaata5vs', arguments='{\"query\":\"2023 Nobel Prize in Physics\"}', name='wikipedia')]\n", - "From: WikiPedia_Assistant\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T09:51:38.518288], tool_agent_for_WikiPedia_Assistant:\u001b[0m\n", - "\n", - "[FunctionExecutionResult(content='Page: Nobel Prize in Physics\\nSummary: The Nobel Prize in Physics (Swedish: Nobelpriset i fysik) is a', call_id='call_bdLqS1msbHCy5IMGYaata5vs')]\n", - "From: tool_agent_for_WikiPedia_Assistant\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T09:51:39.070911], WikiPedia_Assistant:\u001b[0m\n", - "\n", - "[FunctionCall(id='call_BFXGGeuBbOQ1LPb4f0NiNva2', arguments='{\"query\":\"2023 Nobel Prize in Physics recipients\"}', name='wikipedia')]\n", - "From: WikiPedia_Assistant\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T09:51:39.727147], tool_agent_for_WikiPedia_Assistant:\u001b[0m\n", - "\n", - "[FunctionExecutionResult(content='Page: Nobel Prize in Physics\\nSummary: The Nobel Prize in Physics (Swedish: Nobelpriset i fysik) is a', call_id='call_BFXGGeuBbOQ1LPb4f0NiNva2')]\n", - "From: tool_agent_for_WikiPedia_Assistant\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T09:51:40.746467], WikiPedia_Assistant:\u001b[0m\n", - "\n", - "[FunctionCall(id='call_iH2gkY5A2LiQTiy2eh86XpP5', arguments='{\"query\": \"2023 Nobel Prize in Physics winners\"}', name='wikipedia'), FunctionCall(id='call_rJXgJQiAKoD7yrymNJCsQA9N', arguments='{\"query\": \"Nobel Prize in Physics\"}', name='wikipedia')]\n", - "From: WikiPedia_Assistant\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T09:51:41.469348], tool_agent_for_WikiPedia_Assistant:\u001b[0m\n", - "\n", - "[FunctionExecutionResult(content='Page: Nobel Prize in Physics\\nSummary: The Nobel Prize in Physics (Swedish: Nobelpriset i fysik) is a', call_id='call_iH2gkY5A2LiQTiy2eh86XpP5'), FunctionExecutionResult(content='Page: Nobel Prize in Physics\\nSummary: The Nobel Prize in Physics (Swedish: Nobelpriset i fysik) is a', call_id='call_rJXgJQiAKoD7yrymNJCsQA9N')]\n", - "From: tool_agent_for_WikiPedia_Assistant\n", - "--------------------------------------------------------------------------- \n", - "\u001b[91m[2024-10-08T09:51:42.576718], WikiPedia_Assistant:\u001b[0m\n", - "\n", - "I couldn't find specific information about the recipients of the 2023 Nobel Prize in Physics. You might want to check a reliable news source or the official Nobel Prize website for the most accurate and up-to-date details. \n", - "\n", - "TERMINATE\n", - "From: WikiPedia_Assistant" - ] - } - ], - "source": [ - "wikipedia_assistant = ToolUseAssistantAgent(\n", - " \"WikiPedia_Assistant\",\n", - " model_client=OpenAIChatCompletionClient(model=\"gpt-4o-mini\"),\n", - " registered_tools=[langchain_wikipedia_tool],\n", - ")\n", - "team = RoundRobinGroupChat([wikipedia_assistant])\n", - "result = await team.run(\"Who was the first president of the United States?\")\n", - "\n", - "# print(result)" - ] + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "--------------------------------------------------------------------------- \n", + "\u001b[91m[2024-10-08T20:34:31.935149]:\u001b[0m\n", + "\n", + "What's the weather in New York?" + ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "--------------------------------------------------------------------------- \n", + "\u001b[91m[2024-10-08T20:34:33.080494], Weather_Assistant:\u001b[0m\n", + "\n", + "The weather in New York is 72 degrees and sunny. \n", + "\n", + "TERMINATE" + ] } - ], - "metadata": { - "kernelspec": { - "display_name": "agnext", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.9" + ], + "source": [ + "assistant = ToolUseAssistantAgent(\n", + " \"Weather_Assistant\",\n", + " model_client=OpenAIChatCompletionClient(model=\"gpt-4o-mini\"),\n", + " registered_tools=[get_weather_tool],\n", + ")\n", + "team = RoundRobinGroupChat([assistant])\n", + "result = await team.run(\"What's the weather in New York?\", termination_condition=StopMessageTermination())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using Langchain Tools \n", + "\n", + "AutoGen also provides direct support for tools from LangChain via the `autogen_ext` package.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# pip install langchain, langchain-community, wikipedia , autogen-ext\n", + "\n", + "import wikipedia\n", + "from autogen_ext.tools.langchain import LangChainToolAdapter\n", + "from langchain.tools import WikipediaQueryRun\n", + "from langchain_community.utilities import WikipediaAPIWrapper\n", + "\n", + "api_wrapper = WikipediaAPIWrapper(wiki_client=wikipedia, top_k_results=1, doc_content_chars_max=100)\n", + "tool = WikipediaQueryRun(api_wrapper=api_wrapper)\n", + "\n", + "langchain_wikipedia_tool = LangChainToolAdapter(tool)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "--------------------------------------------------------------------------- \n", + "\u001b[91m[2024-10-08T20:44:08.218758]:\u001b[0m\n", + "\n", + "Who was the first president of the United States?\n", + "--------------------------------------------------------------------------- \n", + "\u001b[91m[2024-10-08T20:44:11.240067], WikiPedia_Assistant:\u001b[0m\n", + "\n", + "The first president of the United States was George Washington, who served from April 30, 1789, to March 4, 1797. \n", + "\n", + "TERMINATE" + ] } + ], + "source": [ + "wikipedia_assistant = ToolUseAssistantAgent(\n", + " \"WikiPedia_Assistant\",\n", + " model_client=OpenAIChatCompletionClient(model=\"gpt-4o-mini\"),\n", + " registered_tools=[langchain_wikipedia_tool],\n", + ")\n", + "team = RoundRobinGroupChat([wikipedia_assistant])\n", + "result = await team.run(\n", + " \"Who was the first president of the United States?\", termination_condition=StopMessageTermination()\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" }, - "nbformat": 4, - "nbformat_minor": 2 + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 } diff --git a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/stocksnippet.md b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/stocksnippet.md index 9308cf14510b..0969c77667a0 100644 --- a/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/stocksnippet.md +++ b/python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/stocksnippet.md @@ -3,7 +3,7 @@ `````{tab-item} AgentChat (v0.4x) ```python from autogen_agentchat.agents import CodeExecutorAgent, CodingAssistantAgent -from autogen_agentchat.teams.group_chat import RoundRobinGroupChat +from autogen_agentchat.teams import RoundRobinGroupChat, StopMessageTermination from autogen_core.components.code_executor import DockerCommandLineCodeExecutor from autogen_core.components.models import OpenAIChatCompletionClient @@ -14,9 +14,9 @@ async with DockerCommandLineCodeExecutor(work_dir="coding") as code_executor: ) group_chat = RoundRobinGroupChat([coding_assistant_agent, code_executor_agent]) result = await group_chat.run( - task="Create a plot of NVIDIA and TESLA stock returns YTD from 2024-01-01 and save it to 'nvidia_tesla_2024_ytd.png'." + task="Create a plot of NVIDIA and TESLA stock returns YTD from 2024-01-01 and save it to 'nvidia_tesla_2024_ytd.png'.", + termination_condition=StopMessageTermination(), ) - print(result) ``` ````` diff --git a/website/blog/2024-10-02-new-autogen-architecture-preview/index.mdx b/website/blog/2024-10-02-new-autogen-architecture-preview/index.mdx new file mode 100644 index 000000000000..42a873d38bb2 --- /dev/null +++ b/website/blog/2024-10-02-new-autogen-architecture-preview/index.mdx @@ -0,0 +1,103 @@ +--- +title: New AutoGen Architecture Preview +authors: + - autogen-team +tags: [AutoGen] +--- + +# New AutoGen Architecture Preview + +
+ +![What are they doing?](img/robots.jpeg) + +
+ +One year ago, we launched AutoGen, a programming framework designed to build +agentic AI systems. The release of AutoGen sparked massive interest within the +developer community. As an early release, it provided us with a unique +opportunity to engage deeply with users, gather invaluable feedback, and learn +from a diverse range of use cases and contributions. By listening and engaging +with the community, we gained insights into what people were building or +attempting to build, how they were approaching the creation of agentic systems, +and where they were struggling. This experience was both humbling and +enlightening, revealing significant opportunities for improvement in our initial +design, especially for power users developing production-level applications with +AutoGen. + +Through engagements with the community, we learned many lessons: + +- Developers value modular and reusable agents. For example, our built-in agents + that could be directly plugged in or easily customized for specific use cases + were particularly popular. At the same time, there was a desire for more + customizability, such as integrating custom agents built using other + programming languages or frameworks. +- Chat-based agent-to-agent communication was an intuitive collaboration + pattern, making it easy for developers to get started and involve humans in + the loop. As developers began to employ agents in a wider range of scenarios, + they sought more flexibility in collaboration patterns. For instance, + developers wanted to build predictable, ordered workflows with agents, and to + integrate them with new user interfaces that are not chat-based. +- Although it was easy for developers to get started with AutoGen, debugging and + scaling agent teams applications proved more challenging. +- There were many opportunities for improving code quality. + +These learnings, along with many others from other agentic efforts across +Microsoft, prompted us to take a step back and lay the groundwork for a new +direction. A few months ago, we started dedicating time to distilling these +learnings into a roadmap for the future of AutoGen. This led to the development +of AutoGen 0.4, a complete redesign of the framework from the foundation up. +AutoGen 0.4 embraces the actor model of computing to support distributed, highly +scalable, event-driven agentic systems. This approach offers many advantages, +such as: + +- **Composability**. Systems designed in this way are more composable, allowing + developers to bring their own agents implemented in different frameworks or + programming languages and to build more powerful systems using complex agentic + patterns. +- **Flexibility**. It allows for the creation of both deterministic, ordered + workflows and event-driven or decentralized workflows, enabling customers to + bring their own orchestration or integrate with other systems more easily. It + also opens more opportunities for human-in-the-loop scenarios, both active and + reactive. +- **Debugging and Observability**. Event-driven communication moves message delivery + away from agents to a centralized component, making it easier to observe and + debug their activities regardless of agent implementation. +- **Scalability**. An event-based architecture enables distributed and + cloud-deployed agents, which is essential for building scalable AI services + and applications. + +Today, we are delighted to share our progress and invite everyone to collaborate +with us and provide feedback to evolve AutoGen and help shape the future of +multi-agent systems. + +As the first step, we are opening a [pull request](https://github.com/microsoft/autogen/pull/3600) into the main branch with the +current state of development of 0.4. After approximately a week, we plan to +merge this into main and continue development. There's still a lot left to do +before 0.4 is ready for release though, so keep in mind this is a work in +progress. + +Starting in AutoGen 0.4, the project will have three main libraries: + +- **Core** - the building blocks for an event-driven agentic system. +- **AgentChat** - a task-driven, high-level API built with core, including group + chat, code execution, pre-built agents, and more. This is the most similar API + to AutoGen [0.2](https://github.com/microsoft/autogen/tree/0.2) and will be the easiest API to migrate to. +- **Extensions** - implementations of core interfaces and third-party integrations + (e.g., Azure code executor and OpenAI model client). + +AutoGen [0.2](https://github.com/microsoft/autogen/tree/0.2) is still available, developed and maintained out of the [0.2 branch](https://github.com/microsoft/autogen/tree/0.2). +For everyone looking for a stable version, we recommend continuing to use [0.2](https://github.com/microsoft/autogen/tree/0.2) +for the time being. It can be installed using: + +```sh +pip install autogen-agentchat~=0.2 +``` + +This new package name was used to align with the new packages that will come with 0.4: +`autogen-core`, `autogen-agentchat`, and `autogen-ext`. + +Lastly, we will be using [GitHub +Discussion](https://github.com/microsoft/autogen/discussions) as the official +community forum for the new version and, going forward, all discussions related +to the AutoGen project. We look forward to meeting you there.