From 1959743d0beed9bfeed7074ac346e6ba44efc2db Mon Sep 17 00:00:00 2001 From: mannaandpoem <1580466765@qq.com> Date: Fri, 19 Jan 2024 13:34:47 +0800 Subject: [PATCH] update write_code_plan_and_change_an.py and add it to _think and _act process --- .../actions/write_code_plan_and_change_an.py | 32 ++++- metagpt/const.py | 1 + metagpt/roles/engineer.py | 111 ++++++++++-------- metagpt/schema.py | 8 ++ .../actions/test_write_code_plan_an.py | 5 +- 5 files changed, 101 insertions(+), 56 deletions(-) diff --git a/metagpt/actions/write_code_plan_and_change_an.py b/metagpt/actions/write_code_plan_and_change_an.py index 1722855d2..151b2dc25 100644 --- a/metagpt/actions/write_code_plan_and_change_an.py +++ b/metagpt/actions/write_code_plan_and_change_an.py @@ -5,9 +5,14 @@ @Author : mannaandpoem @File : write_code_plan_and_change_an.py """ +import os + +from pydantic import Field from metagpt.actions.action import Action from metagpt.actions.action_node import ActionNode +from metagpt.config import CONFIG +from metagpt.schema import CodePlanAndChangeContext CODE_PLAN_AND_CHANGE = ActionNode( key="Code Plan And Change", @@ -106,10 +111,10 @@ def add_numbers(): CODE_PLAN_AND_CHANGE_CONTEXT = """ ## User New Requirements -{user_requirement} +{requirement} -## Product Requirement Pool -{product_requirement_pools} +## PRD +{prd} ## Design {design} @@ -179,7 +184,26 @@ WRITE_CODE_PLAN_AND_CHANGE_NODE = ActionNode.from_children("WriteCodePlanAndChan class WriteCodePlanAndChange(Action): - async def run(self, context): + name: str = "WriteCodePlanAndChange" + context: CodePlanAndChangeContext = Field(default_factory=CodePlanAndChangeContext) + + async def run(self, *args, **kwargs): self.llm.system_prompt = "You are a professional software engineer, your primary responsibility is to " "meticulously craft comprehensive incremental development plan and deliver detailed incremental change" + requirement = self.context.requirement_doc.content + prd = "\n".join([doc.content for doc in self.context.prd_docs]) + design = "\n".join([doc.content for doc in self.context.design_docs]) + tasks = "\n".join([doc.content for doc in self.context.task_docs]) + code_text = self.get_old_codes() + context = CODE_PLAN_AND_CHANGE_CONTEXT.format( + requirement=requirement, prd=prd, design=design, tasks=tasks, code=code_text + ) return await WRITE_CODE_PLAN_AND_CHANGE_NODE.fill(context=context, llm=self.llm, schema="json") + + @staticmethod + async def get_old_codes() -> str: + CONFIG.old_workspace = CONFIG.git_repo.workdir / os.path.basename(CONFIG.project_path) + old_file_repo = CONFIG.git_repo.new_file_repository(relative_path=CONFIG.old_workspace) + old_codes = await old_file_repo.get_all() + codes = [f"----- {code.filename}\n```{code.content}```" for code in old_codes] + return "\n".join(codes) diff --git a/metagpt/const.py b/metagpt/const.py index fc3d54296..7011f9c6f 100644 --- a/metagpt/const.py +++ b/metagpt/const.py @@ -100,6 +100,7 @@ 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_PLAN_AND_CHANGE_PDF_FILE_REPO = "resources/code_plan_and_change" 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 861c5435e..ac6e3b720 100644 --- a/metagpt/roles/engineer.py +++ b/metagpt/roles/engineer.py @@ -29,24 +29,24 @@ 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_plan_and_change_an import ( - CODE_PLAN_AND_CHANGE_CONTEXT, - WriteCodePlanAndChange, -) -from metagpt.actions.write_prd_an import REFINED_REQUIREMENT_POOL, REQUIREMENT_POOL +from metagpt.actions.write_code_plan_and_change_an import WriteCodePlanAndChange from metagpt.config import CONFIG from metagpt.const import ( CODE_PLAN_AND_CHANGE_FILE_REPO, CODE_PLAN_AND_CHANGE_FILENAME, + CODE_PLAN_AND_CHANGE_PDF_FILE_REPO, CODE_SUMMARIES_FILE_REPO, CODE_SUMMARIES_PDF_FILE_REPO, + DOCS_FILE_REPO, PRDS_FILE_REPO, + REQUIREMENT_FILENAME, SYSTEM_DESIGN_FILE_REPO, TASK_FILE_REPO, ) from metagpt.logs import logger from metagpt.roles import Role from metagpt.schema import ( + CodePlanAndChangeContext, CodeSummarizeContext, CodingContext, Document, @@ -149,6 +149,9 @@ class Engineer(Role): """Determines the mode of action based on whether code review is used.""" if self.rc.todo is None: return None + if isinstance(self.rc.todo, WriteCodePlanAndChange): + self.next_todo_action = any_to_name(WriteCode) + return await self._act_code_plan_and_change() if isinstance(self.rc.todo, WriteCode): self.next_todo_action = any_to_name(SummarizeCode) return await self._act_write_code() @@ -212,6 +215,40 @@ class Engineer(Role): content=json.dumps(tasks), role=self.profile, cause_by=SummarizeCode, send_to=self, sent_from=self ) + async def _act_code_plan_and_change(self): + """Write code plan and change that guides subsequent WriteCode and WriteCodeReview""" + logger.info("Writing code plan and change..") + code_plan_and_change_file_repo = CONFIG.git_repo.new_file_repository(CODE_PLAN_AND_CHANGE_FILE_REPO) + code_plan_and_change_pdf_file_repo = CONFIG.git_repo.new_file_repository(CODE_PLAN_AND_CHANGE_PDF_FILE_REPO) + + node = await self.rc.todo.run() + code_plan_and_change = node.instruct_content.model_dump_json() + + dependencies = { + self.rc.todo.context.requirement_filename, + self.rc.todo.context.prd_filename, + self.rc.todo.context.design_filename, + self.rc.todo.context.task_filename, + } + + code_plan_and_change_filename = os.path.join(CODE_PLAN_AND_CHANGE_FILE_REPO, CODE_PLAN_AND_CHANGE_FILENAME) + await code_plan_and_change_file_repo.save( + filename=code_plan_and_change_filename, content=code_plan_and_change, dependencies=dependencies + ) + await code_plan_and_change_pdf_file_repo.save( + filename=Path(code_plan_and_change_filename).with_suffix(".md").name, + content=node.content, + dependencies=dependencies, + ) + + return Message( + content=code_plan_and_change, + role=self.profile, + cause_by=WriteCodePlanAndChange, + send_to=self, + sent_from=self, + ) + async def _is_pass(self, summary) -> (str, str): rsp = await self.llm.aask(msg=IS_PASS_PROMPT.format(context=summary), stream=False) logger.info(rsp) @@ -222,11 +259,16 @@ class Engineer(Role): async def _think(self) -> Action | None: if not CONFIG.src_workspace: CONFIG.src_workspace = CONFIG.git_repo.workdir / CONFIG.git_repo.workdir.name - write_code_filters = any_to_str_set([WriteTasks, SummarizeCode, FixBug]) + write_plan_and_change_filters = any_to_str_set([WriteTasks]) + write_code_filters = any_to_str_set([WriteTasks, WriteCodePlanAndChange, SummarizeCode, FixBug]) summarize_code_filters = any_to_str_set([WriteCode, WriteCodeReview]) if not self.rc.news: return None msg = self.rc.news[0] + if CONFIG.inc and msg.cause_by in write_plan_and_change_filters: + logger.debug(f"TODO WriteCodePlanAndChange:{msg.model_dump_json()}") + await self._new_code_plan_and_change_action() + return self.rc.todo if msg.cause_by in write_code_filters: logger.debug(f"TODO WriteCode:{msg.model_dump_json()}") await self._new_code_actions(bug_fix=msg.cause_by == any_to_str(FixBug)) @@ -332,50 +374,21 @@ class Engineer(Role): if self.summarize_todos: self.rc.todo = self.summarize_todos[0] + async def _new_code_plan_and_change_action(self): + """Create a WriteCodePlanAndChange action for subsequent to-do actions.""" + requirement_doc = await FileRepository.get_file(filename=REQUIREMENT_FILENAME, relative_path=DOCS_FILE_REPO) + prd_docs = await FileRepository.get_all_files(relative_path=PRDS_FILE_REPO) + design_docs = await FileRepository.get_all_files(relative_path=SYSTEM_DESIGN_FILE_REPO) + tasks_docs = await FileRepository.get_all_files(relative_path=TASK_FILE_REPO) + code_plan_and_change_context = CodePlanAndChangeContext( + requirement_doc=requirement_doc, + prd_docs=prd_docs, + design_docs=design_docs, + tasks_docs=tasks_docs, + ) + self.rc.todo = WriteCodePlanAndChange(context=code_plan_and_change_context, llm=self.llm) + @property def todo(self) -> str: """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_plan_and_change(self): - """Write code plan and change that guides subsequent WriteCode and WriteCodeReview""" - logger.info("Writing code plan and change..") - - user_requirement = str(self.rc.memory.get_by_role("Human")[0]) - pool_contents = [] - prd = await FileRepository.get_all_files(relative_path=PRDS_FILE_REPO) - for doc in prd: - prd_json = json.loads(doc.content) - 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(pool_contents) - - design = await FileRepository.get_all_files(relative_path=SYSTEM_DESIGN_FILE_REPO) - design = "\n".join([doc.content for doc in design]) - - 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_PLAN_AND_CHANGE_CONTEXT.format( - user_requirement=user_requirement, - product_requirement_pools=product_requirement_pools, - tasks=tasks, - design=design, - code=old_codes, - ) - node = await WriteCodePlanAndChange().run(context=context) - code_plan_and_change = node.instruct_content.model_dump_json() - CONFIG.git_repo.new_file_repository(CODE_PLAN_AND_CHANGE_FILE_REPO).save( - filename=CODE_PLAN_AND_CHANGE_FILENAME, content=code_plan_and_change - ) - - @staticmethod - async def get_old_codes() -> str: - CONFIG.old_workspace = CONFIG.git_repo.workdir / os.path.basename(CONFIG.project_path) - old_file_repo = CONFIG.git_repo.new_file_repository(relative_path=CONFIG.old_workspace) - old_codes = await old_file_repo.get_all() - codes = [f"----- {code.filename}\n```{code.content}```" for code in old_codes] - return "\n".join(codes) diff --git a/metagpt/schema.py b/metagpt/schema.py index e36bef395..5daaffdeb 100644 --- a/metagpt/schema.py +++ b/metagpt/schema.py @@ -451,3 +451,11 @@ class CodeSummarizeContext(BaseModel): class BugFixContext(BaseContext): filename: str = "" + + +class CodePlanAndChangeContext(BaseContext): + filename: str = "" + requirement_doc: Document + prd_docs: List[Document] + design_docs: List[Document] + task_docs: List[Document] diff --git a/tests/metagpt/actions/test_write_code_plan_an.py b/tests/metagpt/actions/test_write_code_plan_an.py index 13bda7cc1..44679e561 100644 --- a/tests/metagpt/actions/test_write_code_plan_an.py +++ b/tests/metagpt/actions/test_write_code_plan_an.py @@ -15,7 +15,6 @@ from metagpt.actions.write_code_plan_and_change_an import ( REFINED_TEMPLATE, WriteCodePlanAndChange, ) -from metagpt.actions.write_prd_an import REQUIREMENT_POOL from tests.data.incremental_dev_project.mock import ( CODE_PLAN_AND_CHANGE_SAMPLE, DESIGN_SAMPLE, @@ -45,8 +44,8 @@ async def test_write_code_plan_an(mocker): write_code_plan = WriteCodePlanAndChange() context = CODE_PLAN_AND_CHANGE_CONTEXT.format( - user_requirement=NEW_REQUIREMENT_SAMPLE, - product_requirement_pools=REFINED_PRD_JSON.get(REQUIREMENT_POOL.key), + requirement=NEW_REQUIREMENT_SAMPLE, + PRD=REFINED_PRD_JSON, design=REFINED_DESIGN_JSON, tasks=REFINED_TASKS_JSON, code=OLD_CODE_SAMPLE,