CoACT initialize (#292)

This commit is contained in:
Linxin Song
2025-07-30 19:35:20 -07:00
committed by GitHub
parent 862d704b8c
commit b968155757
228 changed files with 42386 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0
#
from .auto import AutoPattern
from .manual import ManualPattern
from .pattern import DefaultPattern
from .random import RandomPattern
from .round_robin import RoundRobinPattern
__all__ = [
"AutoPattern",
"DefaultPattern",
"ManualPattern",
"RandomPattern",
"RoundRobinPattern",
]

View File

@@ -0,0 +1,159 @@
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0
from typing import TYPE_CHECKING, Any, Callable, Optional, Tuple, Union
from ..context_variables import ContextVariables
from ..targets.group_manager_target import GroupManagerSelectionMessage, GroupManagerTarget
from ..targets.transition_target import TransitionTarget
from .pattern import Pattern
if TYPE_CHECKING:
from ...conversable_agent import ConversableAgent
from ...groupchat import GroupChat, GroupChatManager
from ..group_tool_executor import GroupToolExecutor
class AutoPattern(Pattern):
"""AutoPattern implements a flexible pattern where agents are selected based on their expertise.
In this pattern, a group manager automatically selects the next agent to speak based on the context
of the conversation and agent descriptions. The after_work is always set to "group_manager" as
this is the defining characteristic of this pattern.
"""
def __init__(
self,
initial_agent: "ConversableAgent",
agents: list["ConversableAgent"],
user_agent: Optional["ConversableAgent"] = None,
group_manager_args: Optional[dict[str, Any]] = None,
context_variables: Optional[ContextVariables] = None,
selection_message: Optional[GroupManagerSelectionMessage] = None,
exclude_transit_message: bool = True,
summary_method: Optional[Union[str, Callable[..., Any]]] = "last_msg",
):
"""Initialize the AutoPattern.
The after_work is always set to group_manager selection, which is the defining
characteristic of this pattern. You can customize the selection message used
by the group manager when selecting the next agent.
Args:
initial_agent: The first agent to speak in the group chat.
agents: List of all agents participating in the chat.
user_agent: Optional user proxy agent.
group_manager_args: Optional arguments for the GroupChatManager.
context_variables: Initial context variables for the chat.
selection_message: Custom message to use when the group manager is selecting agents.
exclude_transit_message: Whether to exclude transit messages from the conversation.
summary_method: Method for summarizing the conversation.
"""
# Create the group_manager after_work with the provided selection message
group_manager_after_work = GroupManagerTarget(selection_message=selection_message)
super().__init__(
initial_agent=initial_agent,
agents=agents,
user_agent=user_agent,
group_manager_args=group_manager_args,
context_variables=context_variables,
group_after_work=group_manager_after_work,
exclude_transit_message=exclude_transit_message,
summary_method=summary_method,
)
# Store the selection message for potential use
self.selection_message = selection_message
def prepare_group_chat(
self,
max_rounds: int,
messages: Union[list[dict[str, Any]], str],
) -> Tuple[
list["ConversableAgent"],
list["ConversableAgent"],
Optional["ConversableAgent"],
ContextVariables,
"ConversableAgent",
TransitionTarget,
"GroupToolExecutor",
"GroupChat",
"GroupChatManager",
list[dict[str, Any]],
Any,
list[str],
list[Any],
]:
"""Prepare the group chat for organic agent selection.
Ensures that:
1. The group manager has a valid LLM config
2. All agents have appropriate descriptions for the group manager to use
Args:
max_rounds: Maximum number of conversation rounds.
messages: Initial message(s) to start the conversation.
Returns:
Tuple containing all necessary components for the group chat.
"""
# Validate that group_manager_args has an LLM config which is required for this pattern
if not self.group_manager_args.get("llm_config", False):
# Check if any agent has an LLM config we can use
has_llm_config = any(getattr(agent, "llm_config", False) for agent in self.agents)
if not has_llm_config:
raise ValueError(
"AutoPattern requires the group_manager_args to include an llm_config, "
"or at least one agent to have an llm_config"
)
# Check that all agents have descriptions for effective group manager selection
for agent in self.agents:
if not hasattr(agent, "description") or not agent.description:
agent.description = f"Agent {agent.name}"
# Use the parent class's implementation to prepare the agents and group chat
components = super().prepare_group_chat(
max_rounds=max_rounds,
messages=messages,
)
# Extract the group_after_work and the rest of the components
(
agents,
wrapped_agents,
user_agent,
context_variables,
initial_agent,
_,
tool_executor,
groupchat,
manager,
processed_messages,
last_agent,
group_agent_names,
temp_user_list,
) = components
# Ensure we're using the group_manager after_work
group_after_work = self.group_after_work
# Return all components with our group_after_work
return (
agents,
wrapped_agents,
user_agent,
context_variables,
initial_agent,
group_after_work,
tool_executor,
groupchat,
manager,
processed_messages,
last_agent,
group_agent_names,
temp_user_list,
)

View File

@@ -0,0 +1,176 @@
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0
from typing import TYPE_CHECKING, Any, Callable, Optional, Tuple, Union
from ..context_variables import ContextVariables
from ..group_tool_executor import GroupToolExecutor
from ..targets.transition_target import AskUserTarget, TransitionTarget
from .pattern import Pattern
if TYPE_CHECKING:
from ...conversable_agent import ConversableAgent
from ...groupchat import GroupChat, GroupChatManager
class ManualPattern(Pattern):
"""ManualPattern will ask the user to nominate the next agent to speak at each turn."""
def __init__(
self,
initial_agent: "ConversableAgent",
agents: list["ConversableAgent"],
user_agent: Optional["ConversableAgent"] = None,
group_manager_args: Optional[dict[str, Any]] = None,
context_variables: Optional[ContextVariables] = None,
exclude_transit_message: bool = True,
summary_method: Optional[Union[str, Callable[..., Any]]] = "last_msg",
):
"""Initialize the ManualPattern.
The after_work is always set to ask_user, which will prompt the user for the next agent
Args:
initial_agent: The first agent to speak in the group chat.
agents: List of all agents participating in the chat.
user_agent: Optional user proxy agent.
group_manager_args: Optional arguments for the GroupChatManager.
context_variables: Initial context variables for the chat.
exclude_transit_message: Whether to exclude transit messages from the conversation.
summary_method: Method for summarizing the conversation.
"""
# The group after work will be to ask the user
group_after_work = AskUserTarget()
super().__init__(
initial_agent=initial_agent,
agents=agents,
user_agent=user_agent,
group_manager_args=group_manager_args,
context_variables=context_variables,
group_after_work=group_after_work,
exclude_transit_message=exclude_transit_message,
summary_method=summary_method,
)
def prepare_group_chat(
self,
max_rounds: int,
messages: Union[list[dict[str, Any]], str],
) -> Tuple[
list["ConversableAgent"],
list["ConversableAgent"],
Optional["ConversableAgent"],
ContextVariables,
"ConversableAgent",
TransitionTarget,
"GroupToolExecutor",
"GroupChat",
"GroupChatManager",
list[dict[str, Any]],
Any,
list[str],
list[Any],
]:
"""Prepare the group chat for organic agent selection.
Ensures that:
1. The group manager has a valid LLM config
2. All agents have appropriate descriptions for the group manager to use
Args:
max_rounds: Maximum number of conversation rounds.
messages: Initial message(s) to start the conversation.
Returns:
Tuple containing all necessary components for the group chat.
"""
# Use the parent class's implementation to prepare the agents and group chat
components = super().prepare_group_chat(
max_rounds=max_rounds,
messages=messages,
)
# Extract the group_after_work and the rest of the components
(
agents,
wrapped_agents,
user_agent,
context_variables,
initial_agent,
_,
tool_executor,
groupchat,
manager,
processed_messages,
last_agent,
group_agent_names,
temp_user_list,
) = components
# Ensure we're using the group_manager after_work
group_after_work = self.group_after_work
# Set up the allowed speaker transitions to exclude user_agent and GroupToolExecutor
self._setup_allowed_transitions(groupchat, user_agent, tool_executor)
# Return all components with our group_after_work
return (
agents,
wrapped_agents,
user_agent,
context_variables,
initial_agent,
group_after_work,
tool_executor,
groupchat,
manager,
processed_messages,
last_agent,
group_agent_names,
temp_user_list,
)
def _setup_allowed_transitions(
self, groupchat: "GroupChat", user_agent: Optional["ConversableAgent"], tool_executor: "GroupToolExecutor"
) -> None:
"""Set up the allowed speaker transitions for the group chat so that when a user selects the next agent the tool executor and user agent don't appear as options.
Creates transitions where:
1. Any agent can speak after any other agent, including themselves
2. The user_agent and GroupToolExecutor are excluded from transitions
Args:
groupchat: The GroupChat instance to configure
user_agent: The user agent to exclude from transitions
tool_executor: The GroupToolExecutor to exclude from transitions
"""
# NOTE: THIS IS NOT WORKING - THE TRANSITIONS ARE NOT BEING KEPT?!
"""
# Get all agents in the group chat
all_agents = groupchat.agents
# Filter out user_agent and group tool executor
eligible_agents = []
for agent in all_agents:
# Skip user_agent
if agent == user_agent:
continue
# Skip GroupToolExecutor
if isinstance(agent, GroupToolExecutor):
continue
eligible_agents.append(agent)
# Create a fully connected graph among eligible agents
# Each agent can be followed by any other eligible agent
allowed_transitions = {}
for agent in eligible_agents:
# For each agent, every other eligible agent can follow
allowed_transitions[agent] = eligible_agents
# Set the transitions in the group chat
groupchat.allowed_speaker_transitions_dict = allowed_transitions
"""

View File

@@ -0,0 +1,294 @@
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0
# Patterns of agent orchestrations
# Uses the group chat or the agents' handoffs to create a pattern
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, Callable, Optional, Tuple, Union
from ..context_variables import ContextVariables
from ..group_utils import (
create_group_manager,
create_group_transition,
link_agents_to_group_manager,
prepare_group_agents,
process_initial_messages,
setup_context_variables,
)
from ..targets.transition_target import TerminateTarget, TransitionTarget
if TYPE_CHECKING:
from ...agent import Agent
from ...conversable_agent import ConversableAgent
from ...groupchat import GroupChat, GroupChatManager
from ..group_tool_executor import GroupToolExecutor
class Pattern(ABC):
"""Base abstract class for all orchestration patterns.
Patterns provide a reusable way to define how agents interact within a group chat.
Each pattern encapsulates the logic for setting up agents, configuring handoffs,
and determining the flow of conversation.
This is an abstract base class and should not be instantiated directly.
Use one of the concrete pattern implementations like AutoPattern,
RoundRobinPattern, RandomPattern, or ManualPattern.
"""
def __init__(
self,
initial_agent: "ConversableAgent",
agents: list["ConversableAgent"],
user_agent: Optional["ConversableAgent"] = None,
group_manager_args: Optional[dict[str, Any]] = None,
context_variables: Optional[ContextVariables] = None,
group_after_work: Optional[TransitionTarget] = None,
exclude_transit_message: bool = True,
summary_method: Optional[Union[str, Callable[..., Any]]] = "last_msg",
):
"""Initialize the pattern with the required components.
Args:
initial_agent: The first agent to speak in the group chat.
agents: List of all agents participating in the chat.
user_agent: Optional user proxy agent.
group_manager_args: Optional arguments for the GroupChatManager.
context_variables: Initial context variables for the chat.
group_after_work: Default after work transition behavior when no specific next agent is determined.
exclude_transit_message: Whether to exclude transit messages from the conversation.
summary_method: Method for summarizing the conversation.
"""
self.initial_agent = initial_agent
self.agents = agents
self.user_agent = user_agent
self.group_manager_args = group_manager_args or {}
self.context_variables = context_variables or ContextVariables()
self.group_after_work = group_after_work if group_after_work is not None else TerminateTarget()
self.exclude_transit_message = exclude_transit_message
self.summary_method = summary_method
@abstractmethod
def prepare_group_chat(
self,
max_rounds: int,
messages: Union[list[dict[str, Any]], str],
) -> Tuple[
list["ConversableAgent"],
list["ConversableAgent"],
Optional["ConversableAgent"],
ContextVariables,
"ConversableAgent",
TransitionTarget,
"GroupToolExecutor",
"GroupChat",
"GroupChatManager",
list[dict[str, Any]],
"ConversableAgent",
list[str],
list["Agent"],
]:
"""Prepare the group chat for orchestration.
This is the main method called by initiate_group_chat to set up the pattern.
Subclasses must implement or extend this method to define pattern-specific behavior.
Args:
max_rounds: Maximum number of conversation rounds.
messages: Initial message(s) to start the conversation.
Returns:
Tuple containing:
- List of agents involved in the group chat
- List of wrapped agents
- User agent, if applicable
- Context variables for the group chat
- Initial agent for the group chat
- Group-level after work transition for the group chat
- Tool executor for the group chat
- GroupChat instance
- GroupChatManager instance
- Processed messages
- Last agent to speak
- List of group agent names
- List of temporary user agents
"""
from ...groupchat import GroupChat
# Prepare the agents using the existing helper function
tool_executor, wrapped_agents = prepare_group_agents(
self.agents, self.context_variables, self.exclude_transit_message
)
# Process the initial messages BEFORE creating the GroupChat
# This will create a temporary user agent if needed
processed_messages, last_agent, group_agent_names, temp_user_list = process_initial_messages(
messages, self.user_agent, self.agents, wrapped_agents
)
# Create transition function (has enclosed state for initial agent)
group_transition = create_group_transition(
initial_agent=self.initial_agent,
tool_execution=tool_executor,
group_agent_names=group_agent_names,
user_agent=self.user_agent,
group_after_work=self.group_after_work,
)
# Create the group chat - now we use temp_user_list if no user_agent
groupchat = GroupChat(
agents=[tool_executor]
+ self.agents
+ wrapped_agents
+ ([self.user_agent] if self.user_agent else temp_user_list),
messages=[],
max_round=max_rounds,
speaker_selection_method=group_transition,
)
# Create the group manager
manager = create_group_manager(groupchat, self.group_manager_args, self.agents, self.group_after_work)
# Point all agent's context variables to this function's context_variables
setup_context_variables(
tool_execution=tool_executor,
agents=self.agents,
manager=manager,
user_agent=self.user_agent,
context_variables=self.context_variables,
)
# Link all agents with the GroupChatManager to allow access to the group chat
link_agents_to_group_manager(groupchat.agents, manager)
return (
self.agents,
wrapped_agents,
self.user_agent,
self.context_variables,
self.initial_agent,
self.group_after_work,
tool_executor,
groupchat,
manager,
processed_messages,
last_agent,
group_agent_names,
temp_user_list,
) # type: ignore[return-value]
@classmethod
def create_default(
cls,
initial_agent: "ConversableAgent",
agents: list["ConversableAgent"],
user_agent: Optional["ConversableAgent"] = None,
group_manager_args: Optional[dict[str, Any]] = None,
context_variables: Optional[ContextVariables] = None,
exclude_transit_message: bool = True,
summary_method: Optional[Union[str, Callable[..., Any]]] = "last_msg",
) -> "DefaultPattern":
"""Create a default pattern with minimal configuration.
This replaces the need for a separate BasePattern class by providing
a factory method that creates a simple DefaultPattern instance.
Args:
initial_agent: The first agent to speak in the group chat.
agents: List of all agents participating in the chat.
user_agent: Optional user proxy agent.
group_manager_args: Optional arguments for the GroupChatManager.
context_variables: Initial context variables for the chat.
exclude_transit_message: Whether to exclude transit messages from the conversation.
summary_method: Method for summarizing the conversation.
Returns:
A DefaultPattern instance with basic configuration.
"""
return DefaultPattern(
initial_agent=initial_agent,
agents=agents,
user_agent=user_agent,
group_manager_args=group_manager_args,
context_variables=context_variables,
exclude_transit_message=exclude_transit_message,
summary_method=summary_method,
)
class DefaultPattern(Pattern):
"""DefaultPattern implements a minimal pattern for simple agent interactions.
This replaces the previous BasePattern and provides a concrete implementation
of the Pattern abstract base class.
"""
def prepare_group_chat(
self,
max_rounds: int,
messages: Union[list[dict[str, Any]], str],
) -> Tuple[
list["ConversableAgent"],
list["ConversableAgent"],
Optional["ConversableAgent"],
ContextVariables,
"ConversableAgent",
TransitionTarget,
"GroupToolExecutor",
"GroupChat",
"GroupChatManager",
list[dict[str, Any]],
Any,
list[str],
list[Any],
]:
"""Prepare the group chat with default configuration.
This implementation calls the parent class method but ensures that
the group_after_work in the returned tuple is the pattern's own.
Args:
max_rounds: Maximum number of conversation rounds.
messages: Initial message(s) to start the conversation.
Returns:
Tuple containing all necessary components for the group chat.
"""
# Use the parent class's implementation to prepare the agents and group chat
(
agents,
wrapped_agents,
user_agent,
context_variables,
initial_agent,
_, # Ignore the group_after_work from parent
tool_executor,
groupchat,
manager,
processed_messages,
last_agent,
group_agent_names,
temp_user_list,
) = super().prepare_group_chat(
max_rounds=max_rounds,
messages=messages,
)
# Return all components with our group_after_work
return (
agents,
wrapped_agents,
user_agent,
context_variables,
initial_agent,
self.group_after_work, # Use our own group_after_work
tool_executor,
groupchat,
manager,
processed_messages,
last_agent,
group_agent_names,
temp_user_list,
)

View File

@@ -0,0 +1,106 @@
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0
from typing import TYPE_CHECKING, Any, Optional, Tuple, Union
from ..context_variables import ContextVariables
from ..targets.transition_target import RandomAgentTarget, TransitionTarget
from .pattern import Pattern
if TYPE_CHECKING:
from ...conversable_agent import ConversableAgent
from ...groupchat import GroupChat, GroupChatManager
from ..group_tool_executor import GroupToolExecutor
class RandomPattern(Pattern):
"""RandomPattern implements a random agent selection process."""
def _generate_handoffs(
self,
initial_agent: "ConversableAgent",
agents: list["ConversableAgent"],
user_agent: Optional["ConversableAgent"],
) -> None:
"""Generate handoffs between agents in a random fashion."""
agent_list = agents + ([user_agent] if user_agent is not None else [])
for agent in agent_list:
# Get the list of agents except itself
other_agents = [a for a in agent_list if a != agent]
# Create a random after work
agent.handoffs.set_after_work(target=RandomAgentTarget(agents=other_agents))
def prepare_group_chat(
self,
max_rounds: int,
messages: Union[list[dict[str, Any]], str],
) -> Tuple[
list["ConversableAgent"],
list["ConversableAgent"],
Optional["ConversableAgent"],
ContextVariables,
"ConversableAgent",
TransitionTarget,
"GroupToolExecutor",
"GroupChat",
"GroupChatManager",
list[dict[str, Any]],
Any,
list[str],
list[Any],
]:
"""Prepare the group chat for organic agent selection.
Ensures that:
1. The group manager has a valid LLM config
2. All agents have appropriate descriptions for the group manager to use
Args:
max_rounds: Maximum number of conversation rounds.
messages: Initial message(s) to start the conversation.
Returns:
Tuple containing all necessary components for the group chat.
"""
# Use the parent class's implementation to prepare the agents and group chat
(
agents,
wrapped_agents,
user_agent,
context_variables,
initial_agent,
group_after_work,
tool_executor,
groupchat,
manager,
processed_messages,
last_agent,
group_agent_names,
temp_user_list,
) = super().prepare_group_chat(
max_rounds=max_rounds,
messages=messages,
)
# Create the random handoffs between agents
self._generate_handoffs(initial_agent=initial_agent, agents=agents, user_agent=user_agent)
# Return all components with our group_after_work
return (
agents,
wrapped_agents,
user_agent,
context_variables,
initial_agent,
group_after_work,
tool_executor,
groupchat,
manager,
processed_messages,
last_agent,
group_agent_names,
temp_user_list,
)

View File

@@ -0,0 +1,117 @@
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0
from typing import TYPE_CHECKING, Any, Optional, Tuple, Union
from ..context_variables import ContextVariables
from ..targets.transition_target import AgentTarget, TransitionTarget
from .pattern import Pattern
if TYPE_CHECKING:
from ...conversable_agent import ConversableAgent
from ...groupchat import GroupChat, GroupChatManager
from ..group_tool_executor import GroupToolExecutor
class RoundRobinPattern(Pattern):
"""RoundRobinPattern implements a round robin with handoffs between agents."""
def _generate_handoffs(
self,
initial_agent: "ConversableAgent",
agents: list["ConversableAgent"],
user_agent: Optional["ConversableAgent"],
) -> None:
"""Generate handoffs between agents in a round-robin fashion."""
# Create a list of the agents and the user_agent but put the initial_agent first
agent_list = [initial_agent]
# Add the rest of the agents, excluding the initial_agent and user_agent
for agent in agents:
if agent != initial_agent and (user_agent is None or agent != user_agent):
agent_list.append(agent)
# Add the user_agent last if it exists
if user_agent is not None:
agent_list.append(user_agent)
# Create handoffs in a round-robin fashion
for i, agent in enumerate(agent_list):
# Last agent hands off to the first agent
# Otherwise agent hands off to the next one
handoff_target = agent_list[0] if i == len(agent_list) - 1 else agent_list[i + 1]
agent.handoffs.set_after_work(target=AgentTarget(agent=handoff_target))
def prepare_group_chat(
self,
max_rounds: int,
messages: Union[list[dict[str, Any]], str],
) -> Tuple[
list["ConversableAgent"],
list["ConversableAgent"],
Optional["ConversableAgent"],
ContextVariables,
"ConversableAgent",
TransitionTarget,
"GroupToolExecutor",
"GroupChat",
"GroupChatManager",
list[dict[str, Any]],
Any,
list[str],
list[Any],
]:
"""Prepare the group chat for organic agent selection.
Ensures that:
1. The group manager has a valid LLM config
2. All agents have appropriate descriptions for the group manager to use
Args:
max_rounds: Maximum number of conversation rounds.
messages: Initial message(s) to start the conversation.
Returns:
Tuple containing all necessary components for the group chat.
"""
# Use the parent class's implementation to prepare the agents and group chat
(
agents,
wrapped_agents,
user_agent,
context_variables,
initial_agent,
group_after_work,
tool_executor,
groupchat,
manager,
processed_messages,
last_agent,
group_agent_names,
temp_user_list,
) = super().prepare_group_chat(
max_rounds=max_rounds,
messages=messages,
)
# Create the handoffs between agents
self._generate_handoffs(initial_agent=initial_agent, agents=agents, user_agent=user_agent)
# Return all components with our group_after_work
return (
agents,
wrapped_agents,
user_agent,
context_variables,
initial_agent,
group_after_work,
tool_executor,
groupchat,
manager,
processed_messages,
last_agent,
group_agent_names,
temp_user_list,
)