Skip to content
Closed
155 changes: 126 additions & 29 deletions autogen/agentchat/groupchat.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,109 @@
from typing import Dict, List, Optional, Union
from .agent import Agent
from .conversable_agent import ConversableAgent
from .. import oai
import random


@dataclass
class GroupChat:
"""A group chat class that contains a list of agents and the maximum number of rounds."""

agents: List[Agent]
agents: List[ConversableAgent]
messages: List[Dict]
max_round: int = 10
admin_name: str = "Admin" # the name of the admin agent
llm_config: Optional[Dict] = None
admin_name: Optional[str] = None

@property
def admin(self):
"""
if admin_name is None, then return the first agent in the list
otherwise, return the agent with the name admin_name
"""
if self.admin_name is None:
return self.agents[0]
else:
return self.agent_by_name(self.admin_name)

@property
def agent_names(self) -> List[str]:
"""Return the names of the agents in the group chat."""
return [agent.name for agent in self.agents]
return [agent.name.lower() for agent in self.agents]

def reset(self):
"""Reset the group chat."""
self.messages.clear()

def process_role_play_msgs(self, messages: List[Dict]) -> List[Dict]:
return [
{
"role": "user",
"content": f"""From {message["name"]}:
{message["content"]}""",
}
for message in messages
]

def agent_by_name(self, name: str) -> Agent:
"""Find the next speaker based on the message."""
return self.agents[self.agent_names.index(name)]
return self.agents[self.agent_names.index(name.lower())]

def next_agent(self, agent: Agent) -> Agent:
"""Return the next agent in the list."""
return self.agents[(self.agent_names.index(agent.name) + 1) % len(self.agents)]
return self.agents[(self.agent_names.index(agent.name.lower()) + 1) % len(self.agents)]

def select_speaker_msg(self):
def select_speaker_msgs(self) -> List[Dict]:
"""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()}.
msgs = [
{
"role": "system",
"content": "You are in a role play game. Each conversation must start with 'From {name}:', e.g: From admin: //your message//.",
}
]

Read the following conversation.
Then select the next role from {self.agent_names} to play. Only return the role."""
# # process role information
# # each agent introduce the next agent
# for i in range(len(self.agents)):
# current_agent = self.agents[i]
# next_agent = self.next_agent(current_agent)
# msgs.append({
# "role": "user",
# "content": f'''From {current_agent.name}:
# {next_agent.name}, {next_agent.system_message}''',
# })

def select_speaker(self, last_speaker: Agent, selector: ConversableAgent):
return msgs

def select_speaker(self, last_speaker: Agent):
"""Select the next speaker."""
selector.update_system_message(self.select_speaker_msg())
final, name = selector.generate_oai_reply(
self.messages
+ [
{
"role": "system",
"content": f"Read the above conversation. Then select the next role from {self.agent_names} to play. Only return the role.",
}
]
)
if not final:
# i = self._random.randint(0, len(self._agent_names) - 1) # randomly pick an id
llm_config = self.llm_config

# if self.llm_config is None, randomly select
if llm_config is None:
# search through its agents and randomly select a llm_config from one of them if it exists
# shuffle the agents
llm_configs = [agent.llm_config.copy() for agent in self.agents if isinstance(agent.llm_config, dict)]
if len(llm_configs) > 0:
llm_config = random.choice(llm_configs)
else:
llm_config = None

# if llm_config is still None, then return the next agent
if llm_config is None:
return self.next_agent(last_speaker)

try:
system_messages = self.select_speaker_msgs()
chat_messages = self.process_role_play_msgs(self.messages)
llm_config["stop"] = [":"]
msgs = system_messages + chat_messages
reply = oai.ChatCompletion.create(messages=msgs, **llm_config)
msg = reply["choices"][0]["message"]["content"]
name = msg.split(":")[0].split("From ")[1]
return self.agent_by_name(name)
except ValueError:
return self.next_agent(last_speaker)
except Exception:
return self.admin

def _participant_roles(self):
return "\n".join([f"{agent.name}: {agent.system_message}" for agent in self.agents])
Expand All @@ -84,16 +132,27 @@ def __init__(
system_message=system_message,
**kwargs,
)
self.groupchat = groupchat
self.register_reply(Agent, GroupChatManager.run_chat, config=groupchat, reset_config=GroupChat.reset)
# self._random = random.Random(seed)

def _process_received_message(self, message, sender, silent):
message_with_name = {
"content": message,
"role": "user",
"name": sender.name,
}
self.groupchat.messages.append(message_with_name)
return super()._process_received_message(message, sender, silent)

def run_chat(
self,
messages: Optional[List[Dict]] = None,
sender: Optional[Agent] = None,
config: Optional[GroupChat] = None,
) -> Union[str, Dict, None]:
"""Run a group chat."""

if messages is None:
messages = self._oai_messages[sender]
message = messages[-1]
Expand All @@ -103,19 +162,56 @@ def run_chat(
# set the name to speaker's name if the role is not function
if message["role"] != "function":
message["name"] = speaker.name
groupchat.messages.append(message)
# broadcast the message to all agents except the speaker

# sync the message
msg = {
"content": message["content"],
"name": speaker.name if speaker is not None else "Unknown",
}

if not isinstance(self, GroupChatManager):
groupchat.messages.append(msg)

# distribute the message to all agents
msg_with_name = {
"content": f"""From {msg["name"]}<eof_name>:
{msg["content"]}""",
"role": "user",
}
for agent in groupchat.agents:
if agent != speaker:
self.send(message, agent, request_reply=False, silent=True)
agent.receive(msg_with_name, self, request_reply=False, silent=True)
# self.send(msg_with_name["content"], agent, request_reply=False, silent=True)

if i == groupchat.max_round - 1:
# the last round
break
try:
# select the next speaker
speaker = groupchat.select_speaker(speaker, self)
speaker = groupchat.select_speaker(speaker)

# add <eof_name>: as stop sequence if llm_config is not None
if isinstance(speaker, ConversableAgent) and isinstance(speaker.llm_config, dict):
if "stop" in speaker.llm_config:
speaker.llm_config["stop"].append("<eof_name>:")
else:
speaker.llm_config["stop"] = ["<eof_name>:"]
# let the speaker speak
reply = speaker.generate_reply(sender=self)
# restore the stop sequence
if isinstance(speaker, ConversableAgent) and isinstance(speaker.llm_config, dict):
if "stop" in speaker.llm_config:
speaker.llm_config["stop"].remove("<eof_name>:")

if reply is None:
break
# if reply is 'From xxx', then set reply to xxx, it's your turn to speak
if reply.startswith("From ") and reply.split("From ")[1].lower() in groupchat.agent_names:
name = reply.split("From ")[1]
if name.lower() == speaker.name.lower():
agents_except_speaker = [agent for agent in groupchat.agents if agent != speaker]
speaker = random.choice(agents_except_speaker)
reply = f"{name}, it's your turn to speak."
except KeyboardInterrupt:
# let the admin agent speak if interrupted
if groupchat.admin_name in groupchat.agent_names:
Expand All @@ -127,6 +223,7 @@ def run_chat(
raise
if reply is None:
break

# The speaker sends the message without requesting a reply
speaker.send(reply, self, request_reply=False)
message = self.last_message(speaker)
Expand Down
Loading