diff --git a/metagpt/actions/action.py b/metagpt/actions/action.py index 1d9be60e0..6c1f63f45 100644 --- a/metagpt/actions/action.py +++ b/metagpt/actions/action.py @@ -44,6 +44,7 @@ class Action(ABC): self.prefix = prefix self.profile = profile self.llm.system_prompt = prefix + return self def __str__(self): return self.__class__.__name__ diff --git a/metagpt/actions/action_node.py b/metagpt/actions/action_node.py index 9fb10f35c..1d808ec70 100644 --- a/metagpt/actions/action_node.py +++ b/metagpt/actions/action_node.py @@ -243,7 +243,7 @@ class ActionNode: ) return prompt - @retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6)) + @retry(wait=wait_random_exponential(min=1, max=10), stop=stop_after_attempt(6)) async def _aask_v1( self, prompt: str, diff --git a/metagpt/actions/project_management.py b/metagpt/actions/project_management.py index c95be4012..1f14e7944 100644 --- a/metagpt/actions/project_management.py +++ b/metagpt/actions/project_management.py @@ -99,7 +99,7 @@ class WriteTasks(Action): async def _merge(self, system_design_doc, task_doc, format=CONFIG.prompt_format) -> Document: context = NEW_REQ_TEMPLATE.format(context=system_design_doc.content, old_tasks=task_doc.content) node = await PM_NODE.fill(context, self.llm, format) - task_doc.content = node.content + task_doc.content = node.instruct_content.json(ensure_ascii=False) return task_doc @staticmethod diff --git a/metagpt/actions/write_code.py b/metagpt/actions/write_code.py index b759f4e2a..5960e2621 100644 --- a/metagpt/actions/write_code.py +++ b/metagpt/actions/write_code.py @@ -34,59 +34,52 @@ from metagpt.utils.file_repository import FileRepository PROMPT_TEMPLATE = """ NOTICE -Role: You are a professional engineer; the main goal is to write PEP8 compliant, elegant, modular, easy to read and maintain Python 3.9 code (but you can also use other programming language) +Role: You are a professional engineer; the main goal is to write google-style, elegant, modular, easy to read and maintain code Language: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese. ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced "Format example". ------ -# Design -```json +# Context +## Design {design} -``` ------ -# Tasks -```json + +## Tasks {tasks} -``` ------ -# Legacy Code -```python + +## Legacy Code +```Code {code} ``` ------ -# Debug logs + +## Debug logs ```text {logs} {summary_log} ``` ------ -# Bug Feedback logs + +## Bug Feedback logs ```text {feedback} ``` ------ - -## Code: {filename} Write code with triple quoto, based on the following list and context. -1. Do your best to implement THIS ONLY ONE FILE. ONLY USE EXISTING API. IF NO API, IMPLEMENT IT. -2. Requirement: Based on the context, implement one following code file, note to return only in code form, 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. -4. Follow design: YOU MUST FOLLOW "Data structures and interfaces". DONT CHANGE ANY DESIGN. -5. Think before writing: What should be implemented and provided in this document? -6. CAREFULLY CHECK THAT YOU DONT MISS ANY NECESSARY CLASS/FUNCTION IN THIS FILE. -7. Do not use public member functions that do not exist in your design. -8. Before using a variable, make sure you reference it first -9. Write out EVERY DETAIL, DON'T LEAVE TODO. - -## Format example ------ +# Format example ## Code: {filename} ```python ## {filename} ... ``` ------ + +# Instruction: Based on the context, follow "Format example", write code. + +## Code: {filename}. Write code with triple quoto, based on the following attentions and context. +1. Only One file: do your best to implement THIS ONLY ONE FILE. +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. CAREFULLY CHECK THAT YOU DONT MISS ANY NECESSARY CLASS/FUNCTION IN THIS FILE. +6. Before using a external variable/module, make sure you import it first. +7. Write out EVERY CODE DETAIL, DON'T LEAVE TODO. + """ @@ -107,7 +100,7 @@ class WriteCode(Action): filename="test_" + coding_context.filename + ".json", relative_path=TEST_OUTPUTS_FILE_REPO ) summary_doc = None - if coding_context.design_doc.filename: + if coding_context.design_doc and coding_context.design_doc.filename: summary_doc = await FileRepository.get_file( filename=coding_context.design_doc.filename, relative_path=CODE_SUMMARIES_FILE_REPO ) @@ -115,9 +108,14 @@ class WriteCode(Action): if test_doc: test_detail = RunCodeResult.loads(test_doc.content) logs = test_detail.stderr - code_context = await self.get_codes(coding_context.task_doc, exclude=self.context.filename) + + if bug_feedback: + code_context = coding_context.code_doc.content + else: + code_context = await self.get_codes(coding_context.task_doc, exclude=self.context.filename) + prompt = PROMPT_TEMPLATE.format( - design=coding_context.design_doc.content, + 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, logs=logs, @@ -148,5 +146,5 @@ class WriteCode(Action): doc = await src_file_repo.get(filename=filename) if not doc: continue - codes.append(doc.content) - return "\n----------\n".join(codes) + codes.append(f"----- {filename}\n" + doc.content) + return "\n".join(codes) diff --git a/metagpt/actions/write_code_review.py b/metagpt/actions/write_code_review.py index 75313fea5..62e96acd8 100644 --- a/metagpt/actions/write_code_review.py +++ b/metagpt/actions/write_code_review.py @@ -18,8 +18,8 @@ from metagpt.schema import CodingContext from metagpt.utils.common import CodeParser PROMPT_TEMPLATE = """ -NOTICE -Role: You are a professional software engineer, and your main task is to review the code. You need to ensure that the code conforms to the PEP8 standards, is elegantly designed and modularized, easy to read and maintain, and is written in Python 3.9 (or in another programming language). +# System +Role: You are a professional software engineer, and your main task is to review and revise the code. You need to ensure that the code conforms to the google-style standards, is elegantly designed and modularized, easy to read and maintain. Language: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese. ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced "Format example". @@ -27,53 +27,52 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenc {context} ## Code to be Reviewed: {filename} -``` +```Code {code} ``` +""" ------ -## Code Review: Based on the "Code to be Reviewed", provide key, clear, concise, and specific code modification suggestions, up to 5. +EXAMPLE_AND_INSTRUCTION = """ + +{format_example} + + +# Instruction: Based on the actual code situation, follow one of the "Format example". + +## Code Review: Ordered List. Based on the "Code to be Reviewed", provide key, clear, concise, and specific answer. If any answer is no, explain how to fix it step by step. 1. Is the code implemented as per the requirements? If not, how to achieve it? Analyse it step by step. 2. Is the code logic completely correct? If there are errors, please indicate how to correct them. 3. Does the existing code follow the "Data structures and interfaces"? 4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step. 5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported -6. Is the code implemented concisely enough? Are methods from other files being reused correctly? +6. Are methods from other files being reused correctly? -## Code Review Result: If the code doesn't have bugs, we don't need to rewrite it, so answer LGTM and stop. ONLY ANSWER LGTM/LBTM. +## Actions: Ordered List. Things that should be done after CR, such as implementing class A and function B + +## Code Review Result: str. If the code doesn't have bugs, we don't need to rewrite it, so answer LGTM and stop. ONLY ANSWER LGTM/LBTM. LGTM/LBTM -## Rewrite Code: if it still has some bugs, rewrite {filename} based on "Code Review" with triple quotes, try to get LGTM. Do your utmost to optimize THIS SINGLE FILE. Implement ALL TODO. RETURN ALL CODE, NEVER OMIT ANYTHING. 以任何方式省略代码都是不允许的。 -``` -``` - -## Format example -{format_example} - """ FORMAT_EXAMPLE = """ ------ -# EXAMPLE 1 +# Format example 1 ## Code Review: {filename} -1. No, we should add the logic of ... +1. No, we should fix the logic of class A due to ... 2. ... 3. ... -4. ... +4. No, function B is not implemented, ... 5. ... 6. ... -## Code Review Result: {filename} +## Actions +1. fix class A +2. implement function B + +## Code Review Result LBTM -## Rewrite Code: {filename} -```python -## {filename} -... -``` ------ -# EXAMPLE 2 +# Format example 2 ## Code Review: {filename} 1. Yes. 2. Yes. @@ -82,12 +81,20 @@ LBTM 5. Yes. 6. Yes. -## Code Review Result: {filename} -LGTM - -## Rewrite Code: {filename} +## Actions pass ------ + +## Code Review Result +LGTM +""" + +REWRITE_CODE_TEMPLATE = """ +# Instruction: rewrite code based on the Code Review and Actions +## Rewrite Code: CodeBlock. If it still has some bugs, rewrite {filename} with triple quotes. Do your utmost to optimize THIS SINGLE FILE. Return all completed codes and prohibit the return of unfinished codes. +```Code +## {filename} +... +``` """ @@ -96,11 +103,15 @@ class WriteCodeReview(Action): super().__init__(name, context, llm) @retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6)) - async def write_code_review_and_rewrite(self, prompt): - code_rsp = await self._aask(prompt) - result = CodeParser.parse_block("Code Review Result", code_rsp) + async def write_code_review_and_rewrite(self, context_prompt, cr_prompt, filename): + cr_rsp = await self._aask(context_prompt + cr_prompt) + result = CodeParser.parse_block("Code Review Result", cr_rsp) if "LGTM" in result: return result, None + + # if LBTM, rewrite code + rewrite_prompt = f"{context_prompt}\n{cr_rsp}\n{REWRITE_CODE_TEMPLATE.format(filename=filename)}" + code_rsp = await self._aask(rewrite_prompt) code = CodeParser.parse_code(block="", text=code_rsp) return result, code @@ -111,23 +122,23 @@ class WriteCodeReview(Action): 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) - context = "\n----------\n".join( + context = "\n".join( [ - "```text\n" + self.context.design_doc.content + "```\n", - "```text\n" + task_content + "```\n", - "```python\n" + code_context + "```\n", + "## System Design\n" + str(self.context.design_doc) + "\n", + "## Tasks\n" + task_content + "\n", + "## Code Files\n" + code_context + "\n", ] ) - prompt = PROMPT_TEMPLATE.format( + context_prompt = PROMPT_TEMPLATE.format( context=context, code=iterative_code, filename=self.context.code_doc.filename, - format_example=format_example, ) + cr_prompt = EXAMPLE_AND_INSTRUCTION.format(format_example=format_example, ) logger.info( - f"Code review and rewrite {self.context.code_doc.filename,}: {i+1}/{k} | {len(iterative_code)=}, {len(self.context.code_doc.content)=}" + f"Code review and rewrite {self.context.code_doc.filename}: {i+1}/{k} | {len(iterative_code)=}, {len(self.context.code_doc.content)=}" ) - result, rewrited_code = await self.write_code_review_and_rewrite(prompt) + result, rewrited_code = await self.write_code_review_and_rewrite(context_prompt, cr_prompt, self.context.code_doc.filename) if "LBTM" in result: iterative_code = rewrited_code elif "LGTM" in result: diff --git a/metagpt/actions/write_prd_an.py b/metagpt/actions/write_prd_an.py index 68402e504..d96c0aeac 100644 --- a/metagpt/actions/write_prd_an.py +++ b/metagpt/actions/write_prd_an.py @@ -93,7 +93,7 @@ REQUIREMENT_POOL = ActionNode( key="Requirement Pool", expected_type=list[list[str]], instruction="List down the requirements with their priority (P0, P1, P2).", - example=[["P0", "High priority requirement"], ["P1", "Medium priority requirement"]], + example=[["P0", "..."], ["P1", "..."]], ) UI_DESIGN_DRAFT = ActionNode( diff --git a/metagpt/provider/base_gpt_api.py b/metagpt/provider/base_gpt_api.py index 6c1dc8338..c38576806 100644 --- a/metagpt/provider/base_gpt_api.py +++ b/metagpt/provider/base_gpt_api.py @@ -49,8 +49,8 @@ class BaseGPTAPI(BaseChatbot): message = ( [self._default_system_msg(), self._user_msg(msg)] if self.use_system_prompt else [self._user_msg(msg)] ) - rsp = await self.acompletion_text(message, stream=stream) logger.debug(message) + rsp = await self.acompletion_text(message, stream=stream) # logger.debug(rsp) return rsp diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py index 2f99d132e..f1e65b177 100644 --- a/metagpt/roles/engineer.py +++ b/metagpt/roles/engineer.py @@ -72,7 +72,7 @@ class Engineer(Role): name: str = "Alex", profile: str = "Engineer", goal: str = "write elegant, readable, extensible, efficient code", - constraints: str = "the code should conform to standards like PEP8 and be modular and maintainable. " + constraints: str = "the code should conform to standards like google-style and be modular and maintainable. " "Use same language as user requirement", n_borg: int = 1, use_code_review: bool = False, @@ -105,7 +105,9 @@ class Engineer(Role): coding_context = await todo.run() # Code review if review: - coding_context = await WriteCodeReview(context=coding_context, llm=self._llm).run() + action = WriteCodeReview(context=coding_context, llm=self._llm) + self._init_action_system_message(action) + coding_context = await action.run() await src_file_repo.save( coding_context.filename, dependencies={coding_context.design_doc.root_relative_path, coding_context.task_doc.root_relative_path}, @@ -224,6 +226,7 @@ class Engineer(Role): task_doc = await task_file_repo.get(i.name) elif str(i.parent) == SYSTEM_DESIGN_FILE_REPO: design_doc = await design_file_repo.get(i.name) + # FIXME: design doc没有加载进来,是None context = CodingContext(filename=filename, design_doc=design_doc, task_doc=task_doc, code_doc=old_code_doc) return context diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index 633ad6051..b673c330d 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -134,6 +134,7 @@ class Role: self._setting = RoleSetting( name=name, profile=profile, goal=goal, constraints=constraints, desc=desc, is_human=is_human ) + self._llm.system_prompt = self._get_prefix() self._states = [] self._actions = [] self._role_id = str(self._setting) @@ -144,6 +145,9 @@ class Role: self._states = [] self._actions = [] + def _init_action_system_message(self, action: Action): + action.set_prefix(self._get_prefix(), self.profile) + def _init_actions(self, actions): self._reset() for idx, action in enumerate(actions): @@ -158,7 +162,7 @@ class Role: ) i = action # i.set_env(self._rc.env) - i.set_prefix(self._get_prefix(), self.profile) + self._init_action_system_message(i) self._actions.append(i) self._states.append(f"{idx}. {action}") @@ -408,7 +412,7 @@ class Role: logger.debug(f"{self._setting}: no news. waiting.") return - rsp = await self._react() + rsp = await self.react() # Reset the next action to be taken. self._rc.todo = None diff --git a/metagpt/schema.py b/metagpt/schema.py index baed5582b..758149efa 100644 --- a/metagpt/schema.py +++ b/metagpt/schema.py @@ -74,6 +74,12 @@ class Document(BaseModel): return None return str(CONFIG.git_repo.workdir / self.root_path / self.filename) + def __str__(self): + return self.content + + def __repr__(self): + return self.content + class Documents(BaseModel): """A class representing a collection of documents. @@ -259,7 +265,7 @@ class MessageQueue: class CodingContext(BaseModel): filename: str - design_doc: Document + design_doc: Optional[Document] task_doc: Optional[Document] code_doc: Optional[Document] diff --git a/metagpt/utils/common.py b/metagpt/utils/common.py index f08519f8e..a9bdd6e2d 100644 --- a/metagpt/utils/common.py +++ b/metagpt/utils/common.py @@ -223,10 +223,15 @@ class CodeParser: # 遍历所有的block for block in blocks: # 如果block不为空,则继续处理 - if block.strip() != "": + if block.strip() == "": + continue + if "\n" not in block: + block_title = block + block_content = "" + else: # 将block的标题和内容分开,并分别去掉前后的空白字符 block_title, block_content = block.split("\n", 1) - block_dict[block_title.strip()] = block_content.strip() + block_dict[block_title.strip()] = block_content.strip() return block_dict diff --git a/metagpt/utils/git_repository.py b/metagpt/utils/git_repository.py index 1340b1768..d2bdf5d85 100644 --- a/metagpt/utils/git_repository.py +++ b/metagpt/utils/git_repository.py @@ -233,6 +233,8 @@ class GitRepository: files = [] try: directory_path = Path(self.workdir) / relative_path + if not directory_path.exists(): + return [] for file_path in directory_path.iterdir(): if file_path.is_file(): rpath = file_path.relative_to(root_relative_path)