From 7571753a53f324a0b4a5313827e15fe956de273b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Thu, 9 May 2024 14:52:54 +0800 Subject: [PATCH 1/9] =?UTF-8?q?refactor:=20=E6=B8=85=E7=A9=BA=E4=BA=86?= =?UTF-8?q?=E8=BD=AF=E4=BB=B6=E5=85=AC=E5=8F=B8message=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E6=97=A0=E7=94=A8=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metagpt/actions/design_api.py | 3 +-- metagpt/actions/project_management.py | 2 -- metagpt/actions/write_prd.py | 16 +++++++--------- metagpt/roles/engineer.py | 14 ++++---------- metagpt/roles/qa_engineer.py | 2 +- metagpt/roles/role.py | 2 +- metagpt/schema.py | 4 ---- 7 files changed, 14 insertions(+), 29 deletions(-) diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index 83139035a..25b9d3720 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -13,7 +13,7 @@ import json from pathlib import Path from typing import Optional -from metagpt.actions import Action, ActionOutput +from metagpt.actions import Action from metagpt.actions.design_api_an import ( DATA_STRUCTURES_AND_INTERFACES, DESIGN_API_NODE, @@ -68,7 +68,6 @@ class WriteDesign(Action): logger.info("Nothing has changed.") # Wait until all files under `docs/system_designs/` are processed before sending the publish message, # leaving room for global optimization in subsequent steps. - return ActionOutput(content=changed_files.model_dump_json(), instruct_content=changed_files) async def _new_system_design(self, context): node = await DESIGN_API_NODE.fill(context=context, llm=self.llm, schema=self.prompt_schema) diff --git a/metagpt/actions/project_management.py b/metagpt/actions/project_management.py index 5aace9fa9..9fb4b6119 100644 --- a/metagpt/actions/project_management.py +++ b/metagpt/actions/project_management.py @@ -14,7 +14,6 @@ import json from typing import Optional from metagpt.actions.action import Action -from metagpt.actions.action_output import ActionOutput from metagpt.actions.project_management_an import PM_NODE, REFINED_PM_NODE from metagpt.const import PACKAGE_REQUIREMENTS_FILENAME from metagpt.logs import logger @@ -55,7 +54,6 @@ class WriteTasks(Action): logger.info("Nothing has changed.") # Wait until all files under `docs/tasks/` are processed before sending the publish_message, leaving room for # global optimization in subsequent steps. - return ActionOutput(content=change_files.model_dump_json(), instruct_content=change_files) async def _update_tasks(self, filename): system_design_doc = await self.repo.docs.system_design.get(filename) diff --git a/metagpt/actions/write_prd.py b/metagpt/actions/write_prd.py index 02bd1d6d5..79284530c 100644 --- a/metagpt/actions/write_prd.py +++ b/metagpt/actions/write_prd.py @@ -15,6 +15,7 @@ from __future__ import annotations import json from pathlib import Path +from typing import Optional, Union from metagpt.actions import Action, ActionOutput from metagpt.actions.action_node import ActionNode @@ -33,7 +34,7 @@ from metagpt.const import ( REQUIREMENT_FILENAME, ) from metagpt.logs import logger -from metagpt.schema import BugFixContext, Document, Documents, Message +from metagpt.schema import AIMessage, Document, Documents, Message from metagpt.utils.common import CodeParser from metagpt.utils.file_repository import FileRepository from metagpt.utils.mermaid import mermaid_to_file @@ -66,7 +67,7 @@ class WritePRD(Action): 3. Requirement update: If the requirement is an update, the PRD document will be updated. """ - async def run(self, with_messages, *args, **kwargs) -> ActionOutput | Message: + async def run(self, with_messages, *args, **kwargs) -> Optional[Union[ActionOutput, Message]]: """Run the action.""" req: Document = await self.repo.requirement docs: list[Document] = await self.repo.docs.prd.get_all() @@ -82,20 +83,17 @@ class WritePRD(Action): # if requirement is related to other documents, update them, otherwise create a new one if related_docs := await self.get_related_docs(req, docs): logger.info(f"Requirement update detected: {req.content}") - return await self._handle_requirement_update(req, related_docs) + await self._handle_requirement_update(req, related_docs) else: logger.info(f"New requirement detected: {req.content}") - return await self._handle_new_requirement(req) + await self._handle_new_requirement(req) async def _handle_bugfix(self, req: Document) -> Message: # ... bugfix logic ... await self.repo.docs.save(filename=BUGFIX_FILENAME, content=req.content) await self.repo.docs.save(filename=REQUIREMENT_FILENAME, content="") - bug_fix = BugFixContext(filename=BUGFIX_FILENAME) - return Message( - content=bug_fix.model_dump_json(), - instruct_content=bug_fix, - role="", + return AIMessage( + content="", cause_by=FixBug, sent_from=self, send_to="Alex", # the name of Engineer diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py index e7b5cf219..0328fb28b 100644 --- a/metagpt/roles/engineer.py +++ b/metagpt/roles/engineer.py @@ -150,12 +150,6 @@ class Engineer(Role): dependencies=list(dependencies), content=coding_context.code_doc.content, ) - AIMessage( - content=coding_context.model_dump_json(), - instruct_content=coding_context, - cause_by=WriteCode, - ) - changed_files.add(coding_context.code_doc.filename) if not changed_files: logger.info("Nothing has changed.") @@ -177,9 +171,9 @@ class Engineer(Role): return await self.rc.todo.run(self.rc.history) async def _act_write_code(self): - changed_files = await self._act_sp_with_cr(review=self.use_code_review) + await self._act_sp_with_cr(review=self.use_code_review) return AIMessage( - content="\n".join(changed_files), + content="", cause_by=WriteCodeReview if self.use_code_review else WriteCode, send_to=self, sent_from=self, @@ -221,7 +215,7 @@ class Engineer(Role): # The maximum number of times the 'SummarizeCode' action is automatically invoked, with -1 indicating unlimited. # This parameter is used for debugging the workflow. self.n_summarize += 1 if self.config.max_auto_summarize_code > self.n_summarize else 0 - return AIMessage(content=json.dumps(tasks), cause_by=SummarizeCode, send_to=self, sent_from=self) + return AIMessage(content="", 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""" @@ -244,7 +238,7 @@ class Engineer(Role): ) return AIMessage( - content=code_plan_and_change, + content="", cause_by=WriteCodePlanAndChange, send_to=self, sent_from=self, diff --git a/metagpt/roles/qa_engineer.py b/metagpt/roles/qa_engineer.py index c5f22174f..2947c942f 100644 --- a/metagpt/roles/qa_engineer.py +++ b/metagpt/roles/qa_engineer.py @@ -21,7 +21,6 @@ from metagpt.actions.summarize_code import SummarizeCode from metagpt.const import MESSAGE_ROUTE_TO_NONE from metagpt.logs import logger from metagpt.roles import Role -from metagpt.utils.report import EditorReporter from metagpt.schema import AIMessage, Document, Message, RunCodeContext, TestingContext from metagpt.utils.common import ( any_to_str, @@ -29,6 +28,7 @@ from metagpt.utils.common import ( init_python_folder, parse_recipient, ) +from metagpt.utils.report import EditorReporter class QaEngineer(Role): diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index b6f6b7d20..14aa7dbb5 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -408,7 +408,7 @@ class Role(SerializationMixin, ContextMixin, BaseModel): elif isinstance(response, Message): msg = response else: - msg = AIMessage(content=response, cause_by=self.rc.todo, sent_from=self) + msg = AIMessage(content=response or "", cause_by=self.rc.todo, sent_from=self) if self.enable_memory: self.rc.memory.add(msg) diff --git a/metagpt/schema.py b/metagpt/schema.py index 5fc1ae242..ba64c0f06 100644 --- a/metagpt/schema.py +++ b/metagpt/schema.py @@ -748,10 +748,6 @@ class CodeSummarizeContext(BaseModel): return hash((self.design_filename, self.task_filename)) -class BugFixContext(BaseContext): - filename: str = "" - - class CodePlanAndChangeContext(BaseModel): requirement: str = "" issue: str = "" From 9be140a42168f9e0c4a4ab471f5f2ffa553f7436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Thu, 9 May 2024 19:01:16 +0800 Subject: [PATCH 2/9] =?UTF-8?q?refactor:=20=E8=BD=AF=E4=BB=B6=E5=85=AC?= =?UTF-8?q?=E5=8F=B8message=E7=94=A8=E6=9D=A5=E4=B8=8Eteam=20leader?= =?UTF-8?q?=E9=80=9A=E8=AE=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metagpt/actions/design_api.py | 12 +++++++++++- metagpt/actions/project_management.py | 12 +++++++++++- metagpt/actions/write_prd.py | 15 ++++++++++++--- metagpt/roles/engineer.py | 12 +++++++++--- metagpt/roles/qa_engineer.py | 3 ++- 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index 25b9d3720..9b4a6c969 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -24,7 +24,7 @@ from metagpt.actions.design_api_an import ( ) from metagpt.const import DATA_API_DESIGN_FILE_REPO, SEQ_FLOW_FILE_REPO from metagpt.logs import logger -from metagpt.schema import Document, Documents, Message +from metagpt.schema import AIMessage, Document, Documents, Message from metagpt.utils.mermaid import mermaid_to_file from metagpt.utils.report import DocsReporter, GalleryReporter @@ -68,6 +68,16 @@ class WriteDesign(Action): logger.info("Nothing has changed.") # Wait until all files under `docs/system_designs/` are processed before sending the publish message, # leaving room for global optimization in subsequent steps. + return AIMessage( + content="Designing is complete. " + + "\n".join( + list(self.repo.docs.system_design.changed_files.keys()) + + list(self.repo.resources.data_api_design.changed_files.keys()) + + list(self.repo.resources.seq_flow.changed_files.keys()) + ), + cause_by=self, + sent_from=self, + ) async def _new_system_design(self, context): node = await DESIGN_API_NODE.fill(context=context, llm=self.llm, schema=self.prompt_schema) diff --git a/metagpt/actions/project_management.py b/metagpt/actions/project_management.py index 9fb4b6119..6e786c6a3 100644 --- a/metagpt/actions/project_management.py +++ b/metagpt/actions/project_management.py @@ -17,7 +17,7 @@ from metagpt.actions.action import Action from metagpt.actions.project_management_an import PM_NODE, REFINED_PM_NODE from metagpt.const import PACKAGE_REQUIREMENTS_FILENAME from metagpt.logs import logger -from metagpt.schema import Document, Documents +from metagpt.schema import AIMessage, Document, Documents from metagpt.utils.report import DocsReporter NEW_REQ_TEMPLATE = """ @@ -54,6 +54,16 @@ class WriteTasks(Action): logger.info("Nothing has changed.") # Wait until all files under `docs/tasks/` are processed before sending the publish_message, leaving room for # global optimization in subsequent steps. + return AIMessage( + content="WBS is completed. " + + "\n".join( + [PACKAGE_REQUIREMENTS_FILENAME] + + list(self.repo.docs.task.changed_files.keys()) + + list(self.repo.resources.api_spec_and_task.changed_files.keys()) + ), + cause_by=self, + sent_from=self, + ) async def _update_tasks(self, filename): system_design_doc = await self.repo.docs.system_design.get(filename) diff --git a/metagpt/actions/write_prd.py b/metagpt/actions/write_prd.py index 79284530c..b90e7bf5f 100644 --- a/metagpt/actions/write_prd.py +++ b/metagpt/actions/write_prd.py @@ -15,7 +15,6 @@ from __future__ import annotations import json from pathlib import Path -from typing import Optional, Union from metagpt.actions import Action, ActionOutput from metagpt.actions.action_node import ActionNode @@ -67,7 +66,7 @@ class WritePRD(Action): 3. Requirement update: If the requirement is an update, the PRD document will be updated. """ - async def run(self, with_messages, *args, **kwargs) -> Optional[Union[ActionOutput, Message]]: + async def run(self, with_messages, *args, **kwargs) -> Message: """Run the action.""" req: Document = await self.repo.requirement docs: list[Document] = await self.repo.docs.prd.get_all() @@ -87,13 +86,23 @@ class WritePRD(Action): else: logger.info(f"New requirement detected: {req.content}") await self._handle_new_requirement(req) + return AIMessage( + content="PRD is completed. " + + "\n".join( + list(self.repo.docs.prd.changed_files.keys()) + + list(self.repo.resources.prd.changed_files.keys()) + + list(self.repo.resources.competitive_analysis.changed_files.keys()) + ), + cause_by=self, + sent_from=self, + ) async def _handle_bugfix(self, req: Document) -> Message: # ... bugfix logic ... await self.repo.docs.save(filename=BUGFIX_FILENAME, content=req.content) await self.repo.docs.save(filename=REQUIREMENT_FILENAME, content="") return AIMessage( - content="", + content=f"A new issue is received: {BUGFIX_FILENAME}", cause_by=FixBug, sent_from=self, send_to="Alex", # the name of Engineer diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py index 0328fb28b..1e51e9db6 100644 --- a/metagpt/roles/engineer.py +++ b/metagpt/roles/engineer.py @@ -182,6 +182,8 @@ class Engineer(Role): async def _act_summarize(self): tasks = [] for todo in self.summarize_todos: + if self.n_summarize >= self.config.max_auto_summarize_code: + break summary = await todo.run() summary_filename = Path(todo.i_context.design_filename).with_suffix(".md").name dependencies = {todo.i_context.design_filename, todo.i_context.task_filename} @@ -203,11 +205,16 @@ class Engineer(Role): ) else: await self.project_repo.docs.code_summary.delete(filename=Path(todo.i_context.design_filename).name) - + self.summarize_todos = [] logger.info(f"--max-auto-summarize-code={self.config.max_auto_summarize_code}") if not tasks or self.config.max_auto_summarize_code == 0: + self.n_summarize = 0 return AIMessage( - content="", + content="Coding is complete. " + "\n".join( + list(self.project_repo.resources.code_summary.changed_files.keys()) + + list(self.project_repo.srcs.changed_files.keys()) + ), cause_by=SummarizeCode, sent_from=self, send_to="Edward", # The name of QaEngineer @@ -410,7 +417,6 @@ class Engineer(Role): self.summarize_todos.append(new_summarize) if self.summarize_todos: self.set_todo(self.summarize_todos[0]) - self.summarize_todos.pop(0) async def _new_code_plan_and_change_action(self, cause_by: str): """Create a WriteCodePlanAndChange action for subsequent to-do actions.""" diff --git a/metagpt/roles/qa_engineer.py b/metagpt/roles/qa_engineer.py index 2947c942f..99c9bb676 100644 --- a/metagpt/roles/qa_engineer.py +++ b/metagpt/roles/qa_engineer.py @@ -157,7 +157,8 @@ class QaEngineer(Role): await init_python_folder(self.project_repo.tests.workdir) if self.test_round > self.test_round_allowed: result_msg = AIMessage( - content=f"Exceeding {self.test_round_allowed} rounds of tests, skip (writing code counts as a round, too)", + content=f"Exceeding {self.test_round_allowed} rounds of tests, stop. " + + "\n".join(list(self.project_repo.tests.changed_files.keys())), cause_by=WriteTest, sent_from=self.profile, send_to=MESSAGE_ROUTE_TO_NONE, From 1e6e9d3103f759020f311fac6360ab01535a27b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Thu, 9 May 2024 20:28:33 +0800 Subject: [PATCH 3/9] feat: +auto append agent metadata --- metagpt/roles/role.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index 14aa7dbb5..7e3c85579 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -449,6 +449,8 @@ class Role(SerializationMixin, ContextMixin, BaseModel): if not self.rc.env: # If env does not exist, do not publish the message return + if isinstance(msg, AIMessage) and not msg.agent: + msg.with_agent(self._setting) self.rc.env.publish_message(msg) def put_message(self, message): From 9fa47aff0328bc81a75fb01443a3af461ed625d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 10 May 2024 11:53:21 +0800 Subject: [PATCH 4/9] fixbug: sent_from, send_to --- metagpt/actions/design_api.py | 1 - metagpt/actions/project_management.py | 1 - metagpt/actions/write_prd.py | 2 -- metagpt/const.py | 1 + metagpt/roles/engineer.py | 16 +++------------- metagpt/roles/role.py | 6 ++++++ metagpt/schema.py | 10 +++++++++- 7 files changed, 19 insertions(+), 18 deletions(-) diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index 9b4a6c969..613c4a47b 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -76,7 +76,6 @@ class WriteDesign(Action): + list(self.repo.resources.seq_flow.changed_files.keys()) ), cause_by=self, - sent_from=self, ) async def _new_system_design(self, context): diff --git a/metagpt/actions/project_management.py b/metagpt/actions/project_management.py index 6e786c6a3..ef0fe6fc6 100644 --- a/metagpt/actions/project_management.py +++ b/metagpt/actions/project_management.py @@ -62,7 +62,6 @@ class WriteTasks(Action): + list(self.repo.resources.api_spec_and_task.changed_files.keys()) ), cause_by=self, - sent_from=self, ) async def _update_tasks(self, filename): diff --git a/metagpt/actions/write_prd.py b/metagpt/actions/write_prd.py index b90e7bf5f..a4f6e1dd1 100644 --- a/metagpt/actions/write_prd.py +++ b/metagpt/actions/write_prd.py @@ -94,7 +94,6 @@ class WritePRD(Action): + list(self.repo.resources.competitive_analysis.changed_files.keys()) ), cause_by=self, - sent_from=self, ) async def _handle_bugfix(self, req: Document) -> Message: @@ -104,7 +103,6 @@ class WritePRD(Action): return AIMessage( content=f"A new issue is received: {BUGFIX_FILENAME}", cause_by=FixBug, - sent_from=self, send_to="Alex", # the name of Engineer ) diff --git a/metagpt/const.py b/metagpt/const.py index eaa22434f..6e823d56c 100644 --- a/metagpt/const.py +++ b/metagpt/const.py @@ -81,6 +81,7 @@ MESSAGE_ROUTE_CAUSE_BY = "cause_by" MESSAGE_META_ROLE = "role" MESSAGE_ROUTE_TO_ALL = "" MESSAGE_ROUTE_TO_NONE = "" +MESSAGE_ROUTE_TO_SELF = "" # Add this tag to replace `ActionOutput` REQUIREMENT_FILENAME = "requirement.txt" BUGFIX_FILENAME = "bugfix.txt" diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py index 1e51e9db6..3b7ba597a 100644 --- a/metagpt/roles/engineer.py +++ b/metagpt/roles/engineer.py @@ -47,6 +47,7 @@ from metagpt.logs import logger from metagpt.roles import Role from metagpt.schema import ( AIMessage, + AISelfMessage, CodePlanAndChangeContext, CodeSummarizeContext, CodingContext, @@ -172,12 +173,7 @@ class Engineer(Role): async def _act_write_code(self): await self._act_sp_with_cr(review=self.use_code_review) - return AIMessage( - content="", - cause_by=WriteCodeReview if self.use_code_review else WriteCode, - send_to=self, - sent_from=self, - ) + return AISelfMessage(content="", cause_by=WriteCodeReview if self.use_code_review else WriteCode) async def _act_summarize(self): tasks = [] @@ -216,7 +212,6 @@ class Engineer(Role): + list(self.project_repo.srcs.changed_files.keys()) ), cause_by=SummarizeCode, - sent_from=self, send_to="Edward", # The name of QaEngineer ) # The maximum number of times the 'SummarizeCode' action is automatically invoked, with -1 indicating unlimited. @@ -244,12 +239,7 @@ class Engineer(Role): dependencies=dependencies, ) - return AIMessage( - content="", - cause_by=WriteCodePlanAndChange, - send_to=self, - sent_from=self, - ) + return AISelfMessage(content="", cause_by=WriteCodePlanAndChange) async def _is_pass(self, summary) -> (str, str): rsp = await self.llm.aask(msg=IS_PASS_PROMPT.format(context=summary), stream=False) diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index 7e3c85579..1eaa77fa3 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -30,6 +30,7 @@ from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny, model_validat from metagpt.actions import Action, ActionOutput from metagpt.actions.action_node import ActionNode from metagpt.actions.add_requirement import UserRequirement +from metagpt.const import MESSAGE_ROUTE_TO_SELF from metagpt.context_mixin import ContextMixin from metagpt.logs import logger from metagpt.memory import Memory @@ -443,6 +444,11 @@ class Role(SerializationMixin, ContextMixin, BaseModel): """If the role belongs to env, then the role's messages will be broadcast to env""" if not msg: return + if MESSAGE_ROUTE_TO_SELF in msg.send_to: + msg.send_to.add(any_to_str(self)) + msg.send_to.remove(MESSAGE_ROUTE_TO_SELF) + if not msg.sent_from or msg.sent_from == MESSAGE_ROUTE_TO_SELF: + msg.sent_from = any_to_str(self) if all(to in {any_to_str(self), self.name} for to in msg.send_to): # Message to myself self.put_message(msg) return diff --git a/metagpt/schema.py b/metagpt/schema.py index ba64c0f06..ee8b1ca96 100644 --- a/metagpt/schema.py +++ b/metagpt/schema.py @@ -43,6 +43,7 @@ from metagpt.const import ( MESSAGE_ROUTE_FROM, MESSAGE_ROUTE_TO, MESSAGE_ROUTE_TO_ALL, + MESSAGE_ROUTE_TO_SELF, PRDS_FILE_REPO, SYSTEM_DESIGN_FILE_REPO, TASK_FILE_REPO, @@ -200,7 +201,7 @@ class Message(BaseModel): """list[: ]""" id: str = Field(default="", validate_default=True) # According to Section 2.2.3.1.1 of RFC 135 - content: str + content: str # natural language for user or agent instruct_content: Optional[BaseModel] = Field(default=None, validate_default=True) role: str = "user" # system / user / assistant cause_by: str = Field(default="", validate_default=True) @@ -399,6 +400,13 @@ class AIMessage(Message): return self.metadata.get(AGENT, "") +class AISelfMessage(AIMessage): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.sent_from = MESSAGE_ROUTE_TO_SELF + self.send_to = MESSAGE_ROUTE_TO_SELF + + class Task(BaseModel): task_id: str = "" dependent_task_ids: list[str] = [] # Tasks prerequisite to this Task From d82f890b2ee451b2a490d6e86f5e8122f5bb61d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 10 May 2024 11:56:56 +0800 Subject: [PATCH 5/9] fixbug: sent_from, send_to --- metagpt/roles/engineer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py index 3b7ba597a..f615c8f8c 100644 --- a/metagpt/roles/engineer.py +++ b/metagpt/roles/engineer.py @@ -217,7 +217,7 @@ class Engineer(Role): # The maximum number of times the 'SummarizeCode' action is automatically invoked, with -1 indicating unlimited. # This parameter is used for debugging the workflow. self.n_summarize += 1 if self.config.max_auto_summarize_code > self.n_summarize else 0 - return AIMessage(content="", cause_by=SummarizeCode, send_to=self, sent_from=self) + return AISelfMessage(content="", cause_by=SummarizeCode) async def _act_code_plan_and_change(self): """Write code plan and change that guides subsequent WriteCode and WriteCodeReview""" From 6067245ae32eaf69b9513ffeea030fb1be9f26b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 10 May 2024 12:16:17 +0800 Subject: [PATCH 6/9] fixbug: sent_from, send_to --- metagpt/roles/qa_engineer.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/metagpt/roles/qa_engineer.py b/metagpt/roles/qa_engineer.py index 99c9bb676..825cd8017 100644 --- a/metagpt/roles/qa_engineer.py +++ b/metagpt/roles/qa_engineer.py @@ -21,7 +21,14 @@ from metagpt.actions.summarize_code import SummarizeCode from metagpt.const import MESSAGE_ROUTE_TO_NONE from metagpt.logs import logger from metagpt.roles import Role -from metagpt.schema import AIMessage, Document, Message, RunCodeContext, TestingContext +from metagpt.schema import ( + AIMessage, + AISelfMessage, + Document, + Message, + RunCodeContext, + TestingContext, +) from metagpt.utils.common import ( any_to_str, any_to_str_set, @@ -95,11 +102,9 @@ class QaEngineer(Role): additional_python_paths=[str(self.context.src_workspace)], ) self.publish_message( - AIMessage( + AISelfMessage( content=run_code_context.model_dump_json(), cause_by=WriteTest, - sent_from=self, - send_to=self, ) ) @@ -133,7 +138,6 @@ class QaEngineer(Role): AIMessage( content=run_code_context.model_dump_json(), cause_by=RunCode, - sent_from=self, send_to=mappings.get(recipient, MESSAGE_ROUTE_TO_NONE), ) ) @@ -143,14 +147,7 @@ class QaEngineer(Role): code = await DebugError(i_context=run_code_context, context=self.context, llm=self.llm).run() await self.project_repo.tests.save(filename=run_code_context.test_filename, content=code) run_code_context.output = None - self.publish_message( - AIMessage( - content=run_code_context.model_dump_json(), - cause_by=DebugError, - sent_from=self, - send_to=self, - ) - ) + self.publish_message(AISelfMessage(content=run_code_context.model_dump_json(), cause_by=DebugError)) async def _act(self) -> Message: if self.project_path: @@ -160,7 +157,6 @@ class QaEngineer(Role): content=f"Exceeding {self.test_round_allowed} rounds of tests, stop. " + "\n".join(list(self.project_repo.tests.changed_files.keys())), cause_by=WriteTest, - sent_from=self.profile, send_to=MESSAGE_ROUTE_TO_NONE, ) return result_msg @@ -186,7 +182,6 @@ class QaEngineer(Role): return AIMessage( content=f"Round {self.test_round} of tests done", cause_by=WriteTest, - sent_from=self.profile, send_to=MESSAGE_ROUTE_TO_NONE, ) From e01d4b3125e406f85b6caadb6da1852239054f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Fri, 10 May 2024 23:48:16 +0800 Subject: [PATCH 7/9] feat: +log --- metagpt/actions/prepare_documents.py | 2 ++ metagpt/environment/base_env.py | 2 ++ metagpt/utils/git_repository.py | 6 ++--- tests/metagpt/test_environment.py | 4 +++- tests/metagpt/tools/libs/test_git.py | 34 ++++++++++++++++++++++++++++ 5 files changed, 44 insertions(+), 4 deletions(-) diff --git a/metagpt/actions/prepare_documents.py b/metagpt/actions/prepare_documents.py index bba3deaa3..eb674374c 100644 --- a/metagpt/actions/prepare_documents.py +++ b/metagpt/actions/prepare_documents.py @@ -13,6 +13,7 @@ from typing import Dict, Optional from metagpt.actions import Action, UserRequirement from metagpt.const import REQUIREMENT_FILENAME +from metagpt.logs import logger from metagpt.schema import AIMessage from metagpt.utils.common import any_to_str from metagpt.utils.file_repository import FileRepository @@ -56,6 +57,7 @@ class PrepareDocuments(Action): if not v or k in ["resources", "reason"]: continue self.context.kwargs.set(k, v) + logger.info(f"{k}={v}") if self.context.kwargs.project_path: self.config.update_via_cli( project_path=self.context.kwargs.project_path, diff --git a/metagpt/environment/base_env.py b/metagpt/environment/base_env.py index f6d2e431d..db6f6b454 100644 --- a/metagpt/environment/base_env.py +++ b/metagpt/environment/base_env.py @@ -202,6 +202,8 @@ class Environment(ExtEnv): for _ in range(k): futures = [] for role in self.roles.values(): + if role.is_idle: + continue future = role.run() futures.append(future) diff --git a/metagpt/utils/git_repository.py b/metagpt/utils/git_repository.py index 3c45c4085..23a79929e 100644 --- a/metagpt/utils/git_repository.py +++ b/metagpt/utils/git_repository.py @@ -505,11 +505,11 @@ class GitRepository: issue=issue, ) else: - head_branch = base_repo.get_branch(base) - base_branch = head_repo.get_branch(head) + base_branch = base_repo.get_branch(base) + head_branch = head_repo.get_branch(head) pr = base_repo.create_pull( base=base_branch.name, - head=head_branch.commit.sha, + head=f"{head_repo.full_name}:{head_branch.name}", title=title, body=body, maintainer_can_modify=maintainer_can_modify, diff --git a/tests/metagpt/test_environment.py b/tests/metagpt/test_environment.py index 07f5b4305..522013804 100644 --- a/tests/metagpt/test_environment.py +++ b/tests/metagpt/test_environment.py @@ -31,6 +31,7 @@ serdeser_path = Path(__file__).absolute().parent.joinpath("../data/serdeser_stor class MockEnv(Environment): def publish_message(self, message: Message, peekable: bool = True) -> bool: + logger.info(f"{message.metadata}:{message.content}") consumers = [] for role, addrs in self.member_addrs.items(): if is_send_to(message, addrs): @@ -87,6 +88,7 @@ async def test_publish_and_process_message(env: Environment): assert len(env.history.storage) == 0 +@pytest.mark.skip @pytest.mark.asyncio @pytest.mark.parametrize( ("content", "send_to"), @@ -105,7 +107,7 @@ async def test_publish_and_process_message(env: Environment): any_to_str(ProjectManager), ), ( - "Rewrite 'main.py' of the project at '/Users/iorishinier/github/MetaGPT/workspace/snake_game'", + "src filename is 'game.py', Uncaught SyntaxError: Identifier 'Position' has already been declared (at game.js:1:1), the project at '/Users/iorishinier/github/bak/MetaGPT/workspace/snake_game'", any_to_str(Engineer), ), ( diff --git a/tests/metagpt/tools/libs/test_git.py b/tests/metagpt/tools/libs/test_git.py index c96a3eb46..c695275ff 100644 --- a/tests/metagpt/tools/libs/test_git.py +++ b/tests/metagpt/tools/libs/test_git.py @@ -91,6 +91,39 @@ async def test_new_pr(): assert pr +@pytest.mark.skip +@pytest.mark.asyncio +async def test_new_pr1(): + body = """ + >>> SUMMARY + >>> Change HTTP library used to send requests + >>> + >>> TESTS + >>> - [x] Send 'GET' request + >>> - [x] Send 'POST' request with/without body + """ + pr = await GitRepository.create_pull( + base_repo_name="iorisa/MetaGPT", + base="fixbug/vscode", + head_repo_name="send18/MetaGPT", + head="dev", + title="Test pr", + body=body, + access_token=await get_env(key="access_token", app_name="github"), + ) + # pr = await GitRepository.create_pull( + # head_repo_name="iorisa/MetaGPT", + # head="fixbug/vscode", + # base_repo_name="send18/MetaGPT", + # base="dev", + # title="Test pr", + # body=body, + # access_token=await get_env(key="access_token", app_name="github"), + # ) + print(pr) + assert pr + + @pytest.mark.skip @pytest.mark.asyncio async def test_auth(): @@ -124,6 +157,7 @@ async def test_github(context): assert pr +@pytest.mark.skip @pytest.mark.asyncio @pytest.mark.parametrize( "content", From 7616d8c6d3e3bc74eecc44ef00ec3e805c7856b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Sat, 11 May 2024 11:57:05 +0800 Subject: [PATCH 8/9] feat: rm AISelfMessage --- metagpt/roles/engineer.py | 10 ++++++---- metagpt/roles/qa_engineer.py | 20 ++++++-------------- metagpt/schema.py | 8 -------- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py index f615c8f8c..a76db09b1 100644 --- a/metagpt/roles/engineer.py +++ b/metagpt/roles/engineer.py @@ -39,6 +39,7 @@ from metagpt.actions.write_code_plan_and_change_an import WriteCodePlanAndChange from metagpt.const import ( BUGFIX_FILENAME, CODE_PLAN_AND_CHANGE_FILE_REPO, + MESSAGE_ROUTE_TO_SELF, REQUIREMENT_FILENAME, SYSTEM_DESIGN_FILE_REPO, TASK_FILE_REPO, @@ -47,7 +48,6 @@ from metagpt.logs import logger from metagpt.roles import Role from metagpt.schema import ( AIMessage, - AISelfMessage, CodePlanAndChangeContext, CodeSummarizeContext, CodingContext, @@ -173,7 +173,9 @@ class Engineer(Role): async def _act_write_code(self): await self._act_sp_with_cr(review=self.use_code_review) - return AISelfMessage(content="", cause_by=WriteCodeReview if self.use_code_review else WriteCode) + return AIMessage( + content="", cause_by=WriteCodeReview if self.use_code_review else WriteCode, send_to=MESSAGE_ROUTE_TO_SELF + ) async def _act_summarize(self): tasks = [] @@ -217,7 +219,7 @@ class Engineer(Role): # The maximum number of times the 'SummarizeCode' action is automatically invoked, with -1 indicating unlimited. # This parameter is used for debugging the workflow. self.n_summarize += 1 if self.config.max_auto_summarize_code > self.n_summarize else 0 - return AISelfMessage(content="", cause_by=SummarizeCode) + return AIMessage(content="", cause_by=SummarizeCode, send_to=MESSAGE_ROUTE_TO_SELF) async def _act_code_plan_and_change(self): """Write code plan and change that guides subsequent WriteCode and WriteCodeReview""" @@ -239,7 +241,7 @@ class Engineer(Role): dependencies=dependencies, ) - return AISelfMessage(content="", cause_by=WriteCodePlanAndChange) + return AIMessage(content="", cause_by=WriteCodePlanAndChange, send_to=MESSAGE_ROUTE_TO_SELF) async def _is_pass(self, summary) -> (str, str): rsp = await self.llm.aask(msg=IS_PASS_PROMPT.format(context=summary), stream=False) diff --git a/metagpt/roles/qa_engineer.py b/metagpt/roles/qa_engineer.py index 825cd8017..f76baff3f 100644 --- a/metagpt/roles/qa_engineer.py +++ b/metagpt/roles/qa_engineer.py @@ -18,17 +18,10 @@ from metagpt.actions import DebugError, RunCode, UserRequirement, WriteTest from metagpt.actions.prepare_documents import PrepareDocuments from metagpt.actions.summarize_code import SummarizeCode -from metagpt.const import MESSAGE_ROUTE_TO_NONE +from metagpt.const import MESSAGE_ROUTE_TO_NONE, MESSAGE_ROUTE_TO_SELF from metagpt.logs import logger from metagpt.roles import Role -from metagpt.schema import ( - AIMessage, - AISelfMessage, - Document, - Message, - RunCodeContext, - TestingContext, -) +from metagpt.schema import AIMessage, Document, Message, RunCodeContext, TestingContext from metagpt.utils.common import ( any_to_str, any_to_str_set, @@ -102,10 +95,7 @@ class QaEngineer(Role): additional_python_paths=[str(self.context.src_workspace)], ) self.publish_message( - AISelfMessage( - content=run_code_context.model_dump_json(), - cause_by=WriteTest, - ) + AIMessage(content=run_code_context.model_dump_json(), cause_by=WriteTest, send_to=MESSAGE_ROUTE_TO_SELF) ) logger.info(f"Done {str(self.project_repo.tests.workdir)} generating.") @@ -147,7 +137,9 @@ class QaEngineer(Role): code = await DebugError(i_context=run_code_context, context=self.context, llm=self.llm).run() await self.project_repo.tests.save(filename=run_code_context.test_filename, content=code) run_code_context.output = None - self.publish_message(AISelfMessage(content=run_code_context.model_dump_json(), cause_by=DebugError)) + self.publish_message( + AIMessage(content=run_code_context.model_dump_json(), cause_by=DebugError, send_to=MESSAGE_ROUTE_TO_SELF) + ) async def _act(self) -> Message: if self.project_path: diff --git a/metagpt/schema.py b/metagpt/schema.py index ee8b1ca96..1b527b594 100644 --- a/metagpt/schema.py +++ b/metagpt/schema.py @@ -43,7 +43,6 @@ from metagpt.const import ( MESSAGE_ROUTE_FROM, MESSAGE_ROUTE_TO, MESSAGE_ROUTE_TO_ALL, - MESSAGE_ROUTE_TO_SELF, PRDS_FILE_REPO, SYSTEM_DESIGN_FILE_REPO, TASK_FILE_REPO, @@ -400,13 +399,6 @@ class AIMessage(Message): return self.metadata.get(AGENT, "") -class AISelfMessage(AIMessage): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.sent_from = MESSAGE_ROUTE_TO_SELF - self.send_to = MESSAGE_ROUTE_TO_SELF - - class Task(BaseModel): task_id: str = "" dependent_task_ids: list[str] = [] # Tasks prerequisite to this Task From 76eca0e9247cd70612ec7860035d8f0c76679c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Sat, 11 May 2024 12:00:23 +0800 Subject: [PATCH 9/9] feat: rm AISelfMessage --- metagpt/environment/base_env.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/metagpt/environment/base_env.py b/metagpt/environment/base_env.py index db6f6b454..fe1660fc6 100644 --- a/metagpt/environment/base_env.py +++ b/metagpt/environment/base_env.py @@ -207,7 +207,8 @@ class Environment(ExtEnv): future = role.run() futures.append(future) - await asyncio.gather(*futures) + if futures: + await asyncio.gather(*futures) logger.debug(f"is idle: {self.is_idle}") def get_roles(self) -> dict[str, "Role"]: