diff --git a/metagpt/actions/design_api_an.py b/metagpt/actions/design_api_an.py index 3e65cee1b..b872159e1 100644 --- a/metagpt/actions/design_api_an.py +++ b/metagpt/actions/design_api_an.py @@ -9,7 +9,7 @@ from typing import List from metagpt.actions.action_node import ActionNode from metagpt.logs import logger -from metagpt.utils.mermaid import MMC1, MMC1_REFINE, MMC2, MMC2_REFINE +from metagpt.utils.mermaid import MMC1, MMC2 IMPLEMENTATION_APPROACH = ActionNode( key="Implementation approach", @@ -62,7 +62,7 @@ REFINED_DATA_STRUCTURES_AND_INTERFACES = ActionNode( "methods (including __init__), and functions with precise type annotations. Delineate additional " "relationships between classes, ensuring clarity and adherence to PEP8 standards." "Retain content that is not related to incremental development but important for consistency and clarity.", - example=MMC1_REFINE, + example=MMC1, ) PROGRAM_CALL_FLOW = ActionNode( @@ -80,7 +80,7 @@ REFINED_PROGRAM_CALL_FLOW = ActionNode( "CRUD and initialization of each object. Ensure correct syntax usage and reflect the incremental changes introduced" "in the classes and API defined above. " "Retain content that is not related to incremental development but important for consistency and clarity.", - example=MMC2_REFINE, + example=MMC2, ) ANYTHING_UNCLEAR = ActionNode( diff --git a/metagpt/actions/project_management_an.py b/metagpt/actions/project_management_an.py index 8b0196707..e3d6537ab 100644 --- a/metagpt/actions/project_management_an.py +++ b/metagpt/actions/project_management_an.py @@ -118,7 +118,7 @@ REFINE_NODES = [ ] PM_NODE = ActionNode.from_children("PM_NODE", NODES) -REFINED_PM_NODES = ActionNode.from_children("Refined_PM_NODES", REFINE_NODES) +REFINED_PM_NODES = ActionNode.from_children("REFINED_PM_NODES", REFINE_NODES) def main(): diff --git a/metagpt/actions/write_code.py b/metagpt/actions/write_code.py index 30ebf09f8..93c7a2f65 100644 --- a/metagpt/actions/write_code.py +++ b/metagpt/actions/write_code.py @@ -16,18 +16,21 @@ """ import json +from typing import Literal from pydantic import Field from tenacity import retry, stop_after_attempt, wait_random_exponential from metagpt.actions.action import Action from metagpt.actions.project_management_an import REFINED_TASK_LIST, TASK_LIST -from metagpt.actions.write_code_guideline_an import REFINED_CODE_TEMPLATE +from metagpt.actions.write_code_plan_an import REFINED_CODE_TEMPLATE from metagpt.config import CONFIG from metagpt.const import ( BUGFIX_FILENAME, CODE_SUMMARIES_FILE_REPO, DOCS_FILE_REPO, + PLAN_FILENAME, + PLAN_PDF_FILE_REPO, REQUIREMENT_FILENAME, TASK_FILE_REPO, TEST_OUTPUTS_FILE_REPO, @@ -104,6 +107,9 @@ class WriteCode(Action): test_doc = await FileRepository.get_file( filename="test_" + coding_context.filename + ".json", relative_path=TEST_OUTPUTS_FILE_REPO ) + plan_doc = await FileRepository.get_file(filename=PLAN_FILENAME, relative_path=PLAN_PDF_FILE_REPO) + plan = plan_doc.content if plan_doc else "" + requirement_doc = await FileRepository.get_file(filename=REQUIREMENT_FILENAME, relative_path=DOCS_FILE_REPO) summary_doc = None if coding_context.design_doc and coding_context.design_doc.filename: summary_doc = await FileRepository.get_file( @@ -114,21 +120,17 @@ class WriteCode(Action): test_detail = RunCodeResult.loads(test_doc.content) logs = test_detail.stderr - docs_file_repo = CONFIG.git_repo.new_file_repository(relative_path=DOCS_FILE_REPO) - requirement_doc = await docs_file_repo.get(filename=REQUIREMENT_FILENAME) - - guideline = kwargs.get("guideline", "") if bug_feedback: code_context = coding_context.code_doc.content - elif guideline: - code_context = await self.get_codes(coding_context.task_doc, exclude=self.context.filename, mode="guide") + elif plan: + code_context = await self.get_codes(coding_context.task_doc, exclude=self.context.filename, mode="plan") else: code_context = await self.get_codes(coding_context.task_doc, exclude=self.context.filename) - if guideline: + if plan: prompt = REFINED_CODE_TEMPLATE.format( user_requirement=requirement_doc.content if requirement_doc else "", - guideline=guideline, + plan=plan, design=coding_context.design_doc.content if coding_context.design_doc else "", tasks=coding_context.task_doc.content if coding_context.task_doc else "", code=code_context, @@ -157,14 +159,14 @@ class WriteCode(Action): return coding_context @staticmethod - async def get_codes(task_doc, exclude, mode="normal") -> str: + async def get_codes(task_doc: Document, exclude: str, mode: Literal["normal", "plan"] = "normal") -> str: """ Get code snippets based on different modes. Attributes: task_doc (Document): Document object of the task file. exclude (str): Specifies the filename to be excluded from the code snippets. - mode (str): Specifies the mode, either "normal" or "guide" (default is "normal"). + mode (str): Specifies the mode, either "normal" or "plan" (default is "normal"). Returns: str: Code snippets. @@ -173,7 +175,7 @@ class WriteCode(Action): If mode is set to "normal", it returns code snippets for the regular coding phase, i.e., all the code generated before writing the current file. - If mode is set to "guide", it returns code snippets for incremental development, + If mode is set to "plan", it returns code snippets for incremental development, building upon the existing code in the "normal" mode and adding code for the current file's older versions. """ if not task_doc: @@ -185,31 +187,36 @@ class WriteCode(Action): codes = [] src_file_repo = CONFIG.git_repo.new_file_repository(relative_path=CONFIG.src_workspace) - if mode == "guide": + if mode == "plan": src_files = src_file_repo.all_files old_file_repo = CONFIG.git_repo.new_file_repository(relative_path=CONFIG.old_workspace) old_files = old_file_repo.all_files + # Get the union of the files in the src and old workspaces union_files_list = list(set(src_files) | set(old_files)) for filename in union_files_list: + # Exclude the current file from the all code snippets to get the context code snippets for generating if filename == exclude: + # If the file is in the old workspace, use the legacy code # Exclude unnecessary code to maintain a clean and focused main.py file, ensuring only relevant and # essential functionality is included for the project’s requirements if filename in old_files and filename != "main.py": # Use legacy code doc = await old_file_repo.get(filename=filename) + # If the file is in the src workspace, skip it else: continue codes.insert(0, f"-----Now, {filename} to be rewritten\n```{doc.content}```\n=====") - + # The context code snippets are generated from the src workspace else: - # Use new code doc = await src_file_repo.get(filename=filename) + # If the file does not exist in the src workspace, skip it if not doc: continue codes.append(f"----- {filename}\n```{doc.content}```") - else: + elif mode == "normal": for filename in code_filenames: + # Exclude the current file to get the context code snippets for generating the current file if filename == exclude: continue doc = await src_file_repo.get(filename=filename) diff --git a/metagpt/actions/write_code_guideline_an.py b/metagpt/actions/write_code_plan_an.py similarity index 68% rename from metagpt/actions/write_code_guideline_an.py rename to metagpt/actions/write_code_plan_an.py index 28af592c0..f3f4177e4 100644 --- a/metagpt/actions/write_code_guideline_an.py +++ b/metagpt/actions/write_code_plan_an.py @@ -3,23 +3,21 @@ """ @Time : 2023/12/26 @Author : mannaandpoem -@File : write_code_guideline_an.py +@File : write_code_plan_an.py """ from metagpt.actions.action import Action -from metagpt.actions.action_node import ActionNode, dict_to_markdown -from metagpt.config import CONFIG -from metagpt.const import CODE_GUIDELINE_FILE_REPO, CODE_GUIDELINE_PDF_FILE_REPO +from metagpt.actions.action_node import ActionNode -GUIDELINES_AND_INCREMENTAL_CHANGE = ActionNode( - key="Guidelines and Incremental Change", +Plan = ActionNode( + key="Plan", expected_type=str, - instruction="Developing comprehensive and step-by-step incremental development guideline, and Write Incremental " + instruction="Developing comprehensive and step-by-step incremental development plan, and write Incremental " "Change by making a code draft that how to implement incremental development including detailed steps based on the " "context. Note: Track incremental changes using mark of '+' or '-' for add/modify/delete code, and conforms to the " "output format of git diff", example=""" -1. Guideline for calculator.py: Enhance the functionality of `calculator.py` by extending it to incorporate methods for subtraction, multiplication, and division. Additionally, implement robust error handling for the division operation to mitigate potential issues related to division by zero. +1. Plan for calculator.py: Enhance the functionality of `calculator.py` by extending it to incorporate methods for subtraction, multiplication, and division. Additionally, implement robust error handling for the division operation to mitigate potential issues related to division by zero. ```python class Calculator: self.result = number1 + number2 @@ -75,7 +73,7 @@ class Calculator: self.result = 0.0 ``` -2. Guideline for main.py: Integrate new API endpoints for subtraction, multiplication, and division into the existing codebase of `main.py`. Then, ensure seamless integration with the overall application architecture and maintain consistency with coding standards. +2. Plan for main.py: Integrate new API endpoints for subtraction, multiplication, and division into the existing codebase of `main.py`. Then, ensure seamless integration with the overall application architecture and maintain consistency with coding standards. ```python def add_numbers(): result = calculator.add_numbers(num1, num2) @@ -106,7 +104,7 @@ def add_numbers(): ```""", ) -CODE_GUIDELINE_CONTEXT = """ +CODE_PLAN_CONTEXT = """ ## User New Requirements {user_requirement} @@ -125,14 +123,14 @@ CODE_GUIDELINE_CONTEXT = """ REFINED_CODE_TEMPLATE = """ NOTICE -Role: You are a professional engineer; The main goal is to complete incremental development by combining legacy code and Guidelines and Incremental Change, ensuring the integration of new features. +Role: You are a professional engineer; The main goal is to complete incremental development by combining legacy code and plan and Incremental Change, ensuring the integration of new features. # Context ## User New Requirements {user_requirement} -## Guidelines and Incremental Change -{guideline} +## Plan +{plan} ## Design {design} @@ -170,32 +168,18 @@ Role: You are a professional engineer; The main goal is to complete incremental 2. COMPLETE CODE: Your code will be part of the entire project, so please implement complete, reliable, reusable code snippets. 3. Set default value: If there is any setting, ALWAYS SET A DEFAULT VALUE, ALWAYS USE STRONG TYPE AND EXPLICIT VARIABLE. AVOID circular import. 4. Follow 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. -5. Follow Guidelines and Incremental Change: If there is any Incremental Change or Legacy Code files contain "{filename} to be rewritten", you must merge it into the code file according to the guidelines. +5. Follow plan and Incremental Change: If there is any Incremental Change or Legacy Code files contain "{filename} to be rewritten", you must merge it into the code file according to the plan. 6. CAREFULLY CHECK THAT YOU DONT MISS ANY NECESSARY CLASS/FUNCTION IN THIS FILE. 7. Before using a external variable/module, make sure you import it first. 8. Write out EVERY CODE DETAIL, DON'T LEAVE TODO. 9. Attention: Retain content that is not related to incremental development but important for consistency and clarity.". """ -WRITE_CODE_GUIDELINE_NODE = ActionNode.from_children("WriteCodeGuideline", [GUIDELINES_AND_INCREMENTAL_CHANGE]) +WRITE_CODE_PLAN_NODE = ActionNode.from_children("WriteCodePlan", [Plan]) -class WriteCodeGuideline(Action): +class WriteCodePlan(Action): async def run(self, context): self.llm.system_prompt = "You are a professional software engineer, your primary responsibility is to " - "meticulously craft comprehensive incremental development guidelines and deliver detailed Incremental Change" - return await WRITE_CODE_GUIDELINE_NODE.fill(context=context, llm=self.llm, schema="json") - - @staticmethod - async def save_json(guideline, filename="code_guideline.json"): - await CONFIG.git_repo.new_file_repository(CODE_GUIDELINE_FILE_REPO).save( - filename=filename, content=str(guideline) - ) - - @staticmethod - async def save_md(guideline, filename="code_guideline.md"): - guideline_md = dict_to_markdown(guideline) - await CONFIG.git_repo.new_file_repository(CODE_GUIDELINE_PDF_FILE_REPO).save( - filename=filename, content=guideline_md - ) - return guideline_md + "meticulously craft comprehensive incremental development plan and deliver detailed Incremental Change" + return await WRITE_CODE_PLAN_NODE.fill(context=context, llm=self.llm, schema="json") diff --git a/metagpt/actions/write_code_review.py b/metagpt/actions/write_code_review.py index 27e4c280e..72424c037 100644 --- a/metagpt/actions/write_code_review.py +++ b/metagpt/actions/write_code_review.py @@ -14,10 +14,16 @@ from tenacity import retry, stop_after_attempt, wait_random_exponential from metagpt.actions import WriteCode from metagpt.actions.action import Action from metagpt.config import CONFIG -from metagpt.const import DOCS_FILE_REPO, REQUIREMENT_FILENAME +from metagpt.const import ( + DOCS_FILE_REPO, + PLAN_FILENAME, + PLAN_PDF_FILE_REPO, + REQUIREMENT_FILENAME, +) from metagpt.logs import logger from metagpt.schema import CodingContext from metagpt.utils.common import CodeParser +from metagpt.utils.file_repository import FileRepository PROMPT_TEMPLATE = """ # System @@ -138,16 +144,17 @@ class WriteCodeReview(Action): async def run(self, *args, **kwargs) -> CodingContext: iterative_code = self.context.code_doc.content - # k = CONFIG.code_review_k_times or 1 - k = 1 - guideline = kwargs.get("guideline") - mode = "guide" if guideline else "normal" + k = CONFIG.code_review_k_times or 1 + plan_doc = await FileRepository.get_file(filename=PLAN_FILENAME, relative_path=PLAN_PDF_FILE_REPO) + plan = plan_doc.content if plan_doc else "" + mode = "plan" if plan else "normal" + for i in range(k): format_example = FORMAT_EXAMPLE.format(filename=self.context.code_doc.filename) task_content = self.context.task_doc.content if self.context.task_doc else "" code_context = await WriteCode.get_codes(self.context.task_doc, exclude=self.context.filename, mode=mode) - if not guideline: + if not plan: context = "\n".join( [ "## System Design\n" + str(self.context.design_doc) + "\n", @@ -156,15 +163,15 @@ class WriteCodeReview(Action): ] ) else: - requirement_doc = await CONFIG.git_repo.new_file_repository(relative_path=DOCS_FILE_REPO).get( - filename=REQUIREMENT_FILENAME + requirement_doc = await FileRepository.get_file( + filename=REQUIREMENT_FILENAME, relative_path=DOCS_FILE_REPO ) user_requirement = requirement_doc.content if requirement_doc else "" context = "\n".join( [ - "## User New Requirements\n" + str(user_requirement) + "\n", - "## Guidelines and Incremental Change\n" + guideline + "\n", + "## User New Requirements\n" + user_requirement + "\n", + "## Plan\n" + plan + "\n", "## System Design\n" + str(self.context.design_doc) + "\n", "## Tasks\n" + task_content + "\n", "## Code Files\n" + code_context + "\n", diff --git a/metagpt/const.py b/metagpt/const.py index 2ee1267d8..014193b59 100644 --- a/metagpt/const.py +++ b/metagpt/const.py @@ -87,19 +87,20 @@ MESSAGE_ROUTE_TO_NONE = "" REQUIREMENT_FILENAME = "requirement.txt" BUGFIX_FILENAME = "bugfix.txt" PACKAGE_REQUIREMENTS_FILENAME = "requirements.txt" +PLAN_FILENAME = "plan.json" DOCS_FILE_REPO = "docs" PRDS_FILE_REPO = "docs/prds" SYSTEM_DESIGN_FILE_REPO = "docs/system_design" TASK_FILE_REPO = "docs/tasks" -CODE_GUIDELINE_FILE_REPO = "docs/code_guideline" +PLAN_FILE_REPO = "docs/plan" COMPETITIVE_ANALYSIS_FILE_REPO = "resources/competitive_analysis" DATA_API_DESIGN_FILE_REPO = "resources/data_api_design" SEQ_FLOW_FILE_REPO = "resources/seq_flow" SYSTEM_DESIGN_PDF_FILE_REPO = "resources/system_design" PRD_PDF_FILE_REPO = "resources/prd" TASK_PDF_FILE_REPO = "resources/api_spec_and_tasks" -CODE_GUIDELINE_PDF_FILE_REPO = "resources/code_guideline" +PLAN_PDF_FILE_REPO = "resources/plan" TEST_CODES_FILE_REPO = "tests" TEST_OUTPUTS_FILE_REPO = "test_outputs" CODE_SUMMARIES_FILE_REPO = "docs/code_summaries" diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py index d767b1ebf..695e9dd2a 100644 --- a/metagpt/roles/engineer.py +++ b/metagpt/roles/engineer.py @@ -23,21 +23,21 @@ import json import os from collections import defaultdict from pathlib import Path -from typing import Set +from typing import Literal, Set from metagpt.actions import Action, WriteCode, WriteCodeReview, WriteTasks from metagpt.actions.fix_bug import FixBug +from metagpt.actions.project_management_an import REFINED_TASK_LIST, TASK_LIST from metagpt.actions.summarize_code import SummarizeCode -from metagpt.actions.write_code_guideline_an import ( - CODE_GUIDELINE_CONTEXT, - WriteCodeGuideline, -) +from metagpt.actions.write_code_plan_an import CODE_PLAN_CONTEXT, WriteCodePlan +from metagpt.actions.write_prd_an import REFINED_REQUIREMENT_POOL, REQUIREMENT_POOL from metagpt.config import CONFIG from metagpt.const import ( - CODE_GUIDELINE_PDF_FILE_REPO, CODE_SUMMARIES_FILE_REPO, CODE_SUMMARIES_PDF_FILE_REPO, - PRDS_FILE_REPO, + PLAN_FILE_REPO, + PLAN_FILENAME, + PLAN_PDF_FILE_REPO, SYSTEM_DESIGN_FILE_REPO, TASK_FILE_REPO, ) @@ -51,6 +51,7 @@ from metagpt.schema import ( Message, ) from metagpt.utils.common import any_to_name, any_to_str, any_to_str_set +from metagpt.utils.file_repository import FileRepository IS_PASS_PROMPT = """ {context} @@ -100,9 +101,9 @@ class Engineer(Role): @staticmethod def _parse_tasks(task_msg: Document) -> list[str]: m = json.loads(task_msg.content) - return m.get("Task list") or m.get("Refined Task list") + return m.get(TASK_LIST.key) or m.get(REFINED_TASK_LIST.key) - async def _act_sp_with_cr(self, review=False, guideline=Document()) -> Set[str]: + async def _act_sp_with_cr(self, review=False, mode: Literal["normal", "plan"] = "normal") -> Set[str]: changed_files = set() src_file_repo = CONFIG.git_repo.new_file_repository(CONFIG.src_workspace) for todo in self.code_todos: @@ -113,16 +114,16 @@ class Engineer(Role): 3. Do we need other codes (currently needed)? TODO: The goal is not to need it. After clear task decomposition, based on the design idea, you should be able to write a single file without needing other codes. If you can't, it means you need a clearer definition. This is the key to writing longer code. """ - coding_context = await todo.run(guideline=guideline.content) + coding_context = await todo.run() # Code review if review: action = WriteCodeReview(context=coding_context, llm=self.llm) self._init_action_system_message(action) - coding_context = await action.run(guideline=guideline.content) + coding_context = await action.run() dependencies = {coding_context.design_doc.root_relative_path, coding_context.task_doc.root_relative_path} - if guideline.content: - dependencies.add(guideline.root_relative_path) + if mode == "plan": + dependencies.add(os.path.join(PLAN_PDF_FILE_REPO, PLAN_FILENAME)) await src_file_repo.save( coding_context.filename, dependencies=dependencies, @@ -155,10 +156,8 @@ class Engineer(Role): async def _act_write_code(self): if CONFIG.inc: - code_guideline = await self._write_code_guideline() - changed_files = await self._act_sp_with_cr(review=self.use_code_review, guideline=code_guideline) - else: - changed_files = await self._act_sp_with_cr(review=self.use_code_review) + await self._write_code_plan() + changed_files = await self._act_sp_with_cr(review=self.use_code_review) return Message( content="\n".join(changed_files), role=self.profile, @@ -335,41 +334,38 @@ class Engineer(Role): """AgentStore uses this attribute to display to the user what actions the current role should take.""" return self.next_todo_action - async def _write_code_guideline(self): - """Write some guidelines that guides subsequent WriteCode and WriteCodeReview""" - logger.info("Writing code guideline..") + async def _write_code_plan(self): + """Write code plan that guides subsequent WriteCode and WriteCodeReview""" + logger.info("Writing code plan..") user_requirement = str(self.rc.memory.get_by_role("Human")[0]) - contents = [] - prd = await CONFIG.git_repo.new_file_repository(PRDS_FILE_REPO).get_all() + pool_contents = [] + prd = await FileRepository.get_all_files(relative_path=PLAN_PDF_FILE_REPO) for doc in prd: prd_json = json.loads(doc.content) - product_requirement_pool = prd_json.get("Requirement Pool", prd_json.get("Refined Requirement Pool")) - contents.append(str(product_requirement_pool)) + product_requirement_pool = prd_json.get(REFINED_REQUIREMENT_POOL.key) or prd_json.get(REQUIREMENT_POOL.key) + pool_contents.append(str(product_requirement_pool)) - product_requirement_pools = "\n".join(contents) + product_requirement_pools = "\n".join(pool_contents) - design = await CONFIG.git_repo.new_file_repository(SYSTEM_DESIGN_FILE_REPO).get_all() + design = await FileRepository.get_all_files(relative_path=SYSTEM_DESIGN_FILE_REPO) design = "\n".join([doc.content for doc in design]) - tasks = await CONFIG.git_repo.new_file_repository(TASK_FILE_REPO).get_all() + tasks = await FileRepository.get_all_files(relative_path=TASK_FILE_REPO) tasks = "\n".join([doc.content for doc in tasks]) old_codes = await self.get_old_codes() - context = CODE_GUIDELINE_CONTEXT.format( + context = CODE_PLAN_CONTEXT.format( user_requirement=user_requirement, product_requirement_pools=product_requirement_pools, tasks=tasks, design=design, code=old_codes, ) - node = await WriteCodeGuideline().run(context=context) - guideline = node.instruct_content.model_dump() - await WriteCodeGuideline.save_json(guideline) - guideline_md = await WriteCodeGuideline.save_md(guideline) - - return Document(root_path=CODE_GUIDELINE_PDF_FILE_REPO, filename="code_guideline.md", content=guideline_md) + node = await WriteCodePlan().run(context=context) + plan = node.instruct_content.model_dump_json() + CONFIG.git_repo.new_file_repository(PLAN_FILE_REPO).save(filename=PLAN_FILENAME, content=plan) @staticmethod async def get_old_codes() -> str: diff --git a/metagpt/utils/mermaid.py b/metagpt/utils/mermaid.py index 25c593e6c..7762784d8 100644 --- a/metagpt/utils/mermaid.py +++ b/metagpt/utils/mermaid.py @@ -121,46 +121,6 @@ classDiagram Index --> KnowledgeBase """ -MMC1_REFINE = """ -classDiagram - class Main { - -SearchEngine search_engine - +main() str - +newMethod() str # Incremental change - } - class SearchEngine { - -Index index - -Ranking ranking - -Summary summary - +search(query: str) str - +newMethod() str # Incremental change - } - class Index { - -KnowledgeBase knowledge_base - +create_index(data: dict) - +query_index(query: str) list - +newMethod() list # Incremental change - } - class Ranking { - +rank_results(results: list) list - +newMethod() list # Incremental change - } - class Summary { - +summarize_results(results: list) str - +newMethod() str # Incremental change - } - class KnowledgeBase { - +update(data: dict) - +fetch_data(query: str) dict - +newMethod() # Incremental change - } - Main --> SearchEngine - SearchEngine --> Index - SearchEngine --> Ranking - SearchEngine --> Summary - Index --> KnowledgeBase -""" - MMC2 = """ sequenceDiagram participant M as Main @@ -180,32 +140,3 @@ sequenceDiagram S-->>SE: return summary SE-->>M: return summary """ - -MMC2_REFINE = """ -sequenceDiagram - participant M as Main - participant SE as SearchEngine - participant I as Index - participant R as Ranking - participant S as Summary - participant KB as KnowledgeBase - M->>SE: search(query) - SE->>I: query_index(query) - I->>KB: fetch_data(query) - KB-->>I: return data - I-->>SE: return results - SE->>R: rank_results(results) - R-->>SE: return ranked_results - SE->>S: summarize_results(ranked_results) - S-->>SE: return summary - SE-->>M: return summary - M->>SE: newMethod() # Incremental change - SE->>I: newMethod() # Incremental change - I->>KB: newMethod() # Incremental change - KB-->>I: newMethod() # Incremental change - SE->>R: newMethod() # Incremental change - R-->>SE: newMethod() # Incremental change - SE->>S: newMethod() # Incremental change - S-->>SE: newMethod() # Incremental change - SE-->>M: newMethod() # Incremental change -""" diff --git a/tests/data/incremental_dev_project/mock.py b/tests/data/incremental_dev_project/mock.py index a7aa2bbe4..bb3d008a1 100644 --- a/tests/data/incremental_dev_project/mock.py +++ b/tests/data/incremental_dev_project/mock.py @@ -372,8 +372,8 @@ REFINED_TASKS_JSON = { "Anything UNCLEAR": "", } -GUIDELINES_AND_INCREMENTAL_CHANGE_SAMPLE = { - "Guidelines and Incremental Change": '\n1. Guideline for gui.py: Develop the GUI using Tkinter to replace the command-line interface. Start by setting up the main window and event handling. Then, add widgets for displaying the game status, results, and feedback. Implement interactive elements for difficulty selection and visualize the guess history. Finally, create animations for guess feedback and ensure responsiveness across different screen sizes.\n```python\nclass GUI:\n- pass\n+ def __init__(self):\n+ self.setup_window()\n+\n+ def setup_window(self):\n+ # Initialize the main window using Tkinter\n+ pass\n+\n+ def bind_events(self):\n+ # Bind button clicks and other events\n+ pass\n+\n+ def update_feedback(self, message: str):\n+ # Update the feedback label with the given message\n+ pass\n+\n+ def update_attempts(self, attempts: int):\n+ # Update the attempts label with the number of attempts\n+ pass\n+\n+ def update_history(self, history: list):\n+ # Update the history view with the list of past guesses\n+ pass\n+\n+ def show_difficulty_selector(self):\n+ # Show buttons or a dropdown for difficulty selection\n+ pass\n+\n+ def animate_guess_result(self, correct: bool):\n+ # Trigger an animation for correct or incorrect guesses\n+ pass\n```\n\n2. Guideline for main.py: Modify the main.py to initialize the GUI and start the event-driven game loop. Ensure that the GUI is the primary interface for user interaction.\n```python\nclass Main:\n def main(self):\n- user_interface = UI()\n- user_interface.start()\n+ graphical_user_interface = GUI()\n+ graphical_user_interface.setup_window()\n+ graphical_user_interface.bind_events()\n+ # Start the Tkinter main loop\n+ pass\n\n if __name__ == "__main__":\n main_instance = Main()\n main_instance.main()\n```\n\n3. Guideline for ui.py: Refactor ui.py to work with the new GUI class. Remove command-line interactions and delegate display and input tasks to the GUI.\n```python\nclass UI:\n- def display_message(self, message: str):\n- print(message)\n+\n+ def display_message(self, message: str):\n+ # This method will now pass the message to the GUI to display\n+ pass\n\n- def get_user_input(self, prompt: str) -> str:\n- return input(prompt)\n+\n+ def get_user_input(self, prompt: str) -> str:\n+ # This method will now trigger the GUI to get user input\n+ pass\n\n- def show_attempts(self, attempts: int):\n- print(f"Number of attempts: {attempts}")\n+\n+ def show_attempts(self, attempts: int):\n+ # This method will now update the GUI with the number of attempts\n+ pass\n\n- def show_history(self, history: list):\n- print("Guess history:")\n- for guess in history:\n- print(guess)\n+\n+ def show_history(self, history: list):\n+ # This method will now update the GUI with the guess history\n+ pass\n```\n\n4. Guideline for game.py: Ensure game.py remains mostly unchanged as it contains the core game logic. However, make minor adjustments if necessary to integrate with the new GUI.\n```python\nclass Game:\n # No changes required for now\n```\n' +PLAN_SAMPLE = { + "Plan": '\n1. Plan for gui.py: Develop the GUI using Tkinter to replace the command-line interface. Start by setting up the main window and event handling. Then, add widgets for displaying the game status, results, and feedback. Implement interactive elements for difficulty selection and visualize the guess history. Finally, create animations for guess feedback and ensure responsiveness across different screen sizes.\n```python\nclass GUI:\n- pass\n+ def __init__(self):\n+ self.setup_window()\n+\n+ def setup_window(self):\n+ # Initialize the main window using Tkinter\n+ pass\n+\n+ def bind_events(self):\n+ # Bind button clicks and other events\n+ pass\n+\n+ def update_feedback(self, message: str):\n+ # Update the feedback label with the given message\n+ pass\n+\n+ def update_attempts(self, attempts: int):\n+ # Update the attempts label with the number of attempts\n+ pass\n+\n+ def update_history(self, history: list):\n+ # Update the history view with the list of past guesses\n+ pass\n+\n+ def show_difficulty_selector(self):\n+ # Show buttons or a dropdown for difficulty selection\n+ pass\n+\n+ def animate_guess_result(self, correct: bool):\n+ # Trigger an animation for correct or incorrect guesses\n+ pass\n```\n\n2. Plan for main.py: Modify the main.py to initialize the GUI and start the event-driven game loop. Ensure that the GUI is the primary interface for user interaction.\n```python\nclass Main:\n def main(self):\n- user_interface = UI()\n- user_interface.start()\n+ graphical_user_interface = GUI()\n+ graphical_user_interface.setup_window()\n+ graphical_user_interface.bind_events()\n+ # Start the Tkinter main loop\n+ pass\n\n if __name__ == "__main__":\n main_instance = Main()\n main_instance.main()\n```\n\n3. Plan for ui.py: Refactor ui.py to work with the new GUI class. Remove command-line interactions and delegate display and input tasks to the GUI.\n```python\nclass UI:\n- def display_message(self, message: str):\n- print(message)\n+\n+ def display_message(self, message: str):\n+ # This method will now pass the message to the GUI to display\n+ pass\n\n- def get_user_input(self, prompt: str) -> str:\n- return input(prompt)\n+\n+ def get_user_input(self, prompt: str) -> str:\n+ # This method will now trigger the GUI to get user input\n+ pass\n\n- def show_attempts(self, attempts: int):\n- print(f"Number of attempts: {attempts}")\n+\n+ def show_attempts(self, attempts: int):\n+ # This method will now update the GUI with the number of attempts\n+ pass\n\n- def show_history(self, history: list):\n- print("Guess history:")\n- for guess in history:\n- print(guess)\n+\n+ def show_history(self, history: list):\n+ # This method will now update the GUI with the guess history\n+ pass\n```\n\n4. Plan for game.py: Ensure game.py remains mostly unchanged as it contains the core game logic. However, make minor adjustments if necessary to integrate with the new GUI.\n```python\nclass Game:\n # No changes required for now\n```\n' } REFINED_CODE_INPUT_SAMPLE = """ diff --git a/tests/metagpt/actions/test_write_code_guideline_an.py b/tests/metagpt/actions/test_write_code_plan_an.py similarity index 61% rename from tests/metagpt/actions/test_write_code_guideline_an.py rename to tests/metagpt/actions/test_write_code_plan_an.py index 5a4e19d57..0babab7c5 100644 --- a/tests/metagpt/actions/test_write_code_guideline_an.py +++ b/tests/metagpt/actions/test_write_code_plan_an.py @@ -3,23 +3,23 @@ """ @Time : 2024/01/03 @Author : mannaandpoem -@File : test_write_code_guideline_an.py +@File : test_write_code_plan_an.py """ import pytest from openai._models import BaseModel from metagpt.actions.action_node import ActionNode from metagpt.actions.write_code import WriteCode -from metagpt.actions.write_code_guideline_an import ( - CODE_GUIDELINE_CONTEXT, +from metagpt.actions.write_code_plan_an import ( + CODE_PLAN_CONTEXT, REFINED_CODE_TEMPLATE, - WriteCodeGuideline, + WriteCodePlan, ) from tests.data.incremental_dev_project.mock import ( DESIGN_SAMPLE, - GUIDELINES_AND_INCREMENTAL_CHANGE_SAMPLE, NEW_REQUIREMENT_SAMPLE, OLD_CODE_SAMPLE, + PLAN_SAMPLE, REFINED_CODE_INPUT_SAMPLE, REFINED_CODE_SAMPLE, REFINED_DESIGN_JSON, @@ -29,30 +29,30 @@ from tests.data.incremental_dev_project.mock import ( ) -def mock_guidelines_and_incremental_change(): - return GUIDELINES_AND_INCREMENTAL_CHANGE_SAMPLE +def mock_plan(): + return PLAN_SAMPLE @pytest.mark.asyncio -async def test_write_code_guideline_an(mocker): +async def test_write_code_plan_an(mocker): root = ActionNode.from_children( - "WriteCodeGuideline", [ActionNode(key="", expected_type=str, instruction="", example="")] + "WriteCodePlan", [ActionNode(key="", expected_type=str, instruction="", example="")] ) root.instruct_content = BaseModel() - root.instruct_content.model_dump = mock_guidelines_and_incremental_change - mocker.patch("metagpt.actions.write_code_guideline_an.WriteCodeGuideline.run", return_value=root) + root.instruct_content.model_dump = mock_plan + mocker.patch("metagpt.actions.write_code_plan_an.WriteCodePlan.run", return_value=root) - write_code_guideline = WriteCodeGuideline() - context = CODE_GUIDELINE_CONTEXT.format( + write_code_plan = WriteCodePlan() + context = CODE_PLAN_CONTEXT.format( user_requirement=NEW_REQUIREMENT_SAMPLE, product_requirement_pools=REFINED_PRD_JSON.get("Refined Requirement Pool", ""), design=REFINED_DESIGN_JSON, tasks=REFINED_TASKS_JSON, code=OLD_CODE_SAMPLE, ) - node = await write_code_guideline.run(context=context) + node = await write_code_plan.run(context=context) - assert "Guidelines and Incremental Change" in node.instruct_content.model_dump() + assert "Plan" in node.instruct_content.model_dump() @pytest.mark.asyncio @@ -60,7 +60,7 @@ async def test_refine_code(mocker): mocker.patch("metagpt.actions.write_code.WriteCode.write_code", return_value=REFINED_CODE_SAMPLE) prompt = REFINED_CODE_TEMPLATE.format( user_requirement=NEW_REQUIREMENT_SAMPLE, - guideline=GUIDELINES_AND_INCREMENTAL_CHANGE_SAMPLE, + plan=PLAN_SAMPLE, design=DESIGN_SAMPLE, tasks=TASKS_SAMPLE, code=REFINED_CODE_INPUT_SAMPLE, diff --git a/tests/metagpt/test_incremental_dev.py b/tests/metagpt/test_incremental_dev.py index ed8fc7e78..41ba785c4 100644 --- a/tests/metagpt/test_incremental_dev.py +++ b/tests/metagpt/test_incremental_dev.py @@ -105,7 +105,7 @@ def log_and_check_result(result, tag_name="refine"): def get_incremental_dev_result(idea, project_name, use_review=True): project_path = TEST_DATA_PATH / "incremental_dev_project" / project_name - if not os.path.exists(project_path): + if project_path.exists(): raise Exception(f"Project {project_name} not exists") check_or_create_base_tag(project_path) args = [idea, "--inc", "--project-path", project_path]