diff --git a/examples/environments/chatroom/chatroom_example.py b/examples/environments/chatroom/chatroom_example.py index 7d192654e..f552f6c17 100644 --- a/examples/environments/chatroom/chatroom_example.py +++ b/examples/environments/chatroom/chatroom_example.py @@ -59,22 +59,17 @@ def main(args: argparse.Namespace) -> None: ), role="system", ) - r = ChatRoom(name="chat", announcement=ann, to_dist=args.use_dist) + r = ChatRoom( + name="chat", + announcement=ann, + model_config_name=YOUR_MODEL_CONFIGURATION_NAME, + to_dist=args.use_dist, + ) # Setup the persona of Alice, Bob and Carol alice = ChatRoomAgent( # Game Art Designer name="Alice", - sys_prompt=r"""You are a game art designer named Alice. """ - r"""Programmer Bob and game planner Carol are your colleagues, """ - r"""and you need to collaborate with them to complete an open """ - r"""world game. Please ask appropriate question to planner or """ - r"""generate appropriate responses in this work group based on """ - r"""the following chat history. When you need to mention someone, """ - r"""you can use @ to remind them. You only need to output Alice's """ - r"""possible replies, without giving anyone else's replies or """ - r"""continuing the conversation. When the discussion is complete, """ - r"""you need to reply with a message containing 'Goodbye' to """ - r"""indicate exiting the conversation.""", + sys_prompt=r"""You are a game art designer named Alice.""", model_config_name=YOUR_MODEL_CONFIGURATION_NAME, to_dist=args.use_dist, ) @@ -82,17 +77,7 @@ def main(args: argparse.Namespace) -> None: bob = ChatRoomAgent( # Game Programmer name="Bob", - sys_prompt=r"""You are a game programmer named Bob. """ - r"""Art designer Alice and game planner Carol are your colleagues, """ - r"""and you need to collaborate with them to complete an open """ - r"""world game. Please ask appropriate questions or generate """ - r"""appropriate responses in the work group based on the following """ - r"""historical records. When you need to mention someone, you can """ - r"""use @ to remind them. You only need to output Bob's possible """ - r"""replies, without giving anyone else's replies or continuing """ - r"""the conversation. When the discussion is complete, you need """ - r"""to reply with a message containing 'Goodbye' to indicate """ - r"""exiting the conversation.""", + sys_prompt=r"""You are a game programmer named Bob.""", model_config_name=YOUR_MODEL_CONFIGURATION_NAME, to_dist=args.use_dist, ) @@ -100,16 +85,7 @@ def main(args: argparse.Namespace) -> None: carol = ChatRoomAgent( # Game Designer name="Carol", - sys_prompt=r"""You are a game planner named Carol. """ - r"""Programmer Bob and art designer Alice are your colleagues, """ - r"""and you need to guide them in developing an open world game. """ - r"""Please generate a suitable response in this work group based """ - r"""on the following chat history. When you need to mention """ - r"""someone, you can use @ to remind them. You only need to output """ - r"""Carol's possible replies, without giving anyone else's replies """ - r"""or continuing the conversation. When the discussion is """ - r"""complete, you need to reply with a message containing """ - r"""'Goodbye' to indicate exiting the conversation.""", + sys_prompt=r"""You are a game planner named Carol.""", model_config_name=YOUR_MODEL_CONFIGURATION_NAME, to_dist=args.use_dist, ) diff --git a/examples/environments/chatroom/chatroom_with_assistant_example.py b/examples/environments/chatroom/chatroom_with_assistant_example.py index 13dbbf1d5..3983a2711 100644 --- a/examples/environments/chatroom/chatroom_with_assistant_example.py +++ b/examples/environments/chatroom/chatroom_with_assistant_example.py @@ -70,12 +70,12 @@ def main(args: argparse.Namespace) -> None: bob = ChatRoomAgentWithAssistant( name="Bob", - sys_prompt=r"""You are Bob's chat room assistant and he is """ + sys_prompt=r"""You are Bob's chat room assistant and Bob is """ r"""currently unable to reply to messages. Please generate a """ - r"""suitable response based on the following chat history. """ - r"""The content you reply to must be based on the chat history. """ - r"""Please refuse to reply to questions that are beyond the scope """ - r"""of the chat history.""", + r"""suitable response based on the following chat history without """ + r"""reasoning. The content you reply to must be based on the chat """ + r"""history. Please refuse to reply to questions that are beyond """ + r"""the scope of the chat history.""", model_config_name=YOUR_MODEL_CONFIGURATION_NAME, to_dist=args.use_dist, timeout=args.timeout, @@ -176,11 +176,10 @@ def main(args: argparse.Namespace) -> None: # Setup the persona of Carol carol = ChatRoomAgent( name="Carol", - sys_prompt=r"""You are Carol, and now you need to interview Bob. """ - r"""Just ask him where he is from, which school he graduated from, """ - r"""his profession, and his hobbies. At the end of the interview, """ - r"""please output a reply containing Goodbye to indicate the end """ - r"""of the conversation.""", + sys_prompt="""You are Carol, and now you need to interview Bob. """ + """Just ask him where he is from, which school he graduated from, """ + """his profession, and his hobbies. You'd better only ask one """ + """question at a time.""", model_config_name=YOUR_MODEL_CONFIGURATION_NAME, to_dist=args.use_dist, ) diff --git a/examples/environments/chatroom/envs/chatroom.py b/examples/environments/chatroom/envs/chatroom.py index a17b34119..7e1104199 100644 --- a/examples/environments/chatroom/envs/chatroom.py +++ b/examples/environments/chatroom/envs/chatroom.py @@ -21,6 +21,7 @@ event_func, ) from agentscope.models import ModelResponse +from agentscope.manager import ModelManager from agentscope.studio._client import _studio_client from agentscope.web.gradio.utils import user_input @@ -38,6 +39,22 @@ """ +def format_messages(msgs: Union[Msg, List[Msg]]) -> list[dict]: + """Format the messages""" + messages = [] + if isinstance(msgs, Msg): + msgs = [msgs] + for msg in msgs: + messages.append( + { + "role": msg.role, + "name": msg.name, + "content": str(msg.content), + }, + ) + return messages + + class ChatRoomMember(BasicEnv): """A member of chatroom.""" @@ -92,6 +109,7 @@ class ChatRoom(BasicEnv): def __init__( self, name: str = None, + model_config_name: str = None, announcement: Msg = None, participants: List[AgentBase] = None, all_history: bool = False, @@ -126,6 +144,12 @@ def __init__( ) self.history = [] self.announcement = announcement + self.member_introduction = {} + if model_config_name is not None: + model_manager = ModelManager.get_instance() + self.model = model_manager.get_model_by_config_name( + model_config_name, + ) @event_func def join(self, agent: AgentBase) -> bool: @@ -137,15 +161,17 @@ def join(self, agent: AgentBase) -> bool: agent=agent, history_idx=len(self.history), ) + self.member_introduction[agent.name] = agent.introduction self.add_listener("speak", Notifier()) return True @event_func def leave(self, agent: AgentBase) -> bool: """Remove the participant agent from the chatroom.""" - if agent.agent_id not in self.children: + if agent.name not in self.children: return False - del self.children[agent.agent_id] + del self.children[agent.name] + del self.member_introduction[agent.name] return True @event_func @@ -166,10 +192,32 @@ def get_history(self, agent_name: str) -> List[Msg]: history_idx = self.children[agent_name].history_idx return deepcopy(self.history[history_idx:]) + def get_history_length(self, agent_name: str) -> int: + """Get the length of the history of the agent.""" + if agent_name not in self.children: + return 0 + if self.all_history: + history_idx = 0 + else: + history_idx = self.children[agent_name].history_idx + return len(self.history) - history_idx + def describe(self, agent_name: str, **kwargs: Any) -> str: """Get the description of the chatroom.""" - ann = ( - self.announcement.content if self.announcement.content else "EMPTY" + ann = self.announcement.content if self.announcement.content else "" + members_introduction = "\n\n".join( + [ + f"{name}: {introduction}" + for name, introduction in self.member_introduction.items() + ], + ) + ann += f"\n{members_introduction}\n\n" + ann += ( + """Please generate a suitable response in this work group based""" + """ on the following chat history. When you need to mention """ + """someone, you can use @ to remind them. You only need to """ + f"""output {agent_name}'s possible replies, without giving """ + """anyone else's replies or continuing the conversation.""" ) history = "\n\n".join( [ @@ -240,7 +288,7 @@ def chatting_parse_func(self, response: ModelResponse) -> ModelResponse: pattern = re.compile(pattern_str, re.DOTALL) logger.debug(repr(pattern_str)) logger.debug(response.text) - texts = [s.strip() for s in pattern.split(response.text)] + texts = [s.strip() for s in pattern.split(response.text) if s.strip()] logger.debug(texts) return ModelResponse(text=texts[0]) @@ -249,10 +297,13 @@ def chat_freely( delay: float = 1, interval: float = 5, max_round: int = 10, + agent_name_list: List[str] = None, ) -> None: """Let all agents to chat freely without any preset order""" tasks = [] - for agent_name in self.children.keys(): + if agent_name_list is None: + agent_name_list = list(self.children.keys()) + for agent_name in agent_name_list: task = threading.Thread( target=self.children[agent_name].chat_freely, kwargs={ @@ -272,6 +323,7 @@ def chat_in_sequence(self, agent_name_order: List[str] = None) -> None: Args: agent_name_order (`List[str]`): Order of speakers' names. """ + agent_name_order = agent_name_order or list(self.children.keys()) for agent_name in agent_name_order: self.children[agent_name].chat() @@ -287,6 +339,7 @@ def __init__( def __call__(self, room: Env, event: Event) -> None: names = self.pattern.findall(str(event.args["message"].content)) + names = list(set(names)) for name in names: if name in room.children: @@ -315,6 +368,35 @@ def __init__( # pylint: disable=W0613 sys_prompt=sys_prompt, model_config_name=model_config_name, ) + if self.sys_prompt: + prompt = format_messages( + [ + Msg( + name="user", + role="user", + content=( + f"Please generate a brief character introduction " + f"in one sentence, which based on the following " + f"prompt:\n" + f"Prompt: {sys_prompt}\n" + f"The generated description needs to follow the " + f"following format:\n" + f"[PERSONA BEGIN]\n" + f"Description: One sentence introduction\n" + f"[PERSONA END]" + ), + ), + ], + ) + raw_introduction = self.model(prompt).text + raw_introduction = raw_introduction.split("[PERSONA BEGIN]", 1)[1] + raw_introduction = raw_introduction.split("[PERSONA END]")[0] + self.introduction = raw_introduction.strip() + else: + self.introduction = "" + logger.info(f"introduction: {self.introduction}") + self.room_history_length = 0 + self.room_slient_count = 0 self.room = None self.mentioned_messages = [] self.mentioned_messages_lock = threading.Lock() @@ -327,6 +409,7 @@ def add_mentioned_message(self, msg: Msg) -> None: def join(self, room: ChatRoom) -> bool: """Join a room""" self.room = room + self.room_history_length = self.room.get_history_length(self.name) return room.join(self) def _is_mentioned(self) -> bool: @@ -344,21 +427,29 @@ def _generate_mentioned_prompt(self) -> Tuple[bool, str]: for msg in self.mentioned_messages ], ) + self.mentioned_messages = [] return True, hint return False, "" def _want_to_speak(self, hint: str) -> bool: """Check whether the agent want to speak currently""" - prompt = self.model.format( - Msg(name="system", role="system", content=hint), - Msg( - name="user", - role="user", - content="Based on the CHATROOM." - " Do you want to speak in the chatroom now?\n" - "Speak yes or no.", - ), + hint = ( + f"{self.sys_prompt}\n\nYou are participating in a chatroom.\n" + + hint + ) + prompt = format_messages( + [ + Msg(name="system", role="system", content=hint), + Msg( + name="user", + role="user", + content="Based on the CHATROOM." + " Do you want to or need to speak in the chatroom now?\n" + "Return yes or no.", + ), + ], ) + logger.debug(prompt) response = self.model( prompt, max_retries=3, @@ -382,44 +473,45 @@ def speak( def reply(self, x: Msg = None) -> Msg: """Generate reply to chat room""" + room_history_length = self.room.get_history_length(self.name) + if room_history_length != self.room_history_length: + self.room_history_length = room_history_length + self.room_slient_count = 0 + else: + self.room_slient_count += 1 room_info = self.room.describe(self.name) - system_hint = ( - f"{self.sys_prompt}\n\nYou are participating in a chatroom.\n" - f"\n{room_info}" - ) + reply_hint = "" mentioned, mentioned_hint = self._generate_mentioned_prompt() if mentioned: - # if mentioned, response directly - prompt = self.model.format( - Msg( - name="system", - role="system", - content=system_hint, - ), - Msg( - name="user", - role="user", - content=mentioned_hint, - ), - ) + reply_hint = f"{mentioned_hint}\n{self.name}:" else: # decide whether to speak - if self._want_to_speak(room_info): - prompt = self.model.format( - Msg( - name="system", - role="system", - content=system_hint, - ), - Msg( - name="user", - role="user", - content="Please generate a response based on the " - "CHATROOM.", - ), + if self.room_history_length <= 3 or ( + self.room_slient_count <= 2 and self._want_to_speak(room_info) + ): + reply_hint = ( + f"Please generate a response based on the" + f" CHATROOM. You need only generate response without " + f"reasoning.\n{self.name}:" ) else: return Msg(name="assistant", role="assistant", content="") + user_hint = ( + # f"{self.sys_prompt}\n\n" + f"You are participating in a chatroom.\n" + f"\n{room_info}\n{reply_hint}" + ) + prompt = format_messages( + [ + Msg( + name="system", + role="system", + content=self.sys_prompt, + ), + Msg(name="user", role="user", content=user_hint), + ], + ) + prompt[-1]["content"] = prompt[-1]["content"].strip() logger.debug(prompt) response = self.model( prompt, @@ -429,6 +521,7 @@ def reply(self, x: Msg = None) -> Msg: msg = Msg(name=self.name, content=response, role="assistant") if response: self.speak(msg) + self.room_history_length = self.room.get_history_length(self.name) return msg @@ -442,6 +535,7 @@ def __init__( ) -> None: super().__init__(**kwargs) self.timeout = timeout + self.room_history_length = 0 def reply(self, x: Msg = None) -> Msg: if _studio_client.active: @@ -472,14 +566,34 @@ def reply(self, x: Msg = None) -> Msg: if content is not None: # user input response = content else: # assistant reply - msg_hint = self._generate_mentioned_prompt() - self_msg = Msg(name=self.name, content="", role="assistant") - - history = self.room.get_history(self.agent_id) - prompt = self.model.format( - msg_hint, - history, - self_msg, + room_history_length = self.room.get_history_length(self.name) + if room_history_length == self.room_history_length: + return Msg(name="assistant", role="assistant", content="") + self.room_history_length = room_history_length + room_info = self.room.describe(self.name) + reply_hint = "" + mentioned, mentioned_hint = self._generate_mentioned_prompt() + if mentioned: + reply_hint = f"{mentioned_hint}\n{self.name}:" + else: + reply_hint = ( + f"Please generate a response based on the CHATROOM." + f"\n{self.name}:" + ) + system_hint = ( + f"You are participating in a chatroom.\n" + f"\n{room_info}\n{reply_hint}" + ) + + prompt = format_messages( + [ + Msg( + name=self.name, + content=self.sys_prompt, + role="system", + ), + Msg(name="user", content=system_hint, role="user"), + ], ) logger.debug(prompt) response = self.model( @@ -491,4 +605,5 @@ def reply(self, x: Msg = None) -> Msg: response = "[auto reply] " + response msg = Msg(name=self.name, content=response, role="user") self.speak(msg) + self.room_history_length = self.room.get_history_length(self.name) return msg diff --git a/src/agentscope/rpc/rpc_object.py b/src/agentscope/rpc/rpc_object.py index e72054582..ae35823f4 100644 --- a/src/agentscope/rpc/rpc_object.py +++ b/src/agentscope/rpc/rpc_object.py @@ -189,6 +189,7 @@ def __call__(self, *args: Any, **kwargs: Any) -> Any: ) def __getitem__(self, item: str) -> Any: + self._check_created() return self._call_func("__getitem__", {"args": (item,)}) def _launch_server(self) -> None: diff --git a/tests/environment_test.py b/tests/environment_test.py index a3fa2d982..636cc5ca5 100644 --- a/tests/environment_test.py +++ b/tests/environment_test.py @@ -73,6 +73,7 @@ def __init__( # pylint: disable=W0613 ) -> None: super().__init__(name=name) self.room = None + self.introduction = "" self.event_list = [] def join(self, room: ChatRoom) -> bool: