From f621186c2ef3bf1231584329b22376a89a6084f3 Mon Sep 17 00:00:00 2001 From: yzlin Date: Thu, 25 Apr 2024 10:34:46 +0800 Subject: [PATCH] reserve exp retriever & some formatting --- metagpt/environment/base_env.py | 2 +- metagpt/environment/mgx/mgx_env.py | 4 +- metagpt/prompts/di/team_leader.py | 95 +-------------- metagpt/roles/di/team_leader.py | 20 ++-- metagpt/strategy/experience_retriever.py | 130 +++++++++++++++++++++ tests/metagpt/roles/di/test_team_leader.py | 38 +----- 6 files changed, 146 insertions(+), 143 deletions(-) create mode 100644 metagpt/strategy/experience_retriever.py diff --git a/metagpt/environment/base_env.py b/metagpt/environment/base_env.py index 024c46877..4a2d0c114 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 9a148f24c..6626679ce 100644 --- a/metagpt/environment/mgx/mgx_env.py +++ b/metagpt/environment/mgx/mgx_env.py @@ -42,6 +42,6 @@ class MGXEnv(Environment): # NOTE: Can be overwritten in remote setting return get_human_input(question) - async def reply_to_human(self, message: str) -> str: + async def reply_to_human(self, content: str) -> str: # NOTE: Can be overwritten in remote setting - return message + return content diff --git a/metagpt/prompts/di/team_leader.py b/metagpt/prompts/di/team_leader.py index 8b843a47d..5a5dce37f 100644 --- a/metagpt/prompts/di/team_leader.py +++ b/metagpt/prompts/di/team_leader.py @@ -5,7 +5,6 @@ def prepare_command_prompt(commands: list[Command]) -> str: command_prompt = "" for i, command in enumerate(commands): command_prompt += f"{i+1}. {command.value.signature}:\n{command.value.desc}\n\n" - print(command_prompt) return command_prompt @@ -36,7 +35,7 @@ You should NOT assign consecutive tasks to the same team member, instead, assign If plan is created, you should track the progress based on team member feedback message, and update plan accordingly, such as finish_current_task, reset_task, replace_task, etc. 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 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. +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. Pay close attention to the Example provided @@ -88,95 +87,3 @@ Note: ... ] """ - -PLANNING_EXAMPLE = """ -## example 1 -User Requirement: Create a cli snake game -Explanation: The requirement is about software development. Assign each tasks to a different team member based on their expertise. -```json -[ - { - "command_name": "append_task", - "args": { - "task_id": "1", - "dependent_task_ids": [], - "instruction": "Create a product requirement document (PRD) outlining the features, user interface, and user experience of the CLI snake game.", - "assignee": "Alice" - } - }, - { - "command_name": "append_task", - "args": { - "task_id": "2", - "dependent_task_ids": ["1"], - "instruction": "Design the software architecture for the CLI snake game, including the choice of programming language, libraries, and data flow.", - "assignee": "Bob" - } - }, - { - "command_name": "append_task", - "args": { - "task_id": "3", - "dependent_task_ids": ["2"], - "instruction": "Break down the architecture into manageable tasks, identify task dependencies, and prepare a detailed task list for implementation.", - "assignee": "Eve" - } - }, - { - "command_name": "append_task", - "args": { - "task_id": "4", - "dependent_task_ids": ["3"], - "instruction": "Implement the core game logic for the CLI snake game, including snake movement, food generation, and score tracking.", - "assignee": "Alex" - } - }, - { - "command_name": "append_task", - "args": { - "task_id": "5", - "dependent_task_ids": ["4"], - "instruction": "Write comprehensive tests for the game logic and user interface to ensure functionality and reliability.", - "assignee": "Edward" - } - } -] -``` - -## example 2 -User requirement: Run data analysis on sklearn Wine recognition dataset, include a plot, and train a model to predict wine class (20% as validation), and show validation accuracy. -Explanation: DON'T decompose requirement if it is a DATA-RELATED task, assign a single task directly to Data Analyst David. He will manage the decomposition and implementation. -```json -[ - { - "command_name": "append_task", - "args": { - "task_id": "1", - "dependent_task_ids": [], - "instruction": "Run data analysis on sklearn Wine recognition dataset, include a plot, and train a model to predict wine class (20% as validation), and show validation accuracy.", - "assignee": "David" - } - } -] -``` -""" - -ROUTING_EXAMPLE = """ -## 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" - } - } -] -""" diff --git a/metagpt/roles/di/team_leader.py b/metagpt/roles/di/team_leader.py index ec293f776..e8cb6570d 100644 --- a/metagpt/roles/di/team_leader.py +++ b/metagpt/roles/di/team_leader.py @@ -7,13 +7,15 @@ from pydantic import model_validator from metagpt.environment.mgx.mgx_env import MGXEnv from metagpt.prompts.di.team_leader import ( PLANNING_CMD_PROMPT, - PLANNING_EXAMPLE, ROUTING_CMD_PROMPT, - ROUTING_EXAMPLE, prepare_command_prompt, ) from metagpt.roles import Role from metagpt.schema import Message, Task, TaskResult +from metagpt.strategy.experience_retriever import ( + SimplePlanningExpRetriever, + SimpleRoutingExpRetriever, +) from metagpt.strategy.planner import Planner from metagpt.strategy.thinking_command import Command from metagpt.utils.common import CodeParser @@ -92,7 +94,7 @@ class TeamLeader(Role): if not self.planner.plan.goal: user_requirement = self.get_memories()[-1].content self.planner.plan.goal = user_requirement - example = PLANNING_EXAMPLE + example = SimplePlanningExpRetriever().retrieve() # common info team_info = "" @@ -114,8 +116,6 @@ class TeamLeader(Role): available_commands=prepare_command_prompt(self.planning_commands), ) context = self.llm.format_msg(self.get_memory() + [Message(content=plan_prompt, role="user")]) - print(*context, sep="*" * 10 + "\n\n") - # breakpoint() plan_rsp = await self.llm.aask(context) plan_rsp_dict = json.loads(CodeParser.parse_code(block=None, text=plan_rsp)) @@ -126,12 +126,10 @@ class TeamLeader(Role): route_prompt = ROUTING_CMD_PROMPT.format( plan_status=plan_status, team_info=team_info, - example=ROUTING_EXAMPLE, + example=SimpleRoutingExpRetriever().retrieve(), available_commands=prepare_command_prompt(self.env_commands), ) context = self.llm.format_msg(self.get_memory() + [Message(content=route_prompt, role="user")]) - print(*context, sep="*" * 10 + "\n\n") - # breakpoint() route_rsp = await self.llm.aask(context) route_rsp_dict = json.loads(CodeParser.parse_code(block=None, text=route_rsp)) @@ -144,8 +142,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) - - async def run(self, with_message=None) -> Message | None: - if await self._observe(): - await self._think() - await self._act() + return "\n".join(self.commands) diff --git a/metagpt/strategy/experience_retriever.py b/metagpt/strategy/experience_retriever.py new file mode 100644 index 000000000..af7229b6e --- /dev/null +++ b/metagpt/strategy/experience_retriever.py @@ -0,0 +1,130 @@ +from pydantic import BaseModel + + +class ExpRetriever(BaseModel): + """interface for experience retriever""" + + def retrieve(self, context: str) -> str: + raise NotImplementedError + + +class SimplePlanningExpRetriever(ExpRetriever): + """A simple experience retriever that returns manually crafted planning examples.""" + + EXAMPLE: str = """ + ## example 1 + User Requirement: Create a cli snake game + Explanation: The requirement is about software development. Assign each tasks to a different team member based on their expertise. + ```json + [ + { + "command_name": "append_task", + "args": { + "task_id": "1", + "dependent_task_ids": [], + "instruction": "Create a product requirement document (PRD) outlining the features, user interface, and user experience of the CLI snake game.", + "assignee": "Alice" + } + }, + { + "command_name": "append_task", + "args": { + "task_id": "2", + "dependent_task_ids": ["1"], + "instruction": "Design the software architecture for the CLI snake game, including the choice of programming language, libraries, and data flow.", + "assignee": "Bob" + } + }, + { + "command_name": "append_task", + "args": { + "task_id": "3", + "dependent_task_ids": ["2"], + "instruction": "Break down the architecture into manageable tasks, identify task dependencies, and prepare a detailed task list for implementation.", + "assignee": "Eve" + } + }, + { + "command_name": "append_task", + "args": { + "task_id": "4", + "dependent_task_ids": ["3"], + "instruction": "Implement the core game logic for the CLI snake game, including snake movement, food generation, and score tracking.", + "assignee": "Alex" + } + }, + { + "command_name": "append_task", + "args": { + "task_id": "5", + "dependent_task_ids": ["4"], + "instruction": "Write comprehensive tests for the game logic and user interface to ensure functionality and reliability.", + "assignee": "Edward" + } + } + ] + ``` + + ## example 2 + User requirement: Run data analysis on sklearn Wine recognition dataset, include a plot, and train a model to predict wine class (20% as validation), and show validation accuracy. + Explanation: DON'T decompose requirement if it is a DATA-RELATED task, assign a single task directly to Data Analyst David. He will manage the decomposition and implementation. + ```json + [ + { + "command_name": "append_task", + "args": { + "task_id": "1", + "dependent_task_ids": [], + "instruction": "Run data analysis on sklearn Wine recognition dataset, include a plot, and train a model to predict wine class (20% as validation), and show validation accuracy.", + "assignee": "David" + } + } + ] + ``` + + ## example 3 + 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",...], ...}}}}}, + ] + 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 + [ + { + "command_name": "finish_current_task", + "args": {} + } + ] + ``` + """ + + 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/tests/metagpt/roles/di/test_team_leader.py b/tests/metagpt/roles/di/test_team_leader.py index 6dd9ef4a6..1ac3abffe 100644 --- a/tests/metagpt/roles/di/test_team_leader.py +++ b/tests/metagpt/roles/di/test_team_leader.py @@ -123,7 +123,7 @@ async def test_plan_update_and_routing(env): requirement = "create a 2048 game" tl = env.get_role("Team Leader") - env.publish_message(Message(content=requirement, send_to=tl.name)) + env.publish_message(Message(content=requirement)) await tl.run() # Assuming Product Manager finishes its task @@ -131,37 +131,9 @@ async def test_plan_update_and_routing(env): await tl.run() # TL should mark current task as finished, and forward Product Manager's message to Architect - plan_cmd = tl.commands[0] + # Current task should be updated to the second task + plan_cmd = tl.commands[:-1] route_cmd = tl.commands[-1] - assert plan_cmd["command_name"] == "finish_current_task" + 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" - - -async def main(): - requirement = [ - # "Create a cli snake game", - # "I want to use yolov5 for target detection, yolov5 all the information from the following link, please help me according to the content of the link(https://github.com/ultralytics/yolov5), set up the environment and download the model parameters, and finally provide a few pictures for inference, the inference results will be saved!", - # "Create a website widget for TODO list management. Users should be able to add, mark as complete, and delete tasks. Include features like prioritization, due dates, and categories. Make it visually appealing, responsive, and user-friendly. Use HTML, CSS, and JavaScript. Consider additional features like notifications or task export. Keep it simple and enjoyable for users.dont use vue or react.dont use third party library, use localstorage to save data", - # "Search the web for the new game 2048X, then replicate it", - # """从36kr创投平台https://pitchhub.36kr.com/financing-flash 所有初创企业融资的信息, **注意: 这是一个中文网站**; - # 下面是一个大致流程, 你会根据每一步的运行结果对当前计划中的任务做出适当调整: - # 1. 爬取并本地保存html结构; - # 2. 直接打印第7个*`快讯`*关键词后2000个字符的html内容, 作为*快讯的html内容示例*; - # 3. 反思*快讯的html内容示例*中的规律, 设计正则匹配表达式来获取*`快讯`*的标题、链接、时间; - # 4. 筛选最近3天的初创企业融资*`快讯`*, 以list[dict]形式打印前5个。 - # 5. 将全部结果存在本地csv中 - # """, - """ - I would like to imitate the website available at https://news.youth.cn/gn/202404/t20240406_15178916.htm. Could you please browse through it? - Note: - - don't ignore the image, use https://source.unsplash.com/random to get random images - - use the same text, the same layout, the same color as the original website - if you can not do it, please try to get as close as possible. - """, - ] - tl.put_message(Message(requirement[0])) - await tl._observe() - await tl._think() - - -# asyncio.run(main()) + assert tl.planner.plan.current_task_id == "2"