From 6bc7a9bfb0bdc797f374d2a2d75c64da0b5b0328 Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Tue, 21 Nov 2023 08:32:23 -0800 Subject: [PATCH 01/18] Added an agent description field that can be distinct from the system_message, and be used to for orchestration (e.g., GroupChatManager, etc.) --- autogen/agentchat/assistant_agent.py | 3 +++ autogen/agentchat/conversable_agent.py | 4 ++++ autogen/agentchat/groupchat.py | 4 ++-- autogen/agentchat/user_proxy_agent.py | 29 ++++++++++++++++-------- test/agentchat/test_conversable_agent.py | 21 +++++++++++++++++ 5 files changed, 50 insertions(+), 11 deletions(-) diff --git a/autogen/agentchat/assistant_agent.py b/autogen/agentchat/assistant_agent.py index b39b5f75f80e..812381dc0047 100644 --- a/autogen/agentchat/assistant_agent.py +++ b/autogen/agentchat/assistant_agent.py @@ -35,6 +35,9 @@ def __init__( max_consecutive_auto_reply: Optional[int] = None, human_input_mode: Optional[str] = "NEVER", code_execution_config: Optional[Union[Dict, Literal[False]]] = False, + description: Optional[ + str + ] = "A helpful and general-purpose AI assistant that has strong language skills, Python skills, and Linux command line skills.", **kwargs, ): """ diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 5252f526c0b4..b39c465ea0bb 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -58,11 +58,14 @@ def __init__( code_execution_config: Optional[Union[Dict, Literal[False]]] = None, llm_config: Optional[Union[Dict, Literal[False]]] = None, default_auto_reply: Optional[Union[str, Dict, None]] = "", + description: Optional[str] = None, ): """ Args: name (str): name of the agent. system_message (str): system message for the ChatCompletion inference. + description (str): a short description of the agent. This description is used by other agents + (e.g. the GroupChatManager) to decide when to call upon this agent. (Default: system_message) is_termination_msg (function): a function that takes a message in the form of a dictionary and returns a boolean value indicating if this received message is a termination message. The dict can contain the following keys: "content", "role", "name", "function_call". @@ -104,6 +107,7 @@ def __init__( # a dictionary of conversations, default value is list self._oai_messages = defaultdict(list) self._oai_system_message = [{"content": system_message, "role": "system"}] + self.description = description if description is not None else system_message self._is_termination_msg = ( is_termination_msg if is_termination_msg is not None else (lambda x: x.get("content") == "TERMINATE") ) diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index a59f035fb89e..f94a83852a52 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -192,9 +192,9 @@ def _participant_roles(self, agents: List[Agent] = None) -> str: for agent in agents: if agent.system_message.strip() == "": logger.warning( - f"The agent '{agent.name}' has an empty system_message, and may not work well with GroupChat." + f"The agent '{agent.name}' has an empty description, and may not work well with GroupChat." ) - roles.append(f"{agent.name}: {agent.system_message}") + roles.append(f"{agent.name}: {agent.description}") return "\n".join(roles) def _mentioned_agents(self, message_content: str, agents: List[Agent]) -> Dict: diff --git a/autogen/agentchat/user_proxy_agent.py b/autogen/agentchat/user_proxy_agent.py index 8a218b1fe492..8586fed2b914 100644 --- a/autogen/agentchat/user_proxy_agent.py +++ b/autogen/agentchat/user_proxy_agent.py @@ -1,6 +1,13 @@ from .conversable_agent import ConversableAgent from typing import Callable, Dict, Literal, Optional, Union +# Default UserProxyAgent.description values, based on human_input_mode +DEFAULT_USER_PROXY_AGENT_DESCRIPTIONS = { + "ALWAYS": "An attentive HUMAN user who can answer questions about the task, and can perform tasks such as running Python code or inputting command line commands at a Linux terminal and reporting back the execution results.", + "TERMINATE": "A user that can run Python code or input command line commands at a Linux terminal and report back the execution results.", + "NEVER": "A user that can run Python code or input command line commands at a Linux terminal and report back the execution results.", +} + class UserProxyAgent(ConversableAgent): """(In preview) A proxy agent for the user, that can execute code and provide feedback to the other agents. @@ -26,6 +33,7 @@ def __init__( default_auto_reply: Optional[Union[str, Dict, None]] = "", llm_config: Optional[Union[Dict, Literal[False]]] = False, system_message: Optional[str] = "", + description: Optional[str] = None, ): """ Args: @@ -70,13 +78,16 @@ def __init__( Only used when llm_config is not False. Use it to reprogram the agent. """ super().__init__( - name, - system_message, - is_termination_msg, - max_consecutive_auto_reply, - human_input_mode, - function_map, - code_execution_config, - llm_config, - default_auto_reply, + name=name, + system_message=system_message, + is_termination_msg=is_termination_msg, + max_consecutive_auto_reply=max_consecutive_auto_reply, + human_input_mode=human_input_mode, + function_map=function_map, + code_execution_config=code_execution_config, + llm_config=llm_config, + default_auto_reply=default_auto_reply, + description=description + if description is not None + else DEFAULT_USER_PROXY_AGENT_DESCRIPTIONS[human_input_mode], ) diff --git a/test/agentchat/test_conversable_agent.py b/test/agentchat/test_conversable_agent.py index d38193338f4c..839a598b2dae 100644 --- a/test/agentchat/test_conversable_agent.py +++ b/test/agentchat/test_conversable_agent.py @@ -275,6 +275,27 @@ def test_conversable_agent(): with pytest.raises(KeyError): dummy_agent_1.last_message(dummy_agent_3) + # Check the description field + assert dummy_agent_1.description != dummy_agent_1.system_message + assert dummy_agent_2.description == dummy_agent_2.system_message + + dummy_agent_4 = ConversableAgent( + name="dummy_agent_4", + system_message="The fourth dummy agent used for testing.", + llm_config=False, + human_input_mode="TERMINATE", + ) + assert dummy_agent_4.description == "The fourth dummy agent used for testing." # Same as system message + + dummy_agent_5 = ConversableAgent( + name="dummy_agent_5", + system_message="", + description="The fifth dummy agent used for testing.", + llm_config=False, + human_input_mode="TERMINATE", + ) + assert dummy_agent_5.description == "The fifth dummy agent used for testing." # Same as system message + def test_generate_reply(): def add_num(num_to_be_added): From 855dc665539794ce47025ee9a2e174bb0b8a2043 Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Tue, 21 Nov 2023 09:20:43 -0800 Subject: [PATCH 02/18] Added debugging. --- autogen/agentchat/groupchat.py | 1 + 1 file changed, 1 insertion(+) diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index f94a83852a52..89183382ebdc 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -155,6 +155,7 @@ def select_speaker(self, last_speaker: Agent, selector: ConversableAgent): # auto speaker selection selector.update_system_message(self.select_speaker_msg(agents)) + logger.warning("Speaker selector prompt:\n" + selector.system_message) final, name = selector.generate_oai_reply( self.messages + [ From 04a8a75936d0a55111c01ff36a76828c5c8caaac Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Tue, 21 Nov 2023 09:37:35 -0800 Subject: [PATCH 03/18] Moved default descriptions to constants. --- autogen/agentchat/assistant_agent.py | 7 ++++--- autogen/agentchat/user_proxy_agent.py | 16 ++++++++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/autogen/agentchat/assistant_agent.py b/autogen/agentchat/assistant_agent.py index 812381dc0047..a2a50d2cd020 100644 --- a/autogen/agentchat/assistant_agent.py +++ b/autogen/agentchat/assistant_agent.py @@ -26,6 +26,8 @@ class AssistantAgent(ConversableAgent): Reply "TERMINATE" in the end when everything is done. """ + DEFAULT_DESCRIPTION = "A helpful and general-purpose AI assistant that has strong language skills, Python skills, and Linux command line skills." + def __init__( self, name: str, @@ -35,9 +37,7 @@ def __init__( max_consecutive_auto_reply: Optional[int] = None, human_input_mode: Optional[str] = "NEVER", code_execution_config: Optional[Union[Dict, Literal[False]]] = False, - description: Optional[ - str - ] = "A helpful and general-purpose AI assistant that has strong language skills, Python skills, and Linux command line skills.", + description: Optional[str] = DEFAULT_DESCRIPTION, **kwargs, ): """ @@ -65,5 +65,6 @@ def __init__( human_input_mode, code_execution_config=code_execution_config, llm_config=llm_config, + description=description, **kwargs, ) diff --git a/autogen/agentchat/user_proxy_agent.py b/autogen/agentchat/user_proxy_agent.py index 8586fed2b914..8dfa59d47c3f 100644 --- a/autogen/agentchat/user_proxy_agent.py +++ b/autogen/agentchat/user_proxy_agent.py @@ -1,13 +1,6 @@ from .conversable_agent import ConversableAgent from typing import Callable, Dict, Literal, Optional, Union -# Default UserProxyAgent.description values, based on human_input_mode -DEFAULT_USER_PROXY_AGENT_DESCRIPTIONS = { - "ALWAYS": "An attentive HUMAN user who can answer questions about the task, and can perform tasks such as running Python code or inputting command line commands at a Linux terminal and reporting back the execution results.", - "TERMINATE": "A user that can run Python code or input command line commands at a Linux terminal and report back the execution results.", - "NEVER": "A user that can run Python code or input command line commands at a Linux terminal and report back the execution results.", -} - class UserProxyAgent(ConversableAgent): """(In preview) A proxy agent for the user, that can execute code and provide feedback to the other agents. @@ -22,6 +15,13 @@ class UserProxyAgent(ConversableAgent): To customize the initial message when a conversation starts, override `generate_init_message` method. """ + # Default UserProxyAgent.description values, based on human_input_mode + DEFAULT_USER_PROXY_AGENT_DESCRIPTIONS = { + "ALWAYS": "An attentive HUMAN user who can answer questions about the task, and can perform tasks such as running Python code or inputting command line commands at a Linux terminal and reporting back the execution results.", + "TERMINATE": "A user that can run Python code or input command line commands at a Linux terminal and report back the execution results.", + "NEVER": "A user that can run Python code or input command line commands at a Linux terminal and report back the execution results.", + } + def __init__( self, name: str, @@ -89,5 +89,5 @@ def __init__( default_auto_reply=default_auto_reply, description=description if description is not None - else DEFAULT_USER_PROXY_AGENT_DESCRIPTIONS[human_input_mode], + else self.DEFAULT_USER_PROXY_AGENT_DESCRIPTIONS[human_input_mode], ) From 082af1b970bbdecfb3e3e556fd10d91e35fa69c2 Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Tue, 21 Nov 2023 09:51:31 -0800 Subject: [PATCH 04/18] Fixed conditions under which the assistant uses the default description. --- autogen/agentchat/assistant_agent.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/autogen/agentchat/assistant_agent.py b/autogen/agentchat/assistant_agent.py index a2a50d2cd020..20315788d1aa 100644 --- a/autogen/agentchat/assistant_agent.py +++ b/autogen/agentchat/assistant_agent.py @@ -37,7 +37,7 @@ def __init__( max_consecutive_auto_reply: Optional[int] = None, human_input_mode: Optional[str] = "NEVER", code_execution_config: Optional[Union[Dict, Literal[False]]] = False, - description: Optional[str] = DEFAULT_DESCRIPTION, + description: Optional[str] = None, **kwargs, ): """ @@ -68,3 +68,9 @@ def __init__( description=description, **kwargs, ) + + # Update the provided desciption if None, and we are using the default system_message, + # then use the default description. + if description is None: + if system_message == self.DEFAULT_SYSTEM_MESSAGE: + self.description = self.DEFAULT_DESCRIPTION From 60a80faf1d9acbc2df3b8dcb5cc38ce3732f4be7 Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Tue, 21 Nov 2023 09:57:33 -0800 Subject: [PATCH 05/18] Removed debugging. --- autogen/agentchat/groupchat.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 89183382ebdc..223f23aacee7 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -67,7 +67,7 @@ def next_agent(self, agent: Agent, agents: List[Agent]) -> Agent: def select_speaker_msg(self, agents: List[Agent]): """Return the message for selecting the next speaker.""" return f"""You are in a role play game. The following roles are available: -{self._participant_roles(agents)}. +{self._participant_roles(agents)} Read the following conversation. Then select the next role from {[agent.name for agent in agents]} to play. Only return the role.""" @@ -155,7 +155,6 @@ def select_speaker(self, last_speaker: Agent, selector: ConversableAgent): # auto speaker selection selector.update_system_message(self.select_speaker_msg(agents)) - logger.warning("Speaker selector prompt:\n" + selector.system_message) final, name = selector.generate_oai_reply( self.messages + [ From acf28fdb6e445c0ae5d256169a3fa501ec8c567f Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Tue, 21 Nov 2023 10:19:04 -0800 Subject: [PATCH 06/18] Updated GroupChat prompt. --- autogen/agentchat/groupchat.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 223f23aacee7..7858423a409e 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -66,11 +66,13 @@ def next_agent(self, agent: Agent, agents: List[Agent]) -> Agent: def select_speaker_msg(self, agents: List[Agent]): """Return the message for selecting the next speaker.""" - return f"""You are in a role play game. The following roles are available: + return f"""You are moderating a conversation between the following people: + {self._participant_roles(agents)} -Read the following conversation. -Then select the next role from {[agent.name for agent in agents]} to play. Only return the role.""" +Read the following conversation, then carefully consider who should speak next based on who's input would be most valued in this moment (e.g., to make the most progress on the task). +You must select only one speaker to go next, and you must only return their name (i.e., from the set [{[agent.name for agent in agents]}]) +""" def manual_select_speaker(self, agents: List[Agent]) -> Agent: """Manually select the next speaker.""" @@ -160,7 +162,7 @@ def select_speaker(self, last_speaker: Agent, selector: ConversableAgent): + [ { "role": "system", - "content": f"Read the above conversation. Then select the next role from {[agent.name for agent in agents]} to play. Only return the role.", + "content": f"Read the above conversation. Then select the next role from {[agent.name for agent in agents]} to speak next. Only return the role.", } ] ) @@ -194,7 +196,7 @@ def _participant_roles(self, agents: List[Agent] = None) -> str: logger.warning( f"The agent '{agent.name}' has an empty description, and may not work well with GroupChat." ) - roles.append(f"{agent.name}: {agent.description}") + roles.append(f"{agent.name}: {agent.description}".strip()) return "\n".join(roles) def _mentioned_agents(self, message_content: str, agents: List[Agent]) -> Dict: From f2a6dd8219aababf45a03d1b17ac62a8601f4d3d Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Tue, 21 Nov 2023 10:24:00 -0800 Subject: [PATCH 07/18] Re-added debugging. --- autogen/agentchat/groupchat.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 7858423a409e..314969b0b76e 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -157,6 +157,8 @@ def select_speaker(self, last_speaker: Agent, selector: ConversableAgent): # auto speaker selection selector.update_system_message(self.select_speaker_msg(agents)) + logger.warning("GroupChat selection prompt:\n" + selector.system_message) + final, name = selector.generate_oai_reply( self.messages + [ @@ -166,6 +168,9 @@ def select_speaker(self, last_speaker: Agent, selector: ConversableAgent): } ] ) + + logger.warning("GroupChat selection result:" + name) + if not final: # the LLM client is None, thus no reply is generated. Use round robin instead. return self.next_agent(last_speaker, agents) From 74282118ece6f1bd8aa16ed647f583a1ed135f9e Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Tue, 21 Nov 2023 10:29:18 -0800 Subject: [PATCH 08/18] Removed double [[ ]]. --- autogen/agentchat/groupchat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 314969b0b76e..5e5f1d9c5ebc 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -71,7 +71,7 @@ def select_speaker_msg(self, agents: List[Agent]): {self._participant_roles(agents)} Read the following conversation, then carefully consider who should speak next based on who's input would be most valued in this moment (e.g., to make the most progress on the task). -You must select only one speaker to go next, and you must only return their name (i.e., from the set [{[agent.name for agent in agents]}]) +You must select only one speaker to go next, and you must only return their name (i.e., from the set {[agent.name for agent in agents]}) """ def manual_select_speaker(self, agents: List[Agent]) -> Agent: From b4965313a09e8fb25baaac85328ff23482cf6830 Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Tue, 21 Nov 2023 10:37:08 -0800 Subject: [PATCH 09/18] Another update to GroupSelection prompt. --- autogen/agentchat/groupchat.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 5e5f1d9c5ebc..7cf28f04c135 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -70,7 +70,8 @@ def select_speaker_msg(self, agents: List[Agent]): {self._participant_roles(agents)} -Read the following conversation, then carefully consider who should speak next based on who's input would be most valued in this moment (e.g., to make the most progress on the task). +Read the following conversation, then carefully consider who should speak next based on who's input would be most valued in this moment (e.g., to make the most progress on the task). Speakers do not need equal speaking time. You may even ignore non-relevant participants. Your focus is on efficiently driving progress toward task completion. + You must select only one speaker to go next, and you must only return their name (i.e., from the set {[agent.name for agent in agents]}) """ From b2da91d1137328f1faf9d66b41ce0e3e1f20a382 Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Tue, 21 Nov 2023 10:57:19 -0800 Subject: [PATCH 10/18] Changed 'people' to 'participants' since agents are not people. --- autogen/agentchat/groupchat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 7cf28f04c135..ebbad28a6189 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -66,7 +66,7 @@ def next_agent(self, agent: Agent, agents: List[Agent]) -> Agent: def select_speaker_msg(self, agents: List[Agent]): """Return the message for selecting the next speaker.""" - return f"""You are moderating a conversation between the following people: + return f"""You are moderating a conversation between the following participants: {self._participant_roles(agents)} @@ -165,7 +165,7 @@ def select_speaker(self, last_speaker: Agent, selector: ConversableAgent): + [ { "role": "system", - "content": f"Read the above conversation. Then select the next role from {[agent.name for agent in agents]} to speak next. Only return the role.", + "content": f"Read the above conversation, then carefully consider who should speak next based on who's input would be most valued in this moment (e.g., to make the most progress on the task). Speakers do not need equal speaking time. You may even ignore non-relevant participants. Your focus is on efficiently driving progress toward task completion. Select the next speaker from {[agent.name for agent in agents]}. Only return the role.", } ] ) From cb2489d5c4b31047826a838343084ab2d354182f Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Tue, 21 Nov 2023 10:59:32 -0800 Subject: [PATCH 11/18] Changed 'role' to 'name' --- autogen/agentchat/groupchat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index ebbad28a6189..970dcccb8438 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -165,7 +165,7 @@ def select_speaker(self, last_speaker: Agent, selector: ConversableAgent): + [ { "role": "system", - "content": f"Read the above conversation, then carefully consider who should speak next based on who's input would be most valued in this moment (e.g., to make the most progress on the task). Speakers do not need equal speaking time. You may even ignore non-relevant participants. Your focus is on efficiently driving progress toward task completion. Select the next speaker from {[agent.name for agent in agents]}. Only return the role.", + "content": f"Read the above conversation, then carefully consider who should speak next based on who's input would be most valued in this moment (e.g., to make the most progress on the task). Speakers do not need equal speaking time. You may even ignore non-relevant participants. Your focus is on efficiently driving progress toward task completion. Select the next speaker from {[agent.name for agent in agents]}. Only return their name.", } ] ) From 5e277ca07b8ed7787e41bdcc44be25674f6779d9 Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Tue, 21 Nov 2023 12:22:01 -0800 Subject: [PATCH 12/18] Removed debugging statements. --- autogen/agentchat/groupchat.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 970dcccb8438..095f0d2e6c99 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -158,19 +158,19 @@ def select_speaker(self, last_speaker: Agent, selector: ConversableAgent): # auto speaker selection selector.update_system_message(self.select_speaker_msg(agents)) - logger.warning("GroupChat selection prompt:\n" + selector.system_message) + # logger.warning("GroupChat selection prompt:\n" + selector.system_message) final, name = selector.generate_oai_reply( self.messages + [ { "role": "system", - "content": f"Read the above conversation, then carefully consider who should speak next based on who's input would be most valued in this moment (e.g., to make the most progress on the task). Speakers do not need equal speaking time. You may even ignore non-relevant participants. Your focus is on efficiently driving progress toward task completion. Select the next speaker from {[agent.name for agent in agents]}. Only return their name.", + "content": f"Read the above conversation, then carefully consider who should speak next based on who's input would be most valued in this moment to make progress on the task. Select the next speaker from {[agent.name for agent in agents]}. Only return their name.", } ] ) - logger.warning("GroupChat selection result:" + name) + # logger.warning("GroupChat selection result: " + name) if not final: # the LLM client is None, thus no reply is generated. Use round robin instead. From 822f311dfe16aa179a00a89280366e8de9a2156a Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Fri, 1 Dec 2023 14:55:36 -0800 Subject: [PATCH 13/18] Restored the default prompt. Created a contrib class with new prompt. --- .../agentchat/contrib/group_chat_moderator.py | 48 +++++++++++++++ autogen/agentchat/groupchat.py | 32 +++++----- .../contrib/test_group_chat_moderator.py | 58 +++++++++++++++++++ 3 files changed, 119 insertions(+), 19 deletions(-) create mode 100644 autogen/agentchat/contrib/group_chat_moderator.py create mode 100644 test/agentchat/contrib/test_group_chat_moderator.py diff --git a/autogen/agentchat/contrib/group_chat_moderator.py b/autogen/agentchat/contrib/group_chat_moderator.py new file mode 100644 index 000000000000..b428a6308ec2 --- /dev/null +++ b/autogen/agentchat/contrib/group_chat_moderator.py @@ -0,0 +1,48 @@ +from typing import Callable, Dict, Optional, Union, Tuple, List, Any +from autogen import GroupChat, Agent +import logging + +logger = logging.getLogger(__name__) + + +class GroupChatModerator(GroupChat): + """(Experimental) TODO""" + + def __init__( + self, + agents: List[Agent], + messages: List[Dict], + max_round: int = 10, + admin_name: str = "Admin", + func_call_filter: bool = True, + speaker_selection_method: str = "auto", + allow_repeat_speaker: bool = True, + ): + """ + Args: + TODO + """ + super().__init__( + agents=agents, + messages=messages, + max_round=max_round, + admin_name=admin_name, + func_call_filter=func_call_filter, + speaker_selection_method=speaker_selection_method, + allow_repeat_speaker=allow_repeat_speaker, + ) + + def select_speaker_msg(self, agents: List[Agent]): + """Return the system message for selecting the next speaker. This is always the *first* message in the context.""" + return f"""You are moderating a conversation between the following participants: + +{self._participant_roles(agents)} + +Read the following conversation, then carefully consider who should speak next based on who's input would be most valued in this moment (e.g., to make the most progress on the task). Speakers do not need equal speaking time. You may even ignore non-relevant participants. Your focus is on efficiently driving progress toward task completion. + +You must select only one speaker to go next, and you must only return their name (i.e., from the set {[agent.name for agent in agents]}) +""" + + def select_speaker_prompt(self, agents: List[Agent]): + """Return the floating system prompt selecting the next speaker. This is always the *last* message in the context.""" + return f"Read the above conversation, then carefully consider who should speak next based on who's input would be most valued in this moment to make progress on the task. Select the next speaker from {[agent.name for agent in agents]}. Only return their name." diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 42ccb6d97cec..ee30d07112e0 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -1,6 +1,7 @@ import logging import sys import random +import json from dataclasses import dataclass from typing import Dict, List, Optional, Union import re @@ -65,15 +66,16 @@ def next_agent(self, agent: Agent, agents: List[Agent]) -> Agent: return self.agents[(offset + i) % len(self.agents)] def select_speaker_msg(self, agents: List[Agent]): - """Return the message for selecting the next speaker.""" - return f"""You are moderating a conversation between the following participants: + """Return the system message for selecting the next speaker. This is always the *first* message in the context.""" + return f"""You are in a role play game. The following roles are available: +{self._participant_roles(agents)}. -{self._participant_roles(agents)} +Read the following conversation. +Then select the next role from {[agent.name for agent in agents]} to play. Only return the role.""" -Read the following conversation, then carefully consider who should speak next based on who's input would be most valued in this moment (e.g., to make the most progress on the task). Speakers do not need equal speaking time. You may even ignore non-relevant participants. Your focus is on efficiently driving progress toward task completion. - -You must select only one speaker to go next, and you must only return their name (i.e., from the set {[agent.name for agent in agents]}) -""" + def select_speaker_prompt(self, agents: List[Agent]): + """Return the floating system prompt selecting the next speaker. This is always the *last* message in the context.""" + return f"Read the above conversation. Then select the next role from {[agent.name for agent in agents]} to play. Only return the role." def manual_select_speaker(self, agents: List[Agent]) -> Agent: """Manually select the next speaker.""" @@ -158,19 +160,11 @@ def select_speaker(self, last_speaker: Agent, selector: ConversableAgent): # auto speaker selection selector.update_system_message(self.select_speaker_msg(agents)) - # logger.warning("GroupChat selection prompt:\n" + selector.system_message) - - final, name = selector.generate_oai_reply( - self.messages - + [ - { - "role": "system", - "content": f"Read the above conversation, then carefully consider who should speak next based on who's input would be most valued in this moment to make progress on the task. Select the next speaker from {[agent.name for agent in agents]}. Only return their name.", - } - ] - ) + context = self.messages + [{"role": "system", "content": self.select_speaker_prompt(agents)}] - # logger.warning("GroupChat selection result: " + name) + logger.warning("GroupChat selection context: " + json.dumps(context, indent=4)) + final, name = selector.generate_oai_reply(context) + logger.warning("GroupChat selection result: {name}") if not final: # the LLM client is None, thus no reply is generated. Use round robin instead. diff --git a/test/agentchat/contrib/test_group_chat_moderator.py b/test/agentchat/contrib/test_group_chat_moderator.py new file mode 100644 index 000000000000..dc7582b7561b --- /dev/null +++ b/test/agentchat/contrib/test_group_chat_moderator.py @@ -0,0 +1,58 @@ +import pytest +import autogen + +from autogen.agentchat.contrib.group_chat_moderator import GroupChatModerator + + +def test_moderation_prompt(): + agent1 = autogen.ConversableAgent( + "alice", + system_message="You are Alice, a helpful AI assistant.", + max_consecutive_auto_reply=2, + human_input_mode="NEVER", + llm_config=False, + default_auto_reply="This is alice speaking.", + ) + agent2 = autogen.ConversableAgent( + "bob", + description="You are Bob, a helpful AI assistant.", + max_consecutive_auto_reply=2, + human_input_mode="NEVER", + llm_config=False, + default_auto_reply="This is bob speaking.", + ) + agent3 = autogen.ConversableAgent( + "sam", + max_consecutive_auto_reply=2, + human_input_mode="NEVER", + llm_config=False, + default_auto_reply="This is sam speaking.", + ) + agents = [agent1, agent2, agent3] + groupchat = GroupChatModerator(agents=agents, messages=[], max_round=2) + + system_prompt = groupchat.select_speaker_msg(agents) + + # Make sure it contains the text we expect. + assert ( + "Read the following conversation, then carefully consider who should speak next based on who's input would be most valued in this moment (e.g., to make the most progress on the task)." + in system_prompt + ) + + # Make sure expected prompt or descriptions are present. + assert "You are Alice, a helpful AI assistant." in system_prompt # provided prompt + assert "You are Bob, a helpful AI assistant." in system_prompt # provided description + assert "You are a helpful AI Assistant" in system_prompt # default prompt + + selection_prompt = groupchat.select_speaker_prompt(agents) + assert ( + "Read the above conversation, then carefully consider who should speak next based on who's input would be most valued in this moment to make progress on the task." + in selection_prompt + ) + assert "alice" in selection_prompt + assert "bob" in selection_prompt + assert "sam" in selection_prompt + + +if __name__ == "__main__": + test_moderation_prompt() From bc65c5a370cb7dfe2fd470366ee6bb7aa0356f24 Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Fri, 1 Dec 2023 15:17:00 -0800 Subject: [PATCH 14/18] Fixed documentation. --- autogen/agentchat/contrib/group_chat_moderator.py | 7 ++++--- autogen/agentchat/conversable_agent.py | 4 ++-- autogen/agentchat/groupchat.py | 3 --- autogen/agentchat/user_proxy_agent.py | 2 ++ 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/autogen/agentchat/contrib/group_chat_moderator.py b/autogen/agentchat/contrib/group_chat_moderator.py index b428a6308ec2..3f5ee5dc7616 100644 --- a/autogen/agentchat/contrib/group_chat_moderator.py +++ b/autogen/agentchat/contrib/group_chat_moderator.py @@ -6,7 +6,8 @@ class GroupChatModerator(GroupChat): - """(Experimental) TODO""" + """(Experimental) A variation of the standard GroupChat class, but with an alternate prompting strategy + that focus on conversation moderation rather than role play. A drop-in replacement for GroupChat.""" def __init__( self, @@ -19,8 +20,8 @@ def __init__( allow_repeat_speaker: bool = True, ): """ - Args: - TODO + GroupChatModerator uses the same initilization and constructor as GroupChat. + Please refer to [GroupChat](groupchat#groupchat-objects). """ super().__init__( agents=agents, diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index b39c465ea0bb..5b48cf2cdd94 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -64,8 +64,6 @@ def __init__( Args: name (str): name of the agent. system_message (str): system message for the ChatCompletion inference. - description (str): a short description of the agent. This description is used by other agents - (e.g. the GroupChatManager) to decide when to call upon this agent. (Default: system_message) is_termination_msg (function): a function that takes a message in the form of a dictionary and returns a boolean value indicating if this received message is a termination message. The dict can contain the following keys: "content", "role", "name", "function_call". @@ -102,6 +100,8 @@ def __init__( for available options. To disable llm-based auto reply, set to False. default_auto_reply (str or dict or None): default auto reply when no code execution or llm-based reply is generated. + description (str): a short description of the agent. This description is used by other agents + (e.g. the GroupChatManager) to decide when to call upon this agent. (Default: system_message) """ super().__init__(name) # a dictionary of conversations, default value is list diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index ee30d07112e0..49f26e93090c 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -161,10 +161,7 @@ def select_speaker(self, last_speaker: Agent, selector: ConversableAgent): # auto speaker selection selector.update_system_message(self.select_speaker_msg(agents)) context = self.messages + [{"role": "system", "content": self.select_speaker_prompt(agents)}] - - logger.warning("GroupChat selection context: " + json.dumps(context, indent=4)) final, name = selector.generate_oai_reply(context) - logger.warning("GroupChat selection result: {name}") if not final: # the LLM client is None, thus no reply is generated. Use round robin instead. diff --git a/autogen/agentchat/user_proxy_agent.py b/autogen/agentchat/user_proxy_agent.py index 8dfa59d47c3f..9ca0d8b0ead6 100644 --- a/autogen/agentchat/user_proxy_agent.py +++ b/autogen/agentchat/user_proxy_agent.py @@ -76,6 +76,8 @@ def __init__( Default to false, which disables llm-based auto reply. system_message (str): system message for ChatCompletion inference. Only used when llm_config is not False. Use it to reprogram the agent. + description (str): a short description of the agent. This description is used by other agents + (e.g. the GroupChatManager) to decide when to call upon this agent. (Default: system_message) """ super().__init__( name=name, From 86b8355e92fee0b57e89368439f7c26d44e4a97a Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Fri, 1 Dec 2023 15:30:15 -0800 Subject: [PATCH 15/18] Removed broken link. --- autogen/agentchat/contrib/group_chat_moderator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autogen/agentchat/contrib/group_chat_moderator.py b/autogen/agentchat/contrib/group_chat_moderator.py index 3f5ee5dc7616..a3c0ef421561 100644 --- a/autogen/agentchat/contrib/group_chat_moderator.py +++ b/autogen/agentchat/contrib/group_chat_moderator.py @@ -21,7 +21,7 @@ def __init__( ): """ GroupChatModerator uses the same initilization and constructor as GroupChat. - Please refer to [GroupChat](groupchat#groupchat-objects). + Please refer to the GroupChat constructor for more information. """ super().__init__( agents=agents, From b0b9b5acf6dccc1bc28081e29571f7032e2c803a Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Fri, 1 Dec 2023 16:06:19 -0800 Subject: [PATCH 16/18] Fixed a warning message. --- autogen/agentchat/groupchat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 49f26e93090c..03ef28ef623b 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -189,7 +189,7 @@ def _participant_roles(self, agents: List[Agent] = None) -> str: roles = [] for agent in agents: - if agent.system_message.strip() == "": + if agent.description.strip() == "": logger.warning( f"The agent '{agent.name}' has an empty description, and may not work well with GroupChat." ) From 9528aeb0398c65ba131424f220de56fe30fca20d Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Wed, 6 Dec 2023 23:12:23 -0800 Subject: [PATCH 17/18] Removed GroupChatModerator contrib. Will re-add in another PR --- .../agentchat/contrib/group_chat_moderator.py | 49 ---------------- .../contrib/test_group_chat_moderator.py | 58 ------------------- 2 files changed, 107 deletions(-) delete mode 100644 autogen/agentchat/contrib/group_chat_moderator.py delete mode 100644 test/agentchat/contrib/test_group_chat_moderator.py diff --git a/autogen/agentchat/contrib/group_chat_moderator.py b/autogen/agentchat/contrib/group_chat_moderator.py deleted file mode 100644 index a3c0ef421561..000000000000 --- a/autogen/agentchat/contrib/group_chat_moderator.py +++ /dev/null @@ -1,49 +0,0 @@ -from typing import Callable, Dict, Optional, Union, Tuple, List, Any -from autogen import GroupChat, Agent -import logging - -logger = logging.getLogger(__name__) - - -class GroupChatModerator(GroupChat): - """(Experimental) A variation of the standard GroupChat class, but with an alternate prompting strategy - that focus on conversation moderation rather than role play. A drop-in replacement for GroupChat.""" - - def __init__( - self, - agents: List[Agent], - messages: List[Dict], - max_round: int = 10, - admin_name: str = "Admin", - func_call_filter: bool = True, - speaker_selection_method: str = "auto", - allow_repeat_speaker: bool = True, - ): - """ - GroupChatModerator uses the same initilization and constructor as GroupChat. - Please refer to the GroupChat constructor for more information. - """ - super().__init__( - agents=agents, - messages=messages, - max_round=max_round, - admin_name=admin_name, - func_call_filter=func_call_filter, - speaker_selection_method=speaker_selection_method, - allow_repeat_speaker=allow_repeat_speaker, - ) - - def select_speaker_msg(self, agents: List[Agent]): - """Return the system message for selecting the next speaker. This is always the *first* message in the context.""" - return f"""You are moderating a conversation between the following participants: - -{self._participant_roles(agents)} - -Read the following conversation, then carefully consider who should speak next based on who's input would be most valued in this moment (e.g., to make the most progress on the task). Speakers do not need equal speaking time. You may even ignore non-relevant participants. Your focus is on efficiently driving progress toward task completion. - -You must select only one speaker to go next, and you must only return their name (i.e., from the set {[agent.name for agent in agents]}) -""" - - def select_speaker_prompt(self, agents: List[Agent]): - """Return the floating system prompt selecting the next speaker. This is always the *last* message in the context.""" - return f"Read the above conversation, then carefully consider who should speak next based on who's input would be most valued in this moment to make progress on the task. Select the next speaker from {[agent.name for agent in agents]}. Only return their name." diff --git a/test/agentchat/contrib/test_group_chat_moderator.py b/test/agentchat/contrib/test_group_chat_moderator.py deleted file mode 100644 index dc7582b7561b..000000000000 --- a/test/agentchat/contrib/test_group_chat_moderator.py +++ /dev/null @@ -1,58 +0,0 @@ -import pytest -import autogen - -from autogen.agentchat.contrib.group_chat_moderator import GroupChatModerator - - -def test_moderation_prompt(): - agent1 = autogen.ConversableAgent( - "alice", - system_message="You are Alice, a helpful AI assistant.", - max_consecutive_auto_reply=2, - human_input_mode="NEVER", - llm_config=False, - default_auto_reply="This is alice speaking.", - ) - agent2 = autogen.ConversableAgent( - "bob", - description="You are Bob, a helpful AI assistant.", - max_consecutive_auto_reply=2, - human_input_mode="NEVER", - llm_config=False, - default_auto_reply="This is bob speaking.", - ) - agent3 = autogen.ConversableAgent( - "sam", - max_consecutive_auto_reply=2, - human_input_mode="NEVER", - llm_config=False, - default_auto_reply="This is sam speaking.", - ) - agents = [agent1, agent2, agent3] - groupchat = GroupChatModerator(agents=agents, messages=[], max_round=2) - - system_prompt = groupchat.select_speaker_msg(agents) - - # Make sure it contains the text we expect. - assert ( - "Read the following conversation, then carefully consider who should speak next based on who's input would be most valued in this moment (e.g., to make the most progress on the task)." - in system_prompt - ) - - # Make sure expected prompt or descriptions are present. - assert "You are Alice, a helpful AI assistant." in system_prompt # provided prompt - assert "You are Bob, a helpful AI assistant." in system_prompt # provided description - assert "You are a helpful AI Assistant" in system_prompt # default prompt - - selection_prompt = groupchat.select_speaker_prompt(agents) - assert ( - "Read the above conversation, then carefully consider who should speak next based on who's input would be most valued in this moment to make progress on the task." - in selection_prompt - ) - assert "alice" in selection_prompt - assert "bob" in selection_prompt - assert "sam" in selection_prompt - - -if __name__ == "__main__": - test_moderation_prompt() From 378d3b6390ace303b63ba22c7193b00dcd450fda Mon Sep 17 00:00:00 2001 From: Adam Fourney Date: Wed, 6 Dec 2023 23:17:57 -0800 Subject: [PATCH 18/18] Resolving comment. --- autogen/agentchat/groupchat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 7ce919f7a24e..f21e4b643ef4 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -82,7 +82,7 @@ def select_speaker_msg(self, agents: List[Agent]) -> str: Read the following conversation. Then select the next role from {[agent.name for agent in agents]} to play. Only return the role.""" - def select_speaker_prompt(self, agents: List[Agent]): + def select_speaker_prompt(self, agents: List[Agent]) -> str: """Return the floating system prompt selecting the next speaker. This is always the *last* message in the context.""" return f"Read the above conversation. Then select the next role from {[agent.name for agent in agents]} to play. Only return the role."