From febc592af73db77c458726b755d85f6d1a9b5196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BC=9F=E9=9F=AC?= Date: Fri, 16 Aug 2024 19:47:40 +0800 Subject: [PATCH 1/7] Force reply to human when finishing the current task --- metagpt/prompts/di/role_zero.py | 19 ++++++++++++ metagpt/roles/di/role_zero.py | 54 ++++++++++++++++++++++++++++----- metagpt/roles/di/swe_agent.py | 35 ++++++++++----------- 3 files changed, 83 insertions(+), 25 deletions(-) diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index 3c47ebf5f..f85128b38 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -218,3 +218,22 @@ QUICK_RESPONSE_SYSTEM_PROMPT = """ {role_info} However, you MUST respond to the user message by yourself directly, DON'T ask your team members. """ + +REPORT_TO_HUMAN_PROMPT = """ +# Current Plan +{plan_status} + +Your have just finish a task, Use "RoleZero.reply_to_human" to report what you have done. +The output format is : +```json +[ + {{ + "command_name": "RoleZero.reply_to_human", + "args": {{ + "content": "" + }} + }} +] +``` + +""" diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index c6cbfdfa8..727360ae9 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -26,6 +26,7 @@ from metagpt.prompts.di.role_zero import ( QUICK_THINK_PROMPT, QUICK_THINK_SYSTEM_PROMPT, REGENERATE_PROMPT, + REPORT_TO_HUMAN_PROMPT, ROLE_INSTRUCTION, SYSTEM_PROMPT, THOUGHT_GUIDANCE, @@ -86,6 +87,8 @@ class RoleZero(Role): use_fixed_sop: bool = False requirements_constraints: str = "" # the constraints in user requirements + command_history: list[str] = [] + @model_validator(mode="after") def set_plan_and_tool(self) -> "RoleZero": # We force using this parameter for DataAnalyst @@ -234,21 +237,57 @@ class RoleZero(Role): if self.use_fixed_sop: return await super()._act() - commands, ok = await self._parse_commands() + commands, ok = await self._parse_commands(self.command_rsp) if not ok: error_msg = commands + self.rc.memory.add(UserMessage(content=error_msg)) return error_msg logger.info(f"Commands: \n{commands}") outputs = await self._run_commands(commands) logger.info(f"Commands outputs: \n{outputs}") self.rc.memory.add(UserMessage(content=outputs)) + # Report what is done when finishing the task. + current_command_list = [command["command_name"] for command in commands] + self.command_history.extend(current_command_list) + if self.check_whether_report_to_human(): + memory = self.rc.memory.get(self.memory_k) + memory = await self.parse_browser_actions(memory) + memory = self.parse_images(memory) + plan_status, _ = self._get_plan_status() + prompt = REPORT_TO_HUMAN_PROMPT.format(plan_status=plan_status) + req = self.llm.format_msg(memory + [UserMessage(content=prompt)]) + respond_command = await self.llm.aask(msg=req) + commands, ok = await self._parse_commands(respond_command) + if ok and len(commands) == 1 and commands[0]["command_name"] == "RoleZero.reply_to_human": + cmd = commands[0] + report_result = await self.reply_to_human(cmd["args"]) + self.rc.memory.add(AIMessage(content=respond_command)) + self.rc.memory.add(UserMessage(content=report_result)) + self.command_history.append("RoleZero.reply_to_human") + logger.info(f"Commands outputs: \n{report_result}") + outputs += report_result + return AIMessage( content=f"I have finished the task, please mark my task as finished. Outputs: {outputs}", sent_from=self.name, cause_by=RunCommand, ) + def check_whether_report_to_human(self): + """ "Check whether add reply to human command when finish current task""" + interaction_with_human = ["RoleZero.ask_human", "RoleZero.reply_to_human"] + plan_end_action = ["Plan.finish_current_task", "end"] + flag = 0 + for command_name in self.command_history[::-1]: + if command_name in plan_end_action: + flag |= 1 + elif command_name in interaction_with_human: + flag |= 2 + else: + break + return flag == 1 + async def _react(self) -> Message: # NOTE: Diff 1: Each time landing here means news is observed, set todo to allow news processing in _think self._set_state(0) @@ -332,7 +371,7 @@ class RoleZero(Role): command_rsp = await self.llm.aask(regenerate_req) return command_rsp - async def _parse_commands(self) -> Tuple[List[Dict], bool]: + async def _parse_commands(self, command_rsp) -> Tuple[List[Dict], bool]: """Retrieves commands from the Large Language Model (LLM). This function attempts to retrieve a list of commands from the LLM by @@ -344,20 +383,20 @@ class RoleZero(Role): - A boolean flag indicating success (True) or failure (False). """ try: - commands = CodeParser.parse_code(block=None, lang="json", text=self.command_rsp) + commands = CodeParser.parse_code(block=None, lang="json", text=command_rsp) if commands.endswith("]") and not commands.startswith("["): commands = "[" + commands commands = json.loads(repair_llm_raw_output(output=commands, req_keys=[None], repair_type=RepairType.JSON)) except json.JSONDecodeError as e: - logger.warning(f"Failed to parse JSON for: {self.command_rsp}. Trying to repair...") + logger.warning(f"Failed to parse JSON for: {command_rsp}. Trying to repair...") commands = await self.llm.aask( - msg=JSON_REPAIR_PROMPT.format(json_data=self.command_rsp, json_decode_error=str(e)) + msg=JSON_REPAIR_PROMPT.format(json_data=command_rsp, json_decode_error=str(e)) ) try: commands = json.loads(CodeParser.parse_code(block=None, lang="json", text=commands)) except json.JSONDecodeError: # repair escape error of code and math - commands = CodeParser.parse_code(block=None, lang="json", text=self.command_rsp) + commands = CodeParser.parse_code(block=None, lang="json", text=command_rsp) new_command = repair_escape_error(commands) commands = json.loads( repair_llm_raw_output(output=new_command, req_keys=[None], repair_type=RepairType.JSON) @@ -365,8 +404,7 @@ class RoleZero(Role): except Exception as e: tb = traceback.format_exc() print(tb) - error_msg = UserMessage(content=str(e)) - self.rc.memory.add(error_msg) + error_msg = str(e) return error_msg, False # 为了对LLM不按格式生成进行容错 diff --git a/metagpt/roles/di/swe_agent.py b/metagpt/roles/di/swe_agent.py index e1d2c9613..e90fc3045 100644 --- a/metagpt/roles/di/swe_agent.py +++ b/metagpt/roles/di/swe_agent.py @@ -9,6 +9,7 @@ from metagpt.prompts.di.swe_agent import ( NEXT_STEP_TEMPLATE, ) from metagpt.roles.di.role_zero import RoleZero +from metagpt.schema import Message from metagpt.tools.libs.git import git_create_pull from metagpt.tools.libs.terminal import Bash @@ -32,8 +33,6 @@ class SWEAgent(RoleZero): async def _think(self) -> bool: await self._format_instruction() res = await super()._think() - if self.run_eval: - await self._parse_commands_for_eval() return res def _update_tool_execution(self): @@ -55,6 +54,12 @@ class SWEAgent(RoleZero): bash_state = json.loads(state_output) self.cmd_prompt_current_state = CURRENT_BASH_STATE.format(**bash_state).strip() + async def _act(self) -> Message: + message = await super()._act() + if self.run_eval: + self._parse_commands_for_eval() + return message + async def _parse_commands_for_eval(self): """ Handles actions based on parsed commands. @@ -65,23 +70,19 @@ class SWEAgent(RoleZero): This function is specifically added for SWE bench evaluation. """ # only import when evaluation is needed - from metagpt.tools.swe_agent_commands.swe_agent_utils import extract_patch + if not self.rc.todo: + from metagpt.tools.swe_agent_commands.swe_agent_utils import extract_patch - commands, ok = await self._parse_commands() - if not ok: - return - for cmd in commands: - if "end" != cmd.get("command_name", ""): - return - try: - diff_output = await self.terminal.run("git diff --cached") - clear_diff = extract_patch(diff_output) - logger.info(f"Diff output: \n{clear_diff}") - if clear_diff: - self.output_diff = clear_diff + # swe agent have been stop. it means 'end' or other command which can stop the swe agent have been executed + try: + diff_output = await self.terminal.run("git diff --cached") + clear_diff = extract_patch(diff_output) + logger.info(f"Diff output: \n{clear_diff}") + if clear_diff: + self.output_diff = clear_diff - except Exception as e: - logger.error(f"Error during submission: {e}") + except Exception as e: + logger.error(f"Error during submission: {e}") def _retrieve_experience(self) -> str: return MINIMAL_EXAMPLE From 0246b659b7f7448ef0545697742e89066b44b1d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BC=9F=E9=9F=AC?= Date: Tue, 20 Aug 2024 17:13:15 +0800 Subject: [PATCH 2/7] Add a summary after the agent stops --- metagpt/prompts/di/role_zero.py | 27 ++++++++-------- metagpt/roles/di/role_zero.py | 57 ++++++++++++--------------------- metagpt/roles/di/swe_agent.py | 3 +- metagpt/roles/di/team_leader.py | 2 ++ 4 files changed, 37 insertions(+), 52 deletions(-) diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index f85128b38..2294fa731 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -220,20 +220,19 @@ However, you MUST respond to the user message by yourself directly, DON'T ask yo """ REPORT_TO_HUMAN_PROMPT = """ -# Current Plan -{plan_status} +# Restrictions +{requirements_constraints} -Your have just finish a task, Use "RoleZero.reply_to_human" to report what you have done. -The output format is : -```json -[ - {{ - "command_name": "RoleZero.reply_to_human", - "args": {{ - "content": "" - }} - }} -] -``` +Your have just finish all tasks, Use "RoleZero.reply_to_human" to answer the user requirements. +Report to human what you have done. Do Not ouput any other format. +Your reply is: """ +SUMMARY_PROMPY = """ +# Restrictions +{requirements_constraints} + +You have just completed some tasks. +Summarize the tasks you have accomplished without including detailed information. +If there are any deliverables, list their descriptions and provide their file paths. +""" diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index 727360ae9..48967d7ef 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -28,6 +28,7 @@ from metagpt.prompts.di.role_zero import ( REGENERATE_PROMPT, REPORT_TO_HUMAN_PROMPT, ROLE_INSTRUCTION, + SUMMARY_PROMPY, SYSTEM_PROMPT, THOUGHT_GUIDANCE, ) @@ -68,6 +69,8 @@ class RoleZero(Role): react_mode: Literal["react"] = "react" max_react_loop: int = 20 # used for react mode + # Summary Mode + use_summary: bool = True # Tools tools: list[str] = [] # Use special symbol [""] to indicate use of all registered tools tool_recommender: Optional[ToolRecommender] = None @@ -87,8 +90,6 @@ class RoleZero(Role): use_fixed_sop: bool = False requirements_constraints: str = "" # the constraints in user requirements - command_history: list[str] = [] - @model_validator(mode="after") def set_plan_and_tool(self) -> "RoleZero": # We force using this parameter for DataAnalyst @@ -247,26 +248,24 @@ class RoleZero(Role): logger.info(f"Commands outputs: \n{outputs}") self.rc.memory.add(UserMessage(content=outputs)) - # Report what is done when finishing the task. - current_command_list = [command["command_name"] for command in commands] - self.command_history.extend(current_command_list) - if self.check_whether_report_to_human(): - memory = self.rc.memory.get(self.memory_k) - memory = await self.parse_browser_actions(memory) - memory = self.parse_images(memory) - plan_status, _ = self._get_plan_status() - prompt = REPORT_TO_HUMAN_PROMPT.format(plan_status=plan_status) - req = self.llm.format_msg(memory + [UserMessage(content=prompt)]) - respond_command = await self.llm.aask(msg=req) - commands, ok = await self._parse_commands(respond_command) - if ok and len(commands) == 1 and commands[0]["command_name"] == "RoleZero.reply_to_human": - cmd = commands[0] - report_result = await self.reply_to_human(cmd["args"]) - self.rc.memory.add(AIMessage(content=respond_command)) - self.rc.memory.add(UserMessage(content=report_result)) - self.command_history.append("RoleZero.reply_to_human") - logger.info(f"Commands outputs: \n{report_result}") - outputs += report_result + if any(["end" == command["command_name"] for command in commands]): + # Ensure reply to the human before the "end" command is executed. + if all(["reply_to_human" not in memory.content for memory in self.get_memories(k=5)]): + memory = self.rc.memory.get(self.memory_k) + reply_to_human_prompt = REPORT_TO_HUMAN_PROMPT.format( + requirements_constraints=self.requirements_constraints, + ) + reply_content = await self.llm.aask(self.llm.format_msg(memory + [UserMessage(reply_to_human_prompt)])) + await self.reply_to_human(content=reply_content) + self.rc.memory.add(AIMessage(content=reply_content, cause_by=RunCommand)) + + # Summary of the Completed Task and Deliverables + if self.use_summary: + memory = self.rc.memory.get(self.memory_k) + summary_prompt = SUMMARY_PROMPY.format( + requirements_constraints=self.requirements_constraints, + ) + outputs = await self.llm.aask(self.llm.format_msg(memory + [UserMessage(summary_prompt)])) return AIMessage( content=f"I have finished the task, please mark my task as finished. Outputs: {outputs}", @@ -274,20 +273,6 @@ class RoleZero(Role): cause_by=RunCommand, ) - def check_whether_report_to_human(self): - """ "Check whether add reply to human command when finish current task""" - interaction_with_human = ["RoleZero.ask_human", "RoleZero.reply_to_human"] - plan_end_action = ["Plan.finish_current_task", "end"] - flag = 0 - for command_name in self.command_history[::-1]: - if command_name in plan_end_action: - flag |= 1 - elif command_name in interaction_with_human: - flag |= 2 - else: - break - return flag == 1 - async def _react(self) -> Message: # NOTE: Diff 1: Each time landing here means news is observed, set todo to allow news processing in _think self._set_state(0) diff --git a/metagpt/roles/di/swe_agent.py b/metagpt/roles/di/swe_agent.py index e90fc3045..d54c9dd44 100644 --- a/metagpt/roles/di/swe_agent.py +++ b/metagpt/roles/di/swe_agent.py @@ -69,11 +69,10 @@ class SWEAgent(RoleZero): This function is specifically added for SWE bench evaluation. """ - # only import when evaluation is needed + # If todo switches to None, it indicates that this is the final round of reactions, and the Swe-Agent will stop. Use git diff to store any changes made. if not self.rc.todo: from metagpt.tools.swe_agent_commands.swe_agent_utils import extract_patch - # swe agent have been stop. it means 'end' or other command which can stop the swe agent have been executed try: diff_output = await self.terminal.run("git diff --cached") clear_diff = extract_patch(diff_output) diff --git a/metagpt/roles/di/team_leader.py b/metagpt/roles/di/team_leader.py index 97100f295..112ca5a84 100644 --- a/metagpt/roles/di/team_leader.py +++ b/metagpt/roles/di/team_leader.py @@ -30,6 +30,8 @@ class TeamLeader(RoleZero): experience_retriever: Annotated[ExpRetriever, Field(exclude=True)] = SimpleExpRetriever() + use_summary: bool = False + def _update_tool_execution(self): self.tool_execution_map.update( { From 502a0e2ad6e784af6668d4d2a119b6152a695128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BC=9F=E9=9F=AC?= Date: Tue, 20 Aug 2024 19:21:17 +0800 Subject: [PATCH 3/7] combine summary and last reply to human --- metagpt/prompts/di/role_zero.py | 6 +++-- metagpt/roles/di/role_zero.py | 48 ++++++++++++++++++--------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index 2294fa731..80a505d17 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -233,6 +233,8 @@ SUMMARY_PROMPY = """ {requirements_constraints} You have just completed some tasks. -Summarize the tasks you have accomplished without including detailed information. -If there are any deliverables, list their descriptions and provide their file paths. +You must reply to human what you have done and what is the result. +If there are any deliverables, provide descriptions and file paths. +Do Not ouput any other format. limited in 300 words. +Your reply is: """ diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index 48967d7ef..4ab70409e 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -26,7 +26,6 @@ from metagpt.prompts.di.role_zero import ( QUICK_THINK_PROMPT, QUICK_THINK_SYSTEM_PROMPT, REGENERATE_PROMPT, - REPORT_TO_HUMAN_PROMPT, ROLE_INSTRUCTION, SUMMARY_PROMPY, SYSTEM_PROMPT, @@ -248,25 +247,6 @@ class RoleZero(Role): logger.info(f"Commands outputs: \n{outputs}") self.rc.memory.add(UserMessage(content=outputs)) - if any(["end" == command["command_name"] for command in commands]): - # Ensure reply to the human before the "end" command is executed. - if all(["reply_to_human" not in memory.content for memory in self.get_memories(k=5)]): - memory = self.rc.memory.get(self.memory_k) - reply_to_human_prompt = REPORT_TO_HUMAN_PROMPT.format( - requirements_constraints=self.requirements_constraints, - ) - reply_content = await self.llm.aask(self.llm.format_msg(memory + [UserMessage(reply_to_human_prompt)])) - await self.reply_to_human(content=reply_content) - self.rc.memory.add(AIMessage(content=reply_content, cause_by=RunCommand)) - - # Summary of the Completed Task and Deliverables - if self.use_summary: - memory = self.rc.memory.get(self.memory_k) - summary_prompt = SUMMARY_PROMPY.format( - requirements_constraints=self.requirements_constraints, - ) - outputs = await self.llm.aask(self.llm.format_msg(memory + [UserMessage(summary_prompt)])) - return AIMessage( content=f"I have finished the task, please mark my task as finished. Outputs: {outputs}", sent_from=self.name, @@ -442,8 +422,7 @@ class RoleZero(Role): command_output = "Current task is finished. If all tasks are finished, use 'end' to stop." elif cmd["command_name"] == "end": - self._set_state(-1) - command_output = "" + command_output = await self._end() # output from bash.run may be empty, add decorations to the output to ensure visibility. elif cmd["command_name"] == "Bash.run": @@ -502,3 +481,28 @@ class RoleZero(Role): if not isinstance(self.rc.env, MGXEnv): return "Not in MGXEnv, command will not be executed." return await self.rc.env.reply_to_human(content, sent_from=self) + + async def _end(self): + # summary + memory = self.get_memories(k=self.memory_k) + summary_prompt = SUMMARY_PROMPY.format( + requirements_constraints=self.requirements_constraints, + ) + reply_content = await self.llm.aask(self.llm.format_msg(memory + [UserMessage(summary_prompt)])) + # Ensure reply to the human before the "end" command is executed. + need_reply = True + for memory in self.get_memories(k=5)[::-1]: + if "reply_to_human" in memory.content: + need_reply = False + break + if "[Message]" in memory.content: + # Receive new message from other + need_reply = True + break + if need_reply: + await self.reply_to_human(content=reply_content) + self.rc.memory.add(AIMessage(content=reply_content, cause_by=RunCommand)) + + self._set_state(-1) + + return f"{reply_content} \n Plan.end executed Task is finished" From 1730897730962dcaeb438a62ca14da2decaf7d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BC=9F=E9=9F=AC?= Date: Tue, 20 Aug 2024 19:45:13 +0800 Subject: [PATCH 4/7] fix issues --- metagpt/roles/di/role_zero.py | 8 +++----- metagpt/roles/di/team_leader.py | 2 -- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index 4ab70409e..59714b726 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -68,8 +68,6 @@ class RoleZero(Role): react_mode: Literal["react"] = "react" max_react_loop: int = 20 # used for react mode - # Summary Mode - use_summary: bool = True # Tools tools: list[str] = [] # Use special symbol [""] to indicate use of all registered tools tool_recommender: Optional[ToolRecommender] = None @@ -483,6 +481,8 @@ class RoleZero(Role): return await self.rc.env.reply_to_human(content, sent_from=self) async def _end(self): + self._set_state(-1) + # summary memory = self.get_memories(k=self.memory_k) summary_prompt = SUMMARY_PROMPY.format( @@ -503,6 +503,4 @@ class RoleZero(Role): await self.reply_to_human(content=reply_content) self.rc.memory.add(AIMessage(content=reply_content, cause_by=RunCommand)) - self._set_state(-1) - - return f"{reply_content} \n Plan.end executed Task is finished" + return reply_content diff --git a/metagpt/roles/di/team_leader.py b/metagpt/roles/di/team_leader.py index 112ca5a84..97100f295 100644 --- a/metagpt/roles/di/team_leader.py +++ b/metagpt/roles/di/team_leader.py @@ -30,8 +30,6 @@ class TeamLeader(RoleZero): experience_retriever: Annotated[ExpRetriever, Field(exclude=True)] = SimpleExpRetriever() - use_summary: bool = False - def _update_tool_execution(self): self.tool_execution_map.update( { From beb66b05b42ca8731f341edc652f84fa6d0df352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BC=9F=E9=9F=AC?= Date: Tue, 20 Aug 2024 20:41:47 +0800 Subject: [PATCH 5/7] decompose reply to human and summary --- metagpt/prompts/di/role_zero.py | 11 +++++----- metagpt/roles/di/role_zero.py | 37 ++++++++++++++++----------------- metagpt/roles/di/team_leader.py | 2 ++ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index 80a505d17..caba52e57 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -223,8 +223,9 @@ REPORT_TO_HUMAN_PROMPT = """ # Restrictions {requirements_constraints} -Your have just finish all tasks, Use "RoleZero.reply_to_human" to answer the user requirements. -Report to human what you have done. Do Not ouput any other format. +You have just finished all tasks. +Reply to the human requirements. +Do not output any other format. Your reply is: """ @@ -233,8 +234,6 @@ SUMMARY_PROMPY = """ {requirements_constraints} You have just completed some tasks. -You must reply to human what you have done and what is the result. -If there are any deliverables, provide descriptions and file paths. -Do Not ouput any other format. limited in 300 words. -Your reply is: +Summarize the tasks you have accomplished without including detailed information. +If there are any deliverables, list their descriptions and provide their file paths. """ diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index 59714b726..8dfd888d2 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -26,6 +26,7 @@ from metagpt.prompts.di.role_zero import ( QUICK_THINK_PROMPT, QUICK_THINK_SYSTEM_PROMPT, REGENERATE_PROMPT, + REPORT_TO_HUMAN_PROMPT, ROLE_INSTRUCTION, SUMMARY_PROMPY, SYSTEM_PROMPT, @@ -68,6 +69,8 @@ class RoleZero(Role): react_mode: Literal["react"] = "react" max_react_loop: int = 20 # used for react mode + # Summary Mode + use_summary: bool = True # Tools tools: list[str] = [] # Use special symbol [""] to indicate use of all registered tools tool_recommender: Optional[ToolRecommender] = None @@ -482,25 +485,21 @@ class RoleZero(Role): async def _end(self): self._set_state(-1) - - # summary - memory = self.get_memories(k=self.memory_k) - summary_prompt = SUMMARY_PROMPY.format( - requirements_constraints=self.requirements_constraints, - ) - reply_content = await self.llm.aask(self.llm.format_msg(memory + [UserMessage(summary_prompt)])) # Ensure reply to the human before the "end" command is executed. - need_reply = True - for memory in self.get_memories(k=5)[::-1]: - if "reply_to_human" in memory.content: - need_reply = False - break - if "[Message]" in memory.content: - # Receive new message from other - need_reply = True - break - if need_reply: + if not any(["reply_to_human" in memory.content for memory in self.get_memories(k=5)]): + memory = self.rc.memory.get(self.memory_k) + reply_to_human_prompt = REPORT_TO_HUMAN_PROMPT.format( + requirements_constraints=self.requirements_constraints, + ) + reply_content = await self.llm.aask(self.llm.format_msg(memory + [UserMessage(reply_to_human_prompt)])) await self.reply_to_human(content=reply_content) self.rc.memory.add(AIMessage(content=reply_content, cause_by=RunCommand)) - - return reply_content + outputs = "" + # Summary of the Completed Task and Deliverables + if self.use_summary: + memory = self.rc.memory.get(self.memory_k) + summary_prompt = SUMMARY_PROMPY.format( + requirements_constraints=self.requirements_constraints, + ) + outputs = await self.llm.aask(self.llm.format_msg(memory + [UserMessage(summary_prompt)])) + return outputs diff --git a/metagpt/roles/di/team_leader.py b/metagpt/roles/di/team_leader.py index 97100f295..112ca5a84 100644 --- a/metagpt/roles/di/team_leader.py +++ b/metagpt/roles/di/team_leader.py @@ -30,6 +30,8 @@ class TeamLeader(RoleZero): experience_retriever: Annotated[ExpRetriever, Field(exclude=True)] = SimpleExpRetriever() + use_summary: bool = False + def _update_tool_execution(self): self.tool_execution_map.update( { From ca66af381a8fedb1faadc3fe022dcd8a5819a877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BC=9F=E9=9F=AC?= Date: Tue, 20 Aug 2024 20:52:08 +0800 Subject: [PATCH 6/7] fix format issues --- metagpt/prompts/di/role_zero.py | 2 +- metagpt/roles/di/role_zero.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index caba52e57..ce8c61dd5 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -229,7 +229,7 @@ Do not output any other format. Your reply is: """ -SUMMARY_PROMPY = """ +SUMMARY_PROMPT = """ # Restrictions {requirements_constraints} diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index 8dfd888d2..4e61ceab3 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -28,7 +28,7 @@ from metagpt.prompts.di.role_zero import ( REGENERATE_PROMPT, REPORT_TO_HUMAN_PROMPT, ROLE_INSTRUCTION, - SUMMARY_PROMPY, + SUMMARY_PROMPT, SYSTEM_PROMPT, THOUGHT_GUIDANCE, ) @@ -485,9 +485,9 @@ class RoleZero(Role): async def _end(self): self._set_state(-1) + memory = self.rc.memory.get(self.memory_k) # Ensure reply to the human before the "end" command is executed. if not any(["reply_to_human" in memory.content for memory in self.get_memories(k=5)]): - memory = self.rc.memory.get(self.memory_k) reply_to_human_prompt = REPORT_TO_HUMAN_PROMPT.format( requirements_constraints=self.requirements_constraints, ) @@ -497,8 +497,7 @@ class RoleZero(Role): outputs = "" # Summary of the Completed Task and Deliverables if self.use_summary: - memory = self.rc.memory.get(self.memory_k) - summary_prompt = SUMMARY_PROMPY.format( + summary_prompt = SUMMARY_PROMPT.format( requirements_constraints=self.requirements_constraints, ) outputs = await self.llm.aask(self.llm.format_msg(memory + [UserMessage(summary_prompt)])) From 14642407d15eee8e1522f5496856124038cf0f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E4=BC=9F=E9=9F=AC?= Date: Tue, 20 Aug 2024 20:55:33 +0800 Subject: [PATCH 7/7] fix format issues --- metagpt/roles/di/role_zero.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index 4e61ceab3..1153cb22d 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -69,8 +69,6 @@ class RoleZero(Role): react_mode: Literal["react"] = "react" max_react_loop: int = 20 # used for react mode - # Summary Mode - use_summary: bool = True # Tools tools: list[str] = [] # Use special symbol [""] to indicate use of all registered tools tool_recommender: Optional[ToolRecommender] = None @@ -89,6 +87,7 @@ class RoleZero(Role): memory_k: int = 20 # number of memories (messages) to use as historical context use_fixed_sop: bool = False requirements_constraints: str = "" # the constraints in user requirements + use_summary: bool = True # whether to summarize at the end @model_validator(mode="after") def set_plan_and_tool(self) -> "RoleZero":