From e39f7ff0b150d9344de38799f2c846a62d6bc36a Mon Sep 17 00:00:00 2001 From: yzlin Date: Thu, 25 Apr 2024 12:00:32 +0800 Subject: [PATCH] rm forward_message --- metagpt/environment/base_env.py | 2 +- metagpt/environment/mgx/mgx_env.py | 10 -------- metagpt/prompts/di/team_leader.py | 12 +++------ metagpt/roles/di/team_leader.py | 14 +++-------- metagpt/schema.py | 15 ++++++++++- metagpt/strategy/experience_retriever.py | 29 +--------------------- metagpt/strategy/thinking_command.py | 5 ---- tests/metagpt/roles/di/test_team_leader.py | 4 +-- 8 files changed, 26 insertions(+), 65 deletions(-) diff --git a/metagpt/environment/base_env.py b/metagpt/environment/base_env.py index 4a2d0c114..024c46877 100644 --- a/metagpt/environment/base_env.py +++ b/metagpt/environment/base_env.py @@ -190,7 +190,7 @@ class Environment(ExtEnv): found = True if not found: logger.warning(f"Message no recipients: {message.dump()}") - # self.history += f"\n{message}" # For debug + self.history += f"\n{message}" # For debug return True diff --git a/metagpt/environment/mgx/mgx_env.py b/metagpt/environment/mgx/mgx_env.py index 6626679ce..b15d57e0f 100644 --- a/metagpt/environment/mgx/mgx_env.py +++ b/metagpt/environment/mgx/mgx_env.py @@ -6,8 +6,6 @@ from metagpt.schema import Message class MGXEnv(Environment): """MGX Environment""" - history: dict[str, Message] = {} # redefine message history - def _publish_message(self, message: Message, peekable: bool = True) -> bool: return super().publish_message(message, peekable) @@ -28,16 +26,8 @@ class MGXEnv(Environment): # every regular message goes through team leader tl.put_message(message) - self.history[message.id] = message - return True - def forward_message(self, message_id: str) -> str: - if message_id not in self.history: - return f"invalid message_id {message_id}, not found in history." - msg = self.history[message_id] - return self._publish_message(msg) - async def ask_human(self, question: str) -> str: # NOTE: Can be overwritten in remote setting return get_human_input(question) diff --git a/metagpt/prompts/di/team_leader.py b/metagpt/prompts/di/team_leader.py index 5a5dce37f..2ad0c4b54 100644 --- a/metagpt/prompts/di/team_leader.py +++ b/metagpt/prompts/di/team_leader.py @@ -37,14 +37,14 @@ If plan is created, you should track the progress based on team member feedback Note: 1. If the requirement is a pure DATA-RELATED requirement, such as bug fixes, issue reporting, environment setup, terminal operations, pip install, web browsing, web scraping, web searching, web imitation, data science, data analysis, machine learning, deep learning, text-to-image etc. DON'T decompose it, assign a single task with the original user requirement as instruction directly to Data Analyst. 2. If the requirement is developing a software, game, app, or website, excluding the above data-related tasks, you should decompose the requirement into multiple tasks and assign them to different team members based on their expertise, usually the sequence of Product Manager -> Architect -> Project Manager -> Engineer -> QaEngine, each assigned ONE task. -3. If the requirement contains both DATA-RELATED part and software development part, you should decompose the software development part and assign them to different team members based on their expertise, and assign the DATA-RELATED part to Data Analyst David directly. +3. If the requirement contains both DATA-RELATED part mentioned in 1 and software development part mentioned in 2, you should decompose the software development part and assign them to different team members based on their expertise, and assign the DATA-RELATED part to Data Analyst David directly. Pay close attention to the Example provided You may use any of the available commands to create a plan or update the plan. You may output mutiple commands, they will be executed sequentially. If you finish current task, you will automatically take the next task in the existing plan, use finish_task, DON'T append a new task. # Your commands in a json array, in the following output format: -Some text indicating your thoughts, including how you categorize the requirement based on Note or how you should update the plan status. Then a json array of commands. +Some text indicating your thoughts, including how you categorize the requirement based on Note (is it 1., 2., or 3.?) or how you should update the plan status. Then a json array of commands. ```json [ {{ @@ -70,12 +70,8 @@ ROUTING_CMD_PROMPT = """ {example} # Instructions -You are a team leader, you can use publish_message or forward_message to team members, asking them to start their task. -Note: -1. You should carefully review the recent conversation, if there are messages from team members, forward them or withold them properly. -2. Prioritize forward_message if messages exist, only public_message if you want to instruct the team member yourself. -3. Pay attention to task dependency, don't assign a task to a team member if the dependent tasks are not finished yet. -4. Review plan status, think about what you should do next, you can use any of the Available Commands. +You are a team leader, you can publish_message to team members, asking them to start their task. +If there are new user message, review the conversation history and think about what you should do next, you can use any of the Available Commands. # Your commands in a json array, in the following output format: ```json diff --git a/metagpt/roles/di/team_leader.py b/metagpt/roles/di/team_leader.py index e8cb6570d..3b1ac9831 100644 --- a/metagpt/roles/di/team_leader.py +++ b/metagpt/roles/di/team_leader.py @@ -12,10 +12,7 @@ from metagpt.prompts.di.team_leader import ( ) from metagpt.roles import Role from metagpt.schema import Message, Task, TaskResult -from metagpt.strategy.experience_retriever import ( - SimplePlanningExpRetriever, - SimpleRoutingExpRetriever, -) +from metagpt.strategy.experience_retriever import SimplePlanningExpRetriever from metagpt.strategy.planner import Planner from metagpt.strategy.thinking_command import Command from metagpt.utils.common import CodeParser @@ -35,7 +32,6 @@ class TeamLeader(Role): ] env_commands: list[Command] = [ Command.PUBLISH_MESSAGE, - Command.FORWARD_MESSAGE, Command.ASK_HUMAN, Command.REPLY_TO_HUMAN, Command.PASS, @@ -50,8 +46,6 @@ class TeamLeader(Role): assert isinstance(self.rc.env, MGXEnv), "TeamLeader should only be used in an MGXEnv" if cmd["command_name"] == Command.PUBLISH_MESSAGE.cmd_name: self.rc.env.publish_message(Message(**cmd["args"]), publicer=self.profile) - elif cmd["command_name"] in Command.FORWARD_MESSAGE.cmd_name: - self.rc.env.forward_message(**cmd["args"]) elif cmd["command_name"] == Command.ASK_HUMAN.cmd_name: self.rc.env.ask_human(**cmd["args"]) elif cmd["command_name"] == Command.REPLY_TO_HUMAN.cmd_name: @@ -82,7 +76,7 @@ class TeamLeader(Role): mem = self.rc.memory.get() for m in mem: if m.role not in ["system", "user", "assistant"]: - m.content = f"id: {m.id[:10]}, from {m.role} to {m.send_to}: {m.content}" + m.content = f"from {m.role} to {m.send_to}: {m.content}" m.role = "assistant" return mem @@ -126,7 +120,7 @@ class TeamLeader(Role): route_prompt = ROUTING_CMD_PROMPT.format( plan_status=plan_status, team_info=team_info, - example=SimpleRoutingExpRetriever().retrieve(), + example="", available_commands=prepare_command_prompt(self.env_commands), ) context = self.llm.format_msg(self.get_memory() + [Message(content=route_prompt, role="user")]) @@ -142,4 +136,4 @@ class TeamLeader(Role): """Useful in 'react' mode. Return a Message conforming to Role._act interface.""" self.run_commands(self.commands) self.task_result = TaskResult(result="Success", is_success=True) - return "\n".join(self.commands) + return Message(content="Commands executed", role="assistant") diff --git a/metagpt/schema.py b/metagpt/schema.py index b24d18d09..d0396ec26 100644 --- a/metagpt/schema.py +++ b/metagpt/schema.py @@ -348,6 +348,7 @@ class Task(BaseModel): result: str = "" is_success: bool = False is_finished: bool = False + assignee: str = "" def reset(self): self.code = "" @@ -489,7 +490,11 @@ class Plan(BaseModel): Returns: None """ - assert not self.has_task_id(new_task.task_id), "Task already in current plan, use replace_task instead" + # assert not self.has_task_id(new_task.task_id), "Task already in current plan, use replace_task instead" + if self.has_task_id(new_task.task_id): + logger.warning( + "Task already in current plan, should use replace_task instead. Overwriting the existing task." + ) assert all( [self.has_task_id(dep_id) for dep_id in new_task.dependent_task_ids] @@ -504,6 +509,10 @@ class Plan(BaseModel): return task_id in self.task_map def _update_current_task(self): + self.tasks = self._topological_sort(self.tasks) + # Update the task map for quick access to tasks by ID + self.task_map = {task.task_id: task for task in self.tasks} + current_task_id = "" for task in self.tasks: if not task.is_finished: @@ -534,6 +543,10 @@ class Plan(BaseModel): self.current_task.is_finished = True self._update_current_task() # set to next task + def is_plan_finished(self) -> bool: + """Check if all tasks are finished""" + return all(task.is_finished for task in self.tasks) + def get_finished_tasks(self) -> list[Task]: """return all finished tasks in correct linearized order diff --git a/metagpt/strategy/experience_retriever.py b/metagpt/strategy/experience_retriever.py index af7229b6e..aea354645 100644 --- a/metagpt/strategy/experience_retriever.py +++ b/metagpt/strategy/experience_retriever.py @@ -86,7 +86,7 @@ class SimplePlanningExpRetriever(ExpRetriever): Conversation History: [ ..., - {'role': 'assistant', 'content': 'id: 739d9b4983fd4e97a0f78fde5e9ef158, from Alice(Product Manager) to {'Bob'}: {'docs': {'20240424153821.json': {'root_path': 'docs/prd', 'filename': '20240424153821.json', 'content': '{"Language":"en_us","Programming Language":"Python","Original Requirements":"create a cli snake game","Project Name":"snake_game","Product Goals":["Develop an intuitive and addictive snake game",...], ...}}}}}, + {'role': 'assistant', 'content': 'from Alice(Product Manager) to {'Bob'}: {'docs': {'20240424153821.json': {'root_path': 'docs/prd', 'filename': '20240424153821.json', 'content': '{"Language":"en_us","Programming Language":"Python","Original Requirements":"create a cli snake game","Project Name":"snake_game","Product Goals":["Develop an intuitive and addictive snake game",...], ...}}}}}, ] Explanation: You received a message from Alice, the Product Manager, that she has completed the PRD, use finish_current_task, this marks her task as finished and moves the plan to the next task. ```json @@ -101,30 +101,3 @@ class SimplePlanningExpRetriever(ExpRetriever): def retrieve(self, context: str = "") -> str: return self.EXAMPLE - - -class SimpleRoutingExpRetriever(ExpRetriever): - """A simple experience retriever that returns manually crafted routing examples.""" - - EXAMPLE: str = """ - ## example 1: Forward a message from one team member to another - Conversation History: - [ - ..., - {'role': 'assistant', 'content': 'id: 739d9b4983fd4e97a0f78fde5e9ef158, from Alice(Product Manager) to {'Bob'}: {'docs': {'20240424153821.json': {'root_path': 'docs/prd', 'filename': '20240424153821.json', 'content': '{"Language":"en_us","Programming Language":"Python","Original Requirements":"create a cli snake game","Project Name":"snake_game","Product Goals":["Develop an intuitive and addictive snake game",...], ...}}}}}, - {'role': 'assistant', 'content': 'Based on the feedback from Alice, the Product Manager, it seems that the PRD for the snake game has been successfully created. Since the PRD is complete and there are no indications of issues with the task, we can mark the current task as finished and move on to the next task in the plan. The next task is assigned to Bob, the Architect, who will be responsible for designing the software architecture for the game based on the PRD provided by Alice.\n\nHere are the commands to update the plan status:\n\n```json\n[\n {\n "command_name": "finish_current_task"\n }\n]\n```'} - ] - Command: - ```json - [ - { - "command_name": "forward_message", - "args": { - "message_id": "739d9b4983fd4e97a0f78fde5e9ef158" - } - } - ] - """ - - def retrieve(self, context: str = "") -> str: - return self.EXAMPLE diff --git a/metagpt/strategy/thinking_command.py b/metagpt/strategy/thinking_command.py index cc4626c6d..19604ad39 100644 --- a/metagpt/strategy/thinking_command.py +++ b/metagpt/strategy/thinking_command.py @@ -38,11 +38,6 @@ class Command(Enum): signature="publish_message(content: str, send_to: str)", desc="Publish a message to a team member, use member name to fill send_to args. You may copy the full original content or add additional information from upstream. This will make team members start their work. DONT omit any necessary info such as path, link, environment from original content to team members because you are their sole info source. However, if the original content is long or contains concrete info, you should forward the message using forward_message instead of publishing it.", ) - FORWARD_MESSAGE = CommandDef( - name="forward_message", - signature="forward_message(message_id: str)", - desc="Forward a message from one team member to another or all without any modification. This will make the recipient start their work, too.", - ) REPLY_TO_HUMAN = CommandDef( name="reply_to_human", signature="reply_to_human(content: str)", diff --git a/tests/metagpt/roles/di/test_team_leader.py b/tests/metagpt/roles/di/test_team_leader.py index 1ac3abffe..3c0b5ef92 100644 --- a/tests/metagpt/roles/di/test_team_leader.py +++ b/tests/metagpt/roles/di/test_team_leader.py @@ -20,7 +20,7 @@ def env(): da = DataInterpreter( name="David", profile="Data Analyst", - goal="Take on any data-related tasks, such as data analysis, machine learning, deep learning, web browsing, web scraping, web deployment, terminal operation, git operation, etc.", + goal="Take on any data-related tasks, such as data analysis, machine learning, deep learning, web browsing, web scraping, web searching, web deployment, terminal operation, git operation, etc.", react_mode="react", ) test_env.add_roles( @@ -135,5 +135,5 @@ async def test_plan_update_and_routing(env): plan_cmd = tl.commands[:-1] route_cmd = tl.commands[-1] assert "finish_current_task" in [cmd["command_name"] for cmd in plan_cmd] - assert route_cmd["command_name"] == "forward_message" or route_cmd["command_name"] == "publish_message" + assert route_cmd["command_name"] == "publish_message" assert tl.planner.plan.current_task_id == "2"