CoACT initialize (#292)
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@@ -0,0 +1,132 @@
|
||||
# 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, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ....doc_utils import export_module
|
||||
from ...agent import Agent
|
||||
from ..speaker_selection_result import SpeakerSelectionResult
|
||||
from .transition_target import AgentTarget, TransitionTarget
|
||||
from .transition_utils import __AGENT_WRAPPER_PREFIX__
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...conversable_agent import ConversableAgent
|
||||
from ...groupchat import GroupChat
|
||||
from ..patterns.pattern import Pattern
|
||||
|
||||
|
||||
__all__ = ["GroupChatConfig", "GroupChatTarget"]
|
||||
|
||||
|
||||
@export_module("autogen.agentchat.group")
|
||||
class GroupChatConfig(BaseModel):
|
||||
"""Configuration for a group chat transition target.
|
||||
|
||||
Note: If context_variables are not passed in, the outer context variables will be passed in"""
|
||||
|
||||
pattern: "Pattern"
|
||||
messages: Union[list[dict[str, Any]], str]
|
||||
max_rounds: int = 20
|
||||
|
||||
|
||||
@export_module("autogen.agentchat.group")
|
||||
class GroupChatTarget(TransitionTarget):
|
||||
"""Target that represents a group chat."""
|
||||
|
||||
group_chat_config: GroupChatConfig
|
||||
|
||||
def can_resolve_for_speaker_selection(self) -> bool:
|
||||
"""Check if the target can resolve for speaker selection. For GroupChatTarget the chat must be encapsulated into an agent."""
|
||||
return False
|
||||
|
||||
def resolve(
|
||||
self,
|
||||
groupchat: "GroupChat",
|
||||
current_agent: "ConversableAgent",
|
||||
user_agent: Optional["ConversableAgent"],
|
||||
) -> SpeakerSelectionResult:
|
||||
"""Resolve to the nested chat configuration."""
|
||||
raise NotImplementedError(
|
||||
"GroupChatTarget does not support the resolve method. An agent should be used to encapsulate this nested chat and then the target changed to an AgentTarget."
|
||||
)
|
||||
|
||||
def display_name(self) -> str:
|
||||
"""Get the display name for the target."""
|
||||
return "a group chat"
|
||||
|
||||
def normalized_name(self) -> str:
|
||||
"""Get a normalized name for the target that has no spaces, used for function calling."""
|
||||
return "group_chat"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""String representation for AgentTarget, can be shown as a function call message."""
|
||||
return "Transfer to group chat"
|
||||
|
||||
def needs_agent_wrapper(self) -> bool:
|
||||
"""Check if the target needs to be wrapped in an agent. GroupChatTarget must be wrapped in an agent."""
|
||||
return True
|
||||
|
||||
def create_wrapper_agent(self, parent_agent: "ConversableAgent", index: int) -> "ConversableAgent":
|
||||
"""Create a wrapper agent for the group chat."""
|
||||
from autogen.agentchat import initiate_group_chat
|
||||
|
||||
from ...conversable_agent import ConversableAgent # to avoid circular import
|
||||
|
||||
# Create the wrapper agent with a name that identifies it as a wrapped group chat
|
||||
group_chat_agent = ConversableAgent(
|
||||
name=f"{__AGENT_WRAPPER_PREFIX__}group_{parent_agent.name}_{index + 1}",
|
||||
# Copy LLM config from parent agent to ensure it can generate replies if needed
|
||||
llm_config=parent_agent.llm_config,
|
||||
)
|
||||
|
||||
# Store the config directly on the agent
|
||||
group_chat_agent._group_chat_config = self.group_chat_config # type: ignore[attr-defined]
|
||||
|
||||
# Define the reply function that will run the group chat
|
||||
def group_chat_reply(
|
||||
agent: "ConversableAgent",
|
||||
messages: Optional[list[dict[str, Any]]] = None,
|
||||
sender: Optional["Agent"] = None,
|
||||
config: Optional[Any] = None,
|
||||
) -> tuple[bool, Optional[dict[str, Any]]]:
|
||||
"""Run the inner group chat and return its results as a reply."""
|
||||
# Get the configuration stored directly on the agent
|
||||
group_config = agent._group_chat_config # type: ignore[attr-defined]
|
||||
|
||||
# Pull through the second last message from the outer chat (the last message will be the handoff message)
|
||||
# This may need work to make sure we get the right message(s) from the outer chat
|
||||
message = (
|
||||
messages[-2]["content"]
|
||||
if messages and len(messages) >= 2 and "content" in messages[-2]
|
||||
else "No message to pass through."
|
||||
)
|
||||
|
||||
try:
|
||||
# Run the group chat with direct agent references from the config
|
||||
result, _, _ = initiate_group_chat(
|
||||
pattern=group_config.pattern,
|
||||
messages=message,
|
||||
max_rounds=group_config.max_rounds,
|
||||
)
|
||||
|
||||
# Return the summary from the chat result summary
|
||||
return True, {"content": result.summary}
|
||||
|
||||
except Exception as e:
|
||||
# Handle any errors during execution
|
||||
return True, {"content": f"Error running group chat: {str(e)}"}
|
||||
|
||||
# Register the reply function with the wrapper agent
|
||||
group_chat_agent.register_reply(
|
||||
trigger=[ConversableAgent, None],
|
||||
reply_func=group_chat_reply,
|
||||
remove_other_reply_funcs=True, # Use only this reply function
|
||||
)
|
||||
|
||||
# After the group chat completes, transition back to the parent agent
|
||||
group_chat_agent.handoffs.set_after_work(AgentTarget(parent_agent))
|
||||
|
||||
return group_chat_agent
|
||||
@@ -0,0 +1,151 @@
|
||||
# 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, Type, Union
|
||||
|
||||
from pydantic import BaseModel, field_validator
|
||||
|
||||
from ....doc_utils import export_module
|
||||
from ..context_str import ContextStr
|
||||
from ..group_tool_executor import GroupToolExecutor
|
||||
from ..speaker_selection_result import SpeakerSelectionResult
|
||||
from .transition_target import TransitionTarget
|
||||
from .transition_utils import __AGENT_WRAPPER_PREFIX__
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# Avoid circular import
|
||||
from ...conversable_agent import ConversableAgent
|
||||
from ...groupchat import GroupChat
|
||||
|
||||
__all__ = ["GroupManagerTarget"]
|
||||
|
||||
|
||||
def prepare_groupchat_auto_speaker(
|
||||
groupchat: "GroupChat",
|
||||
last_group_agent: "ConversableAgent",
|
||||
group_chat_manager_selection_msg: Optional[Any],
|
||||
) -> None:
|
||||
"""Prepare the group chat for auto speaker selection, includes updating or restore the groupchat speaker selection message.
|
||||
|
||||
Tool Executor and wrapped agents will be removed from the available agents list.
|
||||
|
||||
Args:
|
||||
groupchat (GroupChat): GroupChat instance.
|
||||
last_group_agent ("ConversableAgent"): The last group agent for which the LLM config is used
|
||||
group_chat_manager_selection_msg (GroupManagerSelectionMessage): Optional message to use for the agent selection (in internal group chat).
|
||||
"""
|
||||
from ...groupchat import SELECT_SPEAKER_PROMPT_TEMPLATE
|
||||
|
||||
def substitute_agentlist(template: str) -> str:
|
||||
# Run through group chat's string substitution first for {agentlist}
|
||||
# We need to do this so that the next substitution doesn't fail with agentlist
|
||||
# and we can remove the tool executor and wrapped chats from the available agents list
|
||||
agent_list = [
|
||||
agent
|
||||
for agent in groupchat.agents
|
||||
if not isinstance(agent, GroupToolExecutor) and not agent.name.startswith(__AGENT_WRAPPER_PREFIX__)
|
||||
]
|
||||
|
||||
groupchat.select_speaker_prompt_template = template
|
||||
return groupchat.select_speaker_prompt(agent_list)
|
||||
|
||||
# Use the default speaker selection prompt if one is not specified, otherwise use the specified one
|
||||
groupchat.select_speaker_prompt_template = substitute_agentlist(
|
||||
SELECT_SPEAKER_PROMPT_TEMPLATE
|
||||
if group_chat_manager_selection_msg is None
|
||||
else group_chat_manager_selection_msg.get_message(last_group_agent)
|
||||
)
|
||||
|
||||
|
||||
# GroupManagerSelectionMessage protocol and implementations
|
||||
@export_module("autogen.agentchat.group")
|
||||
class GroupManagerSelectionMessage(BaseModel):
|
||||
"""Base class for all GroupManager selection message types."""
|
||||
|
||||
def get_message(self, agent: "ConversableAgent") -> str:
|
||||
"""Get the formatted message."""
|
||||
raise NotImplementedError("Requires subclasses to implement.")
|
||||
|
||||
|
||||
@export_module("autogen.agentchat.group")
|
||||
class GroupManagerSelectionMessageString(GroupManagerSelectionMessage):
|
||||
"""Selection message that uses a plain string template."""
|
||||
|
||||
message: str
|
||||
|
||||
def get_message(self, agent: "ConversableAgent") -> str:
|
||||
"""Get the message string."""
|
||||
return self.message
|
||||
|
||||
|
||||
@export_module("autogen.agentchat.group")
|
||||
class GroupManagerSelectionMessageContextStr(GroupManagerSelectionMessage):
|
||||
"""Selection message that uses a ContextStr template."""
|
||||
|
||||
context_str_template: str
|
||||
|
||||
# We will replace {agentlist} with another term and return it later for use with the internal group chat auto speaker selection
|
||||
# Otherwise our format will fail
|
||||
@field_validator("context_str_template", mode="before")
|
||||
def _replace_agentlist_placeholder(cls: Type["GroupManagerSelectionMessageContextStr"], v: Any) -> Union[str, Any]: # noqa: N805
|
||||
"""Replace {agentlist} placeholder before validation/assignment."""
|
||||
if isinstance(v, str):
|
||||
if "{agentlist}" in v:
|
||||
return v.replace("{agentlist}", "<<agent_list>>") # Perform the replacement
|
||||
else:
|
||||
return v # If no replacement is needed, return the original value
|
||||
return ""
|
||||
|
||||
def get_message(self, agent: "ConversableAgent") -> str:
|
||||
"""Get the formatted message with context variables substituted."""
|
||||
context_str = ContextStr(template=self.context_str_template)
|
||||
format_result = context_str.format(agent.context_variables)
|
||||
if format_result is None:
|
||||
return ""
|
||||
|
||||
return format_result.replace(
|
||||
"<<agent_list>>", "{agentlist}"
|
||||
) # Restore agentlist so it can be substituted by the internal group chat auto speaker selection
|
||||
|
||||
|
||||
class GroupManagerTarget(TransitionTarget):
|
||||
"""Target that represents an agent by name."""
|
||||
|
||||
selection_message: Optional[GroupManagerSelectionMessage] = None
|
||||
|
||||
def can_resolve_for_speaker_selection(self) -> bool:
|
||||
"""Check if the target can resolve for speaker selection."""
|
||||
return True
|
||||
|
||||
def resolve(
|
||||
self,
|
||||
groupchat: "GroupChat",
|
||||
current_agent: "ConversableAgent",
|
||||
user_agent: Optional["ConversableAgent"],
|
||||
) -> SpeakerSelectionResult:
|
||||
"""Resolve to the speaker selection for the group."""
|
||||
if self.selection_message is not None:
|
||||
prepare_groupchat_auto_speaker(groupchat, current_agent, self.selection_message)
|
||||
|
||||
return SpeakerSelectionResult(speaker_selection_method="auto")
|
||||
|
||||
def display_name(self) -> str:
|
||||
"""Get the display name for the target."""
|
||||
return "the group manager"
|
||||
|
||||
def normalized_name(self) -> str:
|
||||
"""Get a normalized name for the target that has no spaces, used for function calling"""
|
||||
return self.display_name()
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""String representation for AgentTarget, can be shown as a function call message."""
|
||||
return "Transfer to the group manager"
|
||||
|
||||
def needs_agent_wrapper(self) -> bool:
|
||||
"""Check if the target needs to be wrapped in an agent."""
|
||||
return False
|
||||
|
||||
def create_wrapper_agent(self, parent_agent: "ConversableAgent", index: int) -> "ConversableAgent":
|
||||
"""Create a wrapper agent for the target if needed."""
|
||||
raise NotImplementedError("GroupManagerTarget does not require wrapping in an agent.")
|
||||
@@ -0,0 +1,413 @@
|
||||
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import random
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..speaker_selection_result import SpeakerSelectionResult
|
||||
from .transition_utils import __AGENT_WRAPPER_PREFIX__
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# Avoid circular import
|
||||
from ...conversable_agent import ConversableAgent
|
||||
from ...groupchat import GroupChat
|
||||
|
||||
__all__ = [
|
||||
"AgentNameTarget",
|
||||
"AgentTarget",
|
||||
"AskUserTarget",
|
||||
"NestedChatTarget",
|
||||
"RandomAgentTarget",
|
||||
"RevertToUserTarget",
|
||||
"StayTarget",
|
||||
"TerminateTarget",
|
||||
"TransitionTarget",
|
||||
]
|
||||
|
||||
# Common options for transitions
|
||||
# terminate: Terminate the conversation
|
||||
# revert_to_user: Revert to the user agent
|
||||
# stay: Stay with the current agent
|
||||
# group_manager: Use the group manager (auto speaker selection)
|
||||
# ask_user: Use the user manager (ask the user, aka manual)
|
||||
# TransitionOption = Literal["terminate", "revert_to_user", "stay", "group_manager", "ask_user"]
|
||||
|
||||
|
||||
class TransitionTarget(BaseModel):
|
||||
"""Base class for all transition targets across OnCondition, OnContextCondition, and after work."""
|
||||
|
||||
def can_resolve_for_speaker_selection(self) -> bool:
|
||||
"""Check if the target can resolve to an option for speaker selection (Agent, 'None' to end, Str for speaker selection method). In the case of a nested chat, this will return False as it should be encapsulated in an agent."""
|
||||
return False
|
||||
|
||||
def resolve(
|
||||
self,
|
||||
groupchat: "GroupChat",
|
||||
current_agent: "ConversableAgent",
|
||||
user_agent: Optional["ConversableAgent"],
|
||||
) -> SpeakerSelectionResult:
|
||||
"""Resolve to a speaker selection result (Agent, None for termination, or str for speaker selection method)."""
|
||||
raise NotImplementedError("Requires subclasses to implement.")
|
||||
|
||||
def display_name(self) -> str:
|
||||
"""Get the display name for the target."""
|
||||
raise NotImplementedError("Requires subclasses to implement.")
|
||||
|
||||
def normalized_name(self) -> str:
|
||||
"""Get a normalized name for the target that has no spaces, used for function calling"""
|
||||
raise NotImplementedError("Requires subclasses to implement.")
|
||||
|
||||
def needs_agent_wrapper(self) -> bool:
|
||||
"""Check if the target needs to be wrapped in an agent."""
|
||||
raise NotImplementedError("Requires subclasses to implement.")
|
||||
|
||||
def create_wrapper_agent(self, parent_agent: "ConversableAgent", index: int) -> "ConversableAgent":
|
||||
"""Create a wrapper agent for the target if needed."""
|
||||
raise NotImplementedError("Requires subclasses to implement.")
|
||||
|
||||
|
||||
class AgentTarget(TransitionTarget):
|
||||
"""Target that represents a direct agent reference."""
|
||||
|
||||
agent_name: str
|
||||
|
||||
def __init__(self, agent: "ConversableAgent", **data: Any) -> None: # type: ignore[no-untyped-def]
|
||||
# Store the name from the agent for serialization
|
||||
super().__init__(agent_name=agent.name, **data)
|
||||
|
||||
def can_resolve_for_speaker_selection(self) -> bool:
|
||||
"""Check if the target can resolve for speaker selection."""
|
||||
return True
|
||||
|
||||
def resolve(
|
||||
self,
|
||||
groupchat: "GroupChat",
|
||||
current_agent: "ConversableAgent",
|
||||
user_agent: Optional["ConversableAgent"],
|
||||
) -> SpeakerSelectionResult:
|
||||
"""Resolve to the actual agent object from the groupchat."""
|
||||
return SpeakerSelectionResult(agent_name=self.agent_name)
|
||||
|
||||
def display_name(self) -> str:
|
||||
"""Get the display name for the target."""
|
||||
return f"{self.agent_name}"
|
||||
|
||||
def normalized_name(self) -> str:
|
||||
"""Get a normalized name for the target that has no spaces, used for function calling"""
|
||||
return self.display_name()
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""String representation for AgentTarget, can be shown as a function call message."""
|
||||
return f"Transfer to {self.agent_name}"
|
||||
|
||||
def needs_agent_wrapper(self) -> bool:
|
||||
"""Check if the target needs to be wrapped in an agent."""
|
||||
return False
|
||||
|
||||
def create_wrapper_agent(self, parent_agent: "ConversableAgent", index: int) -> "ConversableAgent":
|
||||
"""Create a wrapper agent for the target if needed."""
|
||||
raise NotImplementedError("AgentTarget does not require wrapping in an agent.")
|
||||
|
||||
|
||||
class AgentNameTarget(TransitionTarget):
|
||||
"""Target that represents an agent by name."""
|
||||
|
||||
agent_name: str
|
||||
|
||||
def __init__(self, agent_name: str, **data: Any) -> None:
|
||||
"""Initialize with agent name as a positional parameter."""
|
||||
super().__init__(agent_name=agent_name, **data)
|
||||
|
||||
def can_resolve_for_speaker_selection(self) -> bool:
|
||||
"""Check if the target can resolve for speaker selection."""
|
||||
return True
|
||||
|
||||
def resolve(
|
||||
self,
|
||||
groupchat: "GroupChat",
|
||||
current_agent: "ConversableAgent",
|
||||
user_agent: Optional["ConversableAgent"],
|
||||
) -> SpeakerSelectionResult:
|
||||
"""Resolve to the agent name string."""
|
||||
return SpeakerSelectionResult(agent_name=self.agent_name)
|
||||
|
||||
def display_name(self) -> str:
|
||||
"""Get the display name for the target."""
|
||||
return f"{self.agent_name}"
|
||||
|
||||
def normalized_name(self) -> str:
|
||||
"""Get a normalized name for the target that has no spaces, used for function calling"""
|
||||
return self.display_name()
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""String representation for AgentTarget, can be shown as a function call message."""
|
||||
return f"Transfer to {self.agent_name}"
|
||||
|
||||
def needs_agent_wrapper(self) -> bool:
|
||||
"""Check if the target needs to be wrapped in an agent."""
|
||||
return False
|
||||
|
||||
def create_wrapper_agent(self, parent_agent: "ConversableAgent", index: int) -> "ConversableAgent":
|
||||
"""Create a wrapper agent for the target if needed."""
|
||||
raise NotImplementedError("AgentNameTarget does not require wrapping in an agent.")
|
||||
|
||||
|
||||
class NestedChatTarget(TransitionTarget):
|
||||
"""Target that represents a nested chat configuration."""
|
||||
|
||||
nested_chat_config: dict[str, Any]
|
||||
|
||||
def can_resolve_for_speaker_selection(self) -> bool:
|
||||
"""Check if the target can resolve for speaker selection. For NestedChatTarget the nested chat must be encapsulated into an agent."""
|
||||
return False
|
||||
|
||||
def resolve(
|
||||
self,
|
||||
groupchat: "GroupChat",
|
||||
current_agent: "ConversableAgent",
|
||||
user_agent: Optional["ConversableAgent"],
|
||||
) -> SpeakerSelectionResult:
|
||||
"""Resolve to the nested chat configuration."""
|
||||
raise NotImplementedError(
|
||||
"NestedChatTarget does not support the resolve method. An agent should be used to encapsulate this nested chat and then the target changed to an AgentTarget."
|
||||
)
|
||||
|
||||
def display_name(self) -> str:
|
||||
"""Get the display name for the target."""
|
||||
return "a nested chat"
|
||||
|
||||
def normalized_name(self) -> str:
|
||||
"""Get a normalized name for the target that has no spaces, used for function calling"""
|
||||
return "nested_chat"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""String representation for AgentTarget, can be shown as a function call message."""
|
||||
return "Transfer to nested chat"
|
||||
|
||||
def needs_agent_wrapper(self) -> bool:
|
||||
"""Check if the target needs to be wrapped in an agent. NestedChatTarget must be wrapped in an agent."""
|
||||
return True
|
||||
|
||||
def create_wrapper_agent(self, parent_agent: "ConversableAgent", index: int) -> "ConversableAgent":
|
||||
"""Create a wrapper agent for the nested chat."""
|
||||
from ...conversable_agent import ConversableAgent # to avoid circular import - NEED SOLUTION
|
||||
|
||||
nested_chat_agent = ConversableAgent(name=f"{__AGENT_WRAPPER_PREFIX__}nested_{parent_agent.name}_{index + 1}")
|
||||
|
||||
nested_chat_agent.register_nested_chats(
|
||||
self.nested_chat_config["chat_queue"],
|
||||
reply_func_from_nested_chats=self.nested_chat_config.get("reply_func_from_nested_chats")
|
||||
or "summary_from_nested_chats",
|
||||
config=self.nested_chat_config.get("config"),
|
||||
trigger=lambda sender: True,
|
||||
position=0,
|
||||
use_async=self.nested_chat_config.get("use_async", False),
|
||||
)
|
||||
|
||||
# After the nested chat is complete, transfer back to the parent agent
|
||||
nested_chat_agent.handoffs.set_after_work(AgentTarget(parent_agent))
|
||||
|
||||
return nested_chat_agent
|
||||
|
||||
|
||||
class TerminateTarget(TransitionTarget):
|
||||
"""Target that represents a termination of the conversation."""
|
||||
|
||||
def can_resolve_for_speaker_selection(self) -> bool:
|
||||
"""Check if the target can resolve for speaker selection."""
|
||||
return True
|
||||
|
||||
def resolve(
|
||||
self,
|
||||
groupchat: "GroupChat",
|
||||
current_agent: "ConversableAgent",
|
||||
user_agent: Optional["ConversableAgent"],
|
||||
) -> SpeakerSelectionResult:
|
||||
"""Resolve to termination."""
|
||||
return SpeakerSelectionResult(terminate=True)
|
||||
|
||||
def display_name(self) -> str:
|
||||
"""Get the display name for the target."""
|
||||
return "Terminate"
|
||||
|
||||
def normalized_name(self) -> str:
|
||||
"""Get a normalized name for the target that has no spaces, used for function calling"""
|
||||
return "terminate"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""String representation for AgentTarget, can be shown as a function call message."""
|
||||
return "Terminate"
|
||||
|
||||
def needs_agent_wrapper(self) -> bool:
|
||||
"""Check if the target needs to be wrapped in an agent."""
|
||||
return False
|
||||
|
||||
def create_wrapper_agent(self, parent_agent: "ConversableAgent", index: int) -> "ConversableAgent":
|
||||
"""Create a wrapper agent for the target if needed."""
|
||||
raise NotImplementedError("TerminateTarget does not require wrapping in an agent.")
|
||||
|
||||
|
||||
class StayTarget(TransitionTarget):
|
||||
"""Target that represents staying with the current agent."""
|
||||
|
||||
def can_resolve_for_speaker_selection(self) -> bool:
|
||||
"""Check if the target can resolve for speaker selection."""
|
||||
return True
|
||||
|
||||
def resolve(
|
||||
self,
|
||||
groupchat: "GroupChat",
|
||||
current_agent: "ConversableAgent",
|
||||
user_agent: Optional["ConversableAgent"],
|
||||
) -> SpeakerSelectionResult:
|
||||
"""Resolve to staying with the current agent."""
|
||||
return SpeakerSelectionResult(agent_name=current_agent.name)
|
||||
|
||||
def display_name(self) -> str:
|
||||
"""Get the display name for the target."""
|
||||
return "Stay"
|
||||
|
||||
def normalized_name(self) -> str:
|
||||
"""Get a normalized name for the target that has no spaces, used for function calling"""
|
||||
return "stay"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""String representation for AgentTarget, can be shown as a function call message."""
|
||||
return "Stay with agent"
|
||||
|
||||
def needs_agent_wrapper(self) -> bool:
|
||||
"""Check if the target needs to be wrapped in an agent."""
|
||||
return False
|
||||
|
||||
def create_wrapper_agent(self, parent_agent: "ConversableAgent", index: int) -> "ConversableAgent":
|
||||
"""Create a wrapper agent for the target if needed."""
|
||||
raise NotImplementedError("StayTarget does not require wrapping in an agent.")
|
||||
|
||||
|
||||
class RevertToUserTarget(TransitionTarget):
|
||||
"""Target that represents reverting to the user agent."""
|
||||
|
||||
def can_resolve_for_speaker_selection(self) -> bool:
|
||||
"""Check if the target can resolve for speaker selection."""
|
||||
return True
|
||||
|
||||
def resolve(
|
||||
self,
|
||||
groupchat: "GroupChat",
|
||||
current_agent: "ConversableAgent",
|
||||
user_agent: Optional["ConversableAgent"],
|
||||
) -> SpeakerSelectionResult:
|
||||
"""Resolve to reverting to the user agent."""
|
||||
if user_agent is None:
|
||||
raise ValueError("User agent must be provided to the chat for the revert_to_user option.")
|
||||
return SpeakerSelectionResult(agent_name=user_agent.name)
|
||||
|
||||
def display_name(self) -> str:
|
||||
"""Get the display name for the target."""
|
||||
return "Revert to User"
|
||||
|
||||
def normalized_name(self) -> str:
|
||||
"""Get a normalized name for the target that has no spaces, used for function calling"""
|
||||
return "revert_to_user"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""String representation for AgentTarget, can be shown as a function call message."""
|
||||
return "Revert to User"
|
||||
|
||||
def needs_agent_wrapper(self) -> bool:
|
||||
"""Check if the target needs to be wrapped in an agent."""
|
||||
return False
|
||||
|
||||
def create_wrapper_agent(self, parent_agent: "ConversableAgent", index: int) -> "ConversableAgent":
|
||||
"""Create a wrapper agent for the target if needed."""
|
||||
raise NotImplementedError("RevertToUserTarget does not require wrapping in an agent.")
|
||||
|
||||
|
||||
class AskUserTarget(TransitionTarget):
|
||||
"""Target that represents asking the user for input."""
|
||||
|
||||
def can_resolve_for_speaker_selection(self) -> bool:
|
||||
"""Check if the target can resolve for speaker selection."""
|
||||
return True
|
||||
|
||||
def resolve(
|
||||
self,
|
||||
groupchat: "GroupChat",
|
||||
current_agent: "ConversableAgent",
|
||||
user_agent: Optional["ConversableAgent"],
|
||||
) -> SpeakerSelectionResult:
|
||||
"""Resolve to asking the user for input."""
|
||||
return SpeakerSelectionResult(speaker_selection_method="manual")
|
||||
|
||||
def display_name(self) -> str:
|
||||
"""Get the display name for the target."""
|
||||
return "Ask User"
|
||||
|
||||
def normalized_name(self) -> str:
|
||||
"""Get a normalized name for the target that has no spaces, used for function calling"""
|
||||
return "ask_user"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""String representation for AgentTarget, can be shown as a function call message."""
|
||||
return "Ask User"
|
||||
|
||||
def needs_agent_wrapper(self) -> bool:
|
||||
"""Check if the target needs to be wrapped in an agent."""
|
||||
return False
|
||||
|
||||
def create_wrapper_agent(self, parent_agent: "ConversableAgent", index: int) -> "ConversableAgent":
|
||||
"""Create a wrapper agent for the target if needed."""
|
||||
raise NotImplementedError("AskUserTarget does not require wrapping in an agent.")
|
||||
|
||||
|
||||
class RandomAgentTarget(TransitionTarget):
|
||||
"""Target that represents a random selection from a list of agents."""
|
||||
|
||||
agent_names: list[str]
|
||||
nominated_name: str = "<Not Randomly Selected Yet>"
|
||||
|
||||
def __init__(self, agents: list["ConversableAgent"], **data: Any) -> None: # type: ignore[no-untyped-def]
|
||||
# Store the name from the agent for serialization
|
||||
super().__init__(agent_names=[agent.name for agent in agents], **data)
|
||||
|
||||
def can_resolve_for_speaker_selection(self) -> bool:
|
||||
"""Check if the target can resolve for speaker selection."""
|
||||
return True
|
||||
|
||||
def resolve(
|
||||
self,
|
||||
groupchat: "GroupChat",
|
||||
current_agent: "ConversableAgent",
|
||||
user_agent: Optional["ConversableAgent"],
|
||||
) -> SpeakerSelectionResult:
|
||||
"""Resolve to the actual agent object from the groupchat, choosing a random agent (except the current one)"""
|
||||
# Randomly select the next agent
|
||||
self.nominated_name = random.choice([name for name in self.agent_names if name != current_agent.name])
|
||||
|
||||
return SpeakerSelectionResult(agent_name=self.nominated_name)
|
||||
|
||||
def display_name(self) -> str:
|
||||
"""Get the display name for the target."""
|
||||
return self.nominated_name
|
||||
|
||||
def normalized_name(self) -> str:
|
||||
"""Get a normalized name for the target that has no spaces, used for function calling"""
|
||||
return self.display_name()
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""String representation for RandomAgentTarget, can be shown as a function call message."""
|
||||
return f"Transfer to {self.nominated_name}"
|
||||
|
||||
def needs_agent_wrapper(self) -> bool:
|
||||
"""Check if the target needs to be wrapped in an agent."""
|
||||
return False
|
||||
|
||||
def create_wrapper_agent(self, parent_agent: "ConversableAgent", index: int) -> "ConversableAgent":
|
||||
"""Create a wrapper agent for the target if needed."""
|
||||
raise NotImplementedError("RandomAgentTarget does not require wrapping in an agent.")
|
||||
|
||||
|
||||
# TODO: Consider adding a SequentialChatTarget class
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Prefix for all wrapped agent names
|
||||
__AGENT_WRAPPER_PREFIX__ = "wrapped_"
|
||||
Reference in New Issue
Block a user