diff --git a/examples/exp_pool/init_exp_pool/engineer_exps.py b/examples/exp_pool/init_exp_pool/engineer_exps.py new file mode 100644 index 000000000..e494fdaa3 --- /dev/null +++ b/examples/exp_pool/init_exp_pool/engineer_exps.py @@ -0,0 +1,115 @@ +import json + +OBJ1 = [ + { + "role": "user", + "content": '\n# Data Structure\n\n\n# Current Plan\n{\'goal\': "Please write a 1024 game using JavaScript and HTML code without using any frameworks, user can play with keyboard. Refer to the system design located at \'/tmp/system_design.json\' and the project schedule at \'/tmp/project_schedule.json\' for detailed information.", \'tasks\': []}\n\n# Current Task\n\n\n# Example\n\n\n# Instruction\nBased on the context, write a plan or modify an existing plan to achieve the goal. A plan consists of one to 3 tasks.\nIf plan is created, you should track the progress and update the plan accordingly, such as Plan.finish_current_task, Plan.append_task, Plan.reset_task, Plan.replace_task, etc.\nWhen presented a current task, tackle the task using the available commands.\nPay close attention to new user message, review the conversation history, use RoleZero.reply_to_human to respond to new user requirement.\nNote:\n1. If you keeping encountering errors, unexpected situation, or you are not sure of proceeding, use RoleZero.ask_human to ask for help.\n2. Carefully review your progress at the current task, if your actions so far has not fulfilled the task instruction, you should continue with current task. Otherwise, finish current task.\n3. Each time you finish a task, use RoleZero.reply_to_human to report your progress.\n4. Each time you write a code in your response, write with the Editor directly without preparing a repetitive code block beforehand.\n5. Take on ONE task and write ONE code file in each response. DON\'T attempt all tasks in one response.\n6. When not specified, you should write files in a folder named "src". If you know the project path, then write in a "src" folder under the project path.\n7. When provided system design or project schedule, you MUST read them first before making a plan, then adhere to them in your implementation, especially in the programming language, package, or framework. You MUST implement all code files prescribed in the system design or project schedule. You can create a plan first with each task corresponding to implementing one code file.\n8. Write at most one file per task, do your best to implement THE ONLY ONE FILE. CAREFULLY CHECK THAT YOU DONT MISS ANY NECESSARY CLASS/FUNCTION IN THIS FILE.\n9. COMPLETE CODE: Your code will be part of the entire project, so please implement complete, reliable, reusable code snippets.\n10. When provided system design, YOU MUST FOLLOW "Data structures and interfaces". DONT CHANGE ANY DESIGN. Do not use public member functions that do not exist in your design.\n11. Write out EVERY CODE DETAIL, DON\'T LEAVE TODO.\n12. To modify code in a file, read the entire file, make changes, and update the file with the complete code, ensuring that no line numbers are included in the final write.\n13. When a system design or project schedule is provided, at the end of the plan, add a CodeRview Task for each file; for example, if there are three files, add three CodeRview Tasks. For each CodeRview Task, just call ReviewAndRewriteCode.run.\n\nPay close attention to the Example provided, you can reuse the example for your current situation if it fits.\nYou 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.\nIf you finish current task, you will automatically take the next task in the existing plan, use Plan.finish_task, DON\'T append a new task.\n\n# Your commands in a json array, in the following output format. If there is nothing to do, use the pass or end command:\nSome text indicating your thoughts, such as how you should update the plan status, respond to inquiry, or seek for help. Then a json array of commands. You must output ONE and ONLY ONE json array. DON\'T output multiple json arrays with thoughts between them.\n```json\n[\n {\n "command_name": str,\n "args": {"arg_name": arg_value, ...}\n },\n ...\n]\n```\nNotice: your output JSON data section must start with **```json [**\n', + } +] +OBJ2 = [ + { + "role": "user", + "content": "Command Editor.read executed: file_path='/tmp/system_design.json' block_content=\"...\"\n\nCommand Editor.read executed: file_path='/tmp/project_schedule.json' block_content=\"...\"", + }, + { + "role": "user", + "content": '\n# Data Structure\n\n\n# Current Plan\n{\'goal\': "Please write a 1024 game using JavaScript and HTML code without using any frameworks, user can play with keyboard. Refer to the system design located at \'/tmp/system_design.json\' and the project schedule at \'/tmp/project_schedule.json\' for detailed information.", \'tasks\': []}\n\n# Current Task\n\n\n# Example\n\n\n# Instruction\nBased on the context, write a plan or modify an existing plan to achieve the goal. A plan consists of one to 3 tasks.\nIf plan is created, you should track the progress and update the plan accordingly, such as Plan.finish_current_task, Plan.append_task, Plan.reset_task, Plan.replace_task, etc.\nWhen presented a current task, tackle the task using the available commands.\nPay close attention to new user message, review the conversation history, use RoleZero.reply_to_human to respond to new user requirement.\nNote:\n1. If you keeping encountering errors, unexpected situation, or you are not sure of proceeding, use RoleZero.ask_human to ask for help.\n2. Carefully review your progress at the current task, if your actions so far has not fulfilled the task instruction, you should continue with current task. Otherwise, finish current task.\n3. Each time you finish a task, use RoleZero.reply_to_human to report your progress.\n4. Each time you write a code in your response, write with the Editor directly without preparing a repetitive code block beforehand.\n5. Take on ONE task and write ONE code file in each response. DON\'T attempt all tasks in one response.\n6. When not specified, you should write files in a folder named "src". If you know the project path, then write in a "src" folder under the project path.\n7. When provided system design or project schedule, you MUST read them first before making a plan, then adhere to them in your implementation, especially in the programming language, package, or framework. You MUST implement all code files prescribed in the system design or project schedule. You can create a plan first with each task corresponding to implementing one code file.\n8. Write at most one file per task, do your best to implement THE ONLY ONE FILE. CAREFULLY CHECK THAT YOU DONT MISS ANY NECESSARY CLASS/FUNCTION IN THIS FILE.\n9. COMPLETE CODE: Your code will be part of the entire project, so please implement complete, reliable, reusable code snippets.\n10. When provided system design, YOU MUST FOLLOW "Data structures and interfaces". DONT CHANGE ANY DESIGN. Do not use public member functions that do not exist in your design.\n11. Write out EVERY CODE DETAIL, DON\'T LEAVE TODO.\n12. To modify code in a file, read the entire file, make changes, and update the file with the complete code, ensuring that no line numbers are included in the final write.\n13. When a system design or project schedule is provided, at the end of the plan, add a CodeRview Task for each file; for example, if there are three files, add three CodeRview Tasks. For each CodeRview Task, just call ReviewAndRewriteCode.run.\n\nPay close attention to the Example provided, you can reuse the example for your current situation if it fits.\nYou 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.\nIf you finish current task, you will automatically take the next task in the existing plan, use Plan.finish_task, DON\'T append a new task.\n\n# Your commands in a json array, in the following output format. If there is nothing to do, use the pass or end command:\nSome text indicating your thoughts, such as how you should update the plan status, respond to inquiry, or seek for help. Then a json array of commands. You must output ONE and ONLY ONE json array. DON\'T output multiple json arrays with thoughts between them.\n```json\n[\n {\n "command_name": str,\n "args": {"arg_name": arg_value, ...}\n },\n ...\n]\n```\nNotice: your output JSON data section must start with **```json [**\n', + }, +] + +REQ1 = json.dumps(OBJ1) +REQ2 = json.dumps(OBJ2) + +RESP1 = """ +Explanation: To begin the task of creating a 2048 game using JavaScript and HTML, I need to first read the system design and project schedule documents to understand the specific requirements and architecture outlined for the game development. + +```json +[ + { + "command_name": "Editor.read", + "args": { + "path": "/tmp/system_design.json" + } + }, + { + "command_name": "Editor.read", + "args": { + "path": "/tmp/project_schedule.json" + } + } +] +``` +""" +RESP2 = """ +Explanation: Based on the system design and project schedule, I will create a plan to implement the 2048 game. The plan will consist of tasks to create the necessary files (`index.html`, `style.css`, and `script.js`) and review each file to ensure they meet the design specifications. + +Here's the plan: + +1. **Task 1**: Create `index.html` - This file will contain the HTML structure necessary for the game's UI. +2. **Task 2**: Create `style.css` - This file will define the CSS styles to make the game visually appealing and responsive. +3. **Task 3**: Create `script.js` - This file will contain the JavaScript code for the game logic and UI interactions. +4. **Code Review Tasks**: Review each file to ensure they meet the project requirements and adhere to the system design. + +Let's start by appending the first task to the plan. + +```json +[ + { + "command_name": "Plan.append_task", + "args": { + "task_id": "1", + "dependent_task_ids": [], + "instruction": "Create the index.html file with the basic HTML structure for the 2048 game.", + "assignee": "Alex" + } + }, + { + "command_name": "Plan.append_task", + "args": { + "task_id": "2", + "dependent_task_ids": ["1"], + "instruction": "Create the style.css file with the necessary CSS to style the 2048 game.", + "assignee": "Alex" + } + }, + { + "command_name": "Plan.append_task", + "args": { + "task_id": "3", + "dependent_task_ids": ["1", "2"], + "instruction": "Create the script.js file containing the JavaScript logic for the 2048 game.", + "assignee": "Alex" + } + }, + { + "command_name": "Plan.append_task", + "args": { + "task_id": "4", + "dependent_task_ids": ["1"], + "instruction": "Use ReviewAndRewriteCode to review the code in index.html to ensure it meets the design specifications.", + "assignee": "Alex" + } + }, + { + "command_name": "Plan.append_task", + "args": { + "task_id": "5", + "dependent_task_ids": ["2"], + "instruction": "Use ReviewAndRewriteCode to review the code in style.css to ensure it meets the design specifications.", + "assignee": "Alex" + } + }, + { + "command_name": "Plan.append_task", + "args": { + "task_id": "6", + "dependent_task_ids": ["3"], + "instruction": "Use ReviewAndRewriteCode to review the code in script.js to ensure it meets the design specifications.", + "assignee": "Alex" + } + } +] +``` +""" + +ENGINEER_EXPERIENCES = ((REQ1, RESP1), (REQ2, RESP2)) diff --git a/examples/exp_pool/init_exp_pool/main.py b/examples/exp_pool/init_exp_pool/main.py new file mode 100644 index 000000000..849834513 --- /dev/null +++ b/examples/exp_pool/init_exp_pool/main.py @@ -0,0 +1,55 @@ +"""Init experience pool. + +Put some useful experiences into the experience pool. +""" + +import asyncio + +from examples.exp_pool.init_exp_pool.engineer_exps import ENGINEER_EXPERIENCES +from examples.exp_pool.init_exp_pool.team_leader_exps import TEAM_LEADER_EXPERIENCES +from metagpt.exp_pool import exp_manager +from metagpt.exp_pool.schema import EntryType, Experience, Metric, Score +from metagpt.logs import logger + + +async def add_exp(req: str, resp: str, tag: str, metric: Metric = None): + exp = Experience( + req=req, + resp=resp, + entry_type=EntryType.MANUAL, + tag=tag, + metric=metric or Metric(score=Score(val=10, reason="Manual")), + ) + + exp_manager.config.exp_pool.enable_write = True + exp_manager.create_exp(exp) + logger.info(f"New experience created for the request `{req[:10]}`.") + + +async def add_teamleader_exps(): + tag = "TeamLeader.llm_cached_aask" + + for req, resp in TEAM_LEADER_EXPERIENCES: + await add_exp(req=req, resp=resp, tag=tag) + + +async def add_engineer_exps(): + tag = "Engineer2.llm_cached_aask" + + for req, resp in ENGINEER_EXPERIENCES: + await add_exp(req=req, resp=resp, tag=tag) + + +def query_exps_count(): + count = exp_manager.get_exps_count() + logger.info(f"Experiences Count: {count}") + + +async def main(): + await add_teamleader_exps() + await add_engineer_exps() + query_exps_count() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/exp_pool/init_exp_pool/team_leader_exps.py b/examples/exp_pool/init_exp_pool/team_leader_exps.py new file mode 100644 index 000000000..da0bc3d81 --- /dev/null +++ b/examples/exp_pool/init_exp_pool/team_leader_exps.py @@ -0,0 +1,181 @@ +import json + +OBJ1 = [ + { + "role": "user", + "content": "\n# Data Structure\n\n\n# Current Plan\n{'goal': \"from to {''}: Create a cli snake game using Python.\", 'tasks': []}\n\n# Current Task\n\n\n# Example\n\n\n# Instruction\nYou are a team leader, and you are responsible for drafting tasks and routing tasks to your team members.\nYour team member:\nTim: Team Leader, \nAlice: Product Manager, efficiently create a successful product that meets market demands and user expectations\nBob: Architect, design a concise, usable, complete software system\nEve: Project Manager, break down tasks according to PRD/technical design, generate a task list, and analyze task dependencies to start with the prerequisite modules\nAlex: Engineer, Take on game, app, and web development\nDavid: DataAnalyst, 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 and github operation, etc.\n\nYou should NOT assign consecutive tasks to the same team member, instead, assign an aggregated task (or the complete requirement) and let the team member to decompose it.\nWhen creating a new plan involving multiple members, create all tasks at once.\nIf plan is created, you should track the progress based on team member feedback message, and update plan accordingly, such as Plan.finish_current_task, Plan.reset_task, Plan.replace_task, etc.\nYou should use TeamLeader.publish_team_message to team members, asking them to start their task. DONT omit any necessary info such as path, link, environment, programming language, framework, requirement, constraint from original content to team members because you are their sole info source.\nPay close attention to new user message, review the conversation history, use RoleZero.reply_to_human to respond to the user directly, DON'T ask your team members.\n\nNote:\n1. 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.\n2. 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 -> (optional: QaEngine if present) -> (optional: DataAnalyst if user requests deployment), each assigned ONE task. When publishing message to Product Manager, you should directly copy the full original user requirement.\n3. 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.\n4. If the requirement is a common-sense, logical, or math problem, you should respond directly without assigning any task to team members.\n5. If you think the requirement is not clear or ambiguous, you should ask the user for clarification immediately. Assign tasks only after all info is clear.\n6. It is helpful for Engineer to have both the system design and the project schedule for writing the code, so include paths of both files (if available) and remind Engineer to definitely read them when publishing message to Engineer.\n7. If the requirement is writing a TRD and software framework, you should assign it to Architect. When publishing message to Architect, you should directly copy the full original user requirement.\n\nPay close attention to the Example provided, you can reuse the example for your current situation if it fits.\nYou 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.\nIf you finish current task, you will automatically take the next task in the existing plan, use Plan.finish_task, DON'T append a new task.\n\n# Your commands in a json array, in the following output format. If there is nothing to do, use the pass or end command:\nSome text indicating your thoughts, such as how you should update the plan status, respond to inquiry, or seek for help. Then a json array of commands. You must output ONE and ONLY ONE json array. DON'T output multiple json arrays with thoughts between them.\n```json\n[\n {\n \"command_name\": str,\n \"args\": {\"arg_name\": arg_value, ...}\n },\n ...\n]\n```\nNotice: your output JSON data section must start with **```json [**\n", + } +] +OBJ2 = [ + { + "role": "user", + "content": "\n# Data Structure\n\n\n# Current Plan\n{'goal': \"from to {''}: 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.\", 'tasks': []}\n\n# Current Task\n\n\n# Example\n\n\n# Instruction\nYou are a team leader, and you are responsible for drafting tasks and routing tasks to your team members.\nYour team member:\nTim: Team Leader, \nAlice: Product Manager, efficiently create a successful product that meets market demands and user expectations\nBob: Architect, design a concise, usable, complete software system\nEve: Project Manager, break down tasks according to PRD/technical design, generate a task list, and analyze task dependencies to start with the prerequisite modules\nAlex: Engineer, Take on game, app, and web development\nDavid: DataAnalyst, 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 and github operation, etc.\n\nYou should NOT assign consecutive tasks to the same team member, instead, assign an aggregated task (or the complete requirement) and let the team member to decompose it.\nWhen creating a new plan involving multiple members, create all tasks at once.\nIf plan is created, you should track the progress based on team member feedback message, and update plan accordingly, such as Plan.finish_current_task, Plan.reset_task, Plan.replace_task, etc.\nYou should use TeamLeader.publish_team_message to team members, asking them to start their task. DONT omit any necessary info such as path, link, environment, programming language, framework, requirement, constraint from original content to team members because you are their sole info source.\nPay close attention to new user message, review the conversation history, use RoleZero.reply_to_human to respond to the user directly, DON'T ask your team members.\n\nNote:\n1. 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.\n2. 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 -> (optional: QaEngine if present) -> (optional: DataAnalyst if user requests deployment), each assigned ONE task. When publishing message to Product Manager, you should directly copy the full original user requirement.\n3. 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.\n4. If the requirement is a common-sense, logical, or math problem, you should respond directly without assigning any task to team members.\n5. If you think the requirement is not clear or ambiguous, you should ask the user for clarification immediately. Assign tasks only after all info is clear.\n6. It is helpful for Engineer to have both the system design and the project schedule for writing the code, so include paths of both files (if available) and remind Engineer to definitely read them when publishing message to Engineer.\n7. If the requirement is writing a TRD and software framework, you should assign it to Architect. When publishing message to Architect, you should directly copy the full original user requirement.\n\nPay close attention to the Example provided, you can reuse the example for your current situation if it fits.\nYou 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.\nIf you finish current task, you will automatically take the next task in the existing plan, use Plan.finish_task, DON'T append a new task.\n\n# Your commands in a json array, in the following output format. If there is nothing to do, use the pass or end command:\nSome text indicating your thoughts, such as how you should update the plan status, respond to inquiry, or seek for help. Then a json array of commands. You must output ONE and ONLY ONE json array. DON'T output multiple json arrays with thoughts between them.\n```json\n[\n {\n \"command_name\": str,\n \"args\": {\"arg_name\": arg_value, ...}\n },\n ...\n]\n```\nNotice: your output JSON data section must start with **```json [**\n", + } +] +OBJ4 = [ + { + "role": "user", + "content": "\n# Data Structure\n\n\n# Current Plan\n{'goal': \"from to {''}: how does the project go?\", 'tasks': []}\n\n# Current Task\n\n\n# Example\n\n\n# Instruction\nYou are a team leader, and you are responsible for drafting tasks and routing tasks to your team members.\nYour team member:\nTim: Team Leader, \nAlice: Product Manager, efficiently create a successful product that meets market demands and user expectations\nBob: Architect, design a concise, usable, complete software system\nEve: Project Manager, break down tasks according to PRD/technical design, generate a task list, and analyze task dependencies to start with the prerequisite modules\nAlex: Engineer, Take on game, app, and web development\nDavid: DataAnalyst, 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 and github operation, etc.\n\nYou should NOT assign consecutive tasks to the same team member, instead, assign an aggregated task (or the complete requirement) and let the team member to decompose it.\nWhen creating a new plan involving multiple members, create all tasks at once.\nIf plan is created, you should track the progress based on team member feedback message, and update plan accordingly, such as Plan.finish_current_task, Plan.reset_task, Plan.replace_task, etc.\nYou should use TeamLeader.publish_team_message to team members, asking them to start their task. DONT omit any necessary info such as path, link, environment, programming language, framework, requirement, constraint from original content to team members because you are their sole info source.\nPay close attention to new user message, review the conversation history, use RoleZero.reply_to_human to respond to the user directly, DON'T ask your team members.\n\nNote:\n1. 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.\n2. 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 -> (optional: QaEngine if present) -> (optional: DataAnalyst if user requests deployment), each assigned ONE task. When publishing message to Product Manager, you should directly copy the full original user requirement.\n3. 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.\n4. If the requirement is a common-sense, logical, or math problem, you should respond directly without assigning any task to team members.\n5. If you think the requirement is not clear or ambiguous, you should ask the user for clarification immediately. Assign tasks only after all info is clear.\n6. It is helpful for Engineer to have both the system design and the project schedule for writing the code, so include paths of both files (if available) and remind Engineer to definitely read them when publishing message to Engineer.\n7. If the requirement is writing a TRD and software framework, you should assign it to Architect. When publishing message to Architect, you should directly copy the full original user requirement.\n\nPay close attention to the Example provided, you can reuse the example for your current situation if it fits.\nYou 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.\nIf you finish current task, you will automatically take the next task in the existing plan, use Plan.finish_task, DON'T append a new task.\n\n# Your commands in a json array, in the following output format. If there is nothing to do, use the pass or end command:\nSome text indicating your thoughts, such as how you should update the plan status, respond to inquiry, or seek for help. Then a json array of commands. You must output ONE and ONLY ONE json array. DON'T output multiple json arrays with thoughts between them.\n```json\n[\n {\n \"command_name\": str,\n \"args\": {\"arg_name\": arg_value, ...}\n },\n ...\n]\n```\nNotice: your output JSON data section must start with **```json [**\n", + } +] + +REQ1 = json.dumps(OBJ1) +REQ2 = json.dumps(OBJ2) +REQ3 = """Conversation History: +[ + ..., + {'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",...], ...}}}}}, +] +""" +REQ4 = json.dumps(OBJ4) + +RESP1 = """ +Explanation: The requirement is about software development. Assign each tasks to a different team member based on their expertise. When publishing message to Product Manager, we copy original user requirement directly to ensure no information loss. +```json +[ + { + "command_name": "Plan.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 python snake game.", + "assignee": "Alice" + } + }, + { + "command_name": "Plan.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": "Plan.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": "Plan.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": "Plan.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" + } + }, + { + "command_name": "TeamLeader.publish_message", + "args": { + "content": "Create a cli snake game using Python", + "send_to": "Alice" + } + }, + { + "command_name": "RoleZero.reply_to_human", + "args": { + "content": "I have assigned the tasks to the team members. Alice will create the PRD, Bob will design the software architecture, Eve will break down the architecture into tasks, Alex will implement the core game logic, and Edward will write comprehensive tests. The team will work on the project accordingly", + } + }, + { + "command_name": "end" + } +] +``` +""" +RESP2 = """ +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": "Plan.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" + } + }, + { + "command_name": "TeamLeader.publish_message", + "args": { + "content": "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.", + "send_to": "David" + } + }, + { + "command_name": "RoleZero.reply_to_human", + "args": { + "content": "I have assigned the task to David. He will break down the task further by himself and starts solving it.", + } + }, + { + "command_name": "end" + } +] +``` +""" +RESP3 = """ +Explanation: You received a message from Alice, the Product Manager, that she has completed the PRD, use Plan.finish_current_task to mark her task as finished and moves the plan to the next task. Based on plan status, next task is for Bob (Architect), publish a message asking him to start. The message content should contain important path info. +```json +[ + { + "command_name": "Plan.finish_current_task", + "args": {} + }, + { + "command_name": "TeamLeader.publish_message", + "args": { + "content": "Please design the software architecture for the snake game based on the PRD created by Alice. The PRD is at 'docs/prd/20240424153821.json'. Include the choice of programming language, libraries, and data flow, etc.", + "send_to": "Bob" + } + }, + { + "command_name": "RoleZero.reply_to_human", + "args": { + "content": "Alice has completed the PRD. I have marked her task as finished and sent the PRD to Bob. Bob will work on the software architecture.", + } + }, + { + "command_name": "end" + } +] +``` +""" +RESP4 = """ +Explanation: The user is asking for a general update on the project status. Give a straight answer about the current task the team is working on and provide a summary of the completed tasks. +```json +[ + { + "command_name": "RoleZero.reply_to_human", + "args": { + "content": "The team is currently working on ... We have completed ...", + } + }, + { + "command_name": "end" + } +] +``` +""" + +TEAM_LEADER_EXPERIENCES = ( + (REQ1, RESP1), + (REQ2, RESP2), + (REQ3, RESP3), + (REQ4, RESP4), +) diff --git a/metagpt/exp_pool/manager.py b/metagpt/exp_pool/manager.py index 649210a79..ba1a8bcf0 100644 --- a/metagpt/exp_pool/manager.py +++ b/metagpt/exp_pool/manager.py @@ -7,16 +7,12 @@ from metagpt.config2 import Config, config from metagpt.exp_pool.schema import ( DEFAULT_COLLECTION_NAME, DEFAULT_SIMILARITY_TOP_K, - EntryType, Experience, - Metric, QueryType, - Score, ) from metagpt.logs import logger from metagpt.rag.engines import SimpleEngine from metagpt.rag.schema import ChromaRetrieverConfig, LLMRankerConfig -from metagpt.strategy.experience_retriever import ENGINEER_EXAMPLE, TL_EXAMPLE from metagpt.utils.exceptions import handle_exception @@ -50,17 +46,9 @@ class ExperienceManager(BaseModel): logger.debug(f"exp_pool config: {self.config.exp_pool}") return self - @handle_exception - def init_exp_pool(self): - if not self.config.exp_pool.enable_write: - return - - if self._has_exps(): - return - - self._init_teamleader_exps() - self._init_engineer2_exps() - logger.info("`init_exp_pool` done.") + @property + def vector_store(self) -> ChromaVectorStore: + return self.storage._retriever._vector_store @handle_exception def create_exp(self, exp: Experience): @@ -101,26 +89,8 @@ class ExperienceManager(BaseModel): return exps - def _has_exps(self) -> bool: - vector_store: ChromaVectorStore = self.storage._retriever._vector_store - - return bool(vector_store._get(limit=1, where={}).ids) - - def _init_exp(self, req: str, resp: str, tag: str, metric: Metric = None): - exp = Experience( - req=req, - resp=resp, - entry_type=EntryType.MANUAL, - tag=tag, - metric=metric or Metric(score=Score(val=9, reason="Manual")), - ) - self.create_exp(exp) - - def _init_teamleader_exps(self): - self._init_exp(req=TL_EXAMPLE, resp=TL_EXAMPLE, tag="TeamLeader.llm_cached_aask") - - def _init_engineer2_exps(self): - self._init_exp(req=ENGINEER_EXAMPLE, resp=ENGINEER_EXAMPLE, tag="Engineer2.llm_cached_aask") + def get_exps_count(self) -> int: + return self.vector_store._collection.count() exp_manager = ExperienceManager() diff --git a/tests/metagpt/exp_pool/test_manager.py b/tests/metagpt/exp_pool/test_manager.py index 6d0693efd..2f712ad44 100644 --- a/tests/metagpt/exp_pool/test_manager.py +++ b/tests/metagpt/exp_pool/test_manager.py @@ -62,21 +62,3 @@ class TestExperienceManager: mock_config.exp_pool.enable_read = False result = await mock_experience_manager.query_exps("query") assert result == [] - - def test_has_exps(self, mock_experience_manager, mock_storage): - mock_storage._retriever._vector_store._get.return_value.ids = ["id1"] - - assert mock_experience_manager._has_exps() is True - - mock_storage._retriever._vector_store._get.return_value.ids = [] - assert mock_experience_manager._has_exps() is False - - def test_init_teamleader_exps(self, mock_experience_manager, mocker): - mock_experience_manager._init_exp = mocker.MagicMock() - mock_experience_manager._init_teamleader_exps() - mock_experience_manager._init_exp.assert_called_once() - - def test_init_engineer2_exps(self, mock_experience_manager, mocker): - mock_experience_manager._init_exp = mocker.MagicMock() - mock_experience_manager._init_engineer2_exps() - mock_experience_manager._init_exp.assert_called_once() diff --git a/tests/metagpt/exp_pool/test_serializers/test_action_node.py b/tests/metagpt/exp_pool/test_serializers/test_action_node.py new file mode 100644 index 000000000..e4ab4684d --- /dev/null +++ b/tests/metagpt/exp_pool/test_serializers/test_action_node.py @@ -0,0 +1,35 @@ +from typing import Type + +import pytest + +from metagpt.actions.action_node import ActionNode +from metagpt.exp_pool.serializers.action_node import ActionNodeSerializer + + +class TestActionNodeSerializer: + @pytest.fixture + def serializer(self): + return ActionNodeSerializer() + + @pytest.fixture + def action_node(self): + class InstructContent: + def __init__(self, json_data): + self.json_data = json_data + + def model_dump_json(self): + return self.json_data + + action_node = ActionNode(key="", expected_type=Type[str], instruction="", example="") + action_node.instruct_content = InstructContent('{"key": "value"}') + + return action_node + + def test_serialize_resp(self, serializer: ActionNodeSerializer, action_node: ActionNode): + serialized = serializer.serialize_resp(action_node) + assert serialized == '{"key": "value"}' + + def test_deserialize_resp(self, serializer: ActionNodeSerializer): + deserialized = serializer.deserialize_resp('{"key": "value"}') + assert isinstance(deserialized, ActionNode) + assert deserialized.instruct_content.model_dump_json() == '{"key": "value"}' diff --git a/tests/metagpt/exp_pool/test_serializers/test_role_zero.py b/tests/metagpt/exp_pool/test_serializers/test_role_zero.py new file mode 100644 index 000000000..4c1f3daf3 --- /dev/null +++ b/tests/metagpt/exp_pool/test_serializers/test_role_zero.py @@ -0,0 +1,77 @@ +import json + +import pytest + +from metagpt.exp_pool.serializers import RoleZeroSerializer + + +class TestRoleZeroSerializer: + @pytest.fixture + def serializer(self): + return RoleZeroSerializer() + + def test_serialize_req_empty_input(self, serializer: RoleZeroSerializer): + assert serializer.serialize_req([]) == "" + + def test_serialize_req_with_content(self, serializer: RoleZeroSerializer): + req = [ + {"content": "Command Editor.read executed: file_path=test.py"}, + {"content": "Some other content"}, + { + "content": "# Data Structure\nsome data\n# Current Plan\nsome plan\n# Example\nsome example\n# Instruction\nsome instruction" + }, + ] + expected_output = json.dumps( + [ + {"content": "Command Editor.read executed: file_path=test.py"}, + { + "content": "# Data Structure\n\n\n# Current Plan\nsome plan\n# Example\n\n\n# Instruction\nsome instruction" + }, + ] + ) + assert serializer.serialize_req(req) == expected_output + + def test_filter_req(self, serializer: RoleZeroSerializer): + req = [ + {"content": "Command Editor.read executed: file_path=test1.py"}, + {"content": "Some other content"}, + {"content": "Command Editor.read executed: file_path=test2.py"}, + {"content": "Final content"}, + ] + filtered_req = serializer._filter_req(req) + assert len(filtered_req) == 3 + assert filtered_req[0]["content"] == "Command Editor.read executed: file_path=test1.py" + assert filtered_req[1]["content"] == "Command Editor.read executed: file_path=test2.py" + assert filtered_req[2]["content"] == "Final content" + + def test_clean_last_entry_content(self, serializer: RoleZeroSerializer): + req = [ + {"content": "Some content"}, + { + "content": "# Data Structure\nsome data\n# Current Plan\nsome plan\n# Example\nsome example\n# Instruction\nsome instruction" + }, + ] + serializer._clean_last_entry_content(req) + expected_content = ( + "# Data Structure\n\n\n# Current Plan\nsome plan\n# Example\n\n\n# Instruction\nsome instruction" + ) + assert req[-1]["content"] == expected_content + + def test_integration(self, serializer: RoleZeroSerializer): + req = [ + {"content": "Command Editor.read executed: file_path=test.py"}, + {"content": "Some other content"}, + { + "content": "# Data Structure\nsome data\n# Current Plan\nsome plan\n# Example\nsome example\n# Instruction\nsome instruction" + }, + ] + result = serializer.serialize_req(req) + expected_output = json.dumps( + [ + {"content": "Command Editor.read executed: file_path=test.py"}, + { + "content": "# Data Structure\n\n\n# Current Plan\nsome plan\n# Example\n\n\n# Instruction\nsome instruction" + }, + ] + ) + assert result == expected_output diff --git a/tests/metagpt/exp_pool/test_serializers/test_simple.py b/tests/metagpt/exp_pool/test_serializers/test_simple.py new file mode 100644 index 000000000..05ef1ca11 --- /dev/null +++ b/tests/metagpt/exp_pool/test_serializers/test_simple.py @@ -0,0 +1,44 @@ +import pytest + +from metagpt.exp_pool.serializers.simple import SimpleSerializer + + +class TestSimpleSerializer: + @pytest.fixture + def serializer(self): + return SimpleSerializer() + + def test_serialize_req(self, serializer): + # Test with different types of input + assert serializer.serialize_req(123) == "123" + assert serializer.serialize_req("test") == "test" + assert serializer.serialize_req([1, 2, 3]) == "[1, 2, 3]" + assert serializer.serialize_req({"a": 1}) == "{'a': 1}" + + def test_serialize_resp(self, serializer): + # Test with different types of input + assert serializer.serialize_resp(456) == "456" + assert serializer.serialize_resp("response") == "response" + assert serializer.serialize_resp([4, 5, 6]) == "[4, 5, 6]" + assert serializer.serialize_resp({"b": 2}) == "{'b': 2}" + + def test_deserialize_resp(self, serializer): + # Test with different types of input + assert serializer.deserialize_resp("789") == "789" + assert serializer.deserialize_resp("test_response") == "test_response" + assert serializer.deserialize_resp("[7, 8, 9]") == "[7, 8, 9]" + assert serializer.deserialize_resp("{'c': 3}") == "{'c': 3}" + + def test_roundtrip(self, serializer): + # Test serialization and deserialization roundtrip + original = "test_roundtrip" + serialized = serializer.serialize_resp(original) + deserialized = serializer.deserialize_resp(serialized) + assert deserialized == original + + @pytest.mark.parametrize("input_value", [123, "test", [1, 2, 3], {"a": 1}, None]) + def test_serialize_req_types(self, serializer, input_value): + # Test serialize_req with various input types + result = serializer.serialize_req(input_value) + assert isinstance(result, str) + assert result == str(input_value)