diff --git a/metagpt/actions/rebuild_class_view.py b/metagpt/actions/rebuild_class_view.py index aeff6b42c..9b1a5b3d0 100644 --- a/metagpt/actions/rebuild_class_view.py +++ b/metagpt/actions/rebuild_class_view.py @@ -5,6 +5,7 @@ @Author : mashenquan @File : rebuild_class_view.py @Desc : Reconstructs class diagram from a source code project. + Implement RFC197, https://deepwisdom.feishu.cn/wiki/VyK0wfq56ivuvjklMKJcmHQknGt """ from pathlib import Path @@ -65,15 +66,18 @@ class RebuildClassView(Action): await self._create_mermaid_class_views() await self.graph_db.save() - async def _create_mermaid_class_views(self): + async def _create_mermaid_class_views(self) -> str: """Creates a Mermaid class diagram using data from the `graph_db` graph repository. This method utilizes information stored in the graph repository to generate a Mermaid class diagram. + Returns: + mermaid class diagram file name. """ path = self.context.git_repo.workdir / DATA_API_DESIGN_FILE_REPO path.mkdir(parents=True, exist_ok=True) pathname = path / self.context.git_repo.workdir.name - async with aiofiles.open(str(pathname.with_suffix(".mmd")), mode="w", encoding="utf-8") as writer: + filename = str(pathname.with_suffix(".mmd")) + async with aiofiles.open(filename, mode="w", encoding="utf-8") as writer: content = "classDiagram\n" logger.debug(content) await writer.write(content) @@ -94,6 +98,14 @@ class RebuildClassView(Action): relationship_distinct.update(distinct) logger.info(f"classes: {len(class_distinct)}, relationship: {len(relationship_distinct)}") + if self.i_context: + r_filename = Path(filename).relative_to(self.context.git_repo.workdir) + await self.graph_db.insert( + subject=self.i_context, predicate="hasMermaidClassDiagramFile", object_=str(r_filename) + ) + logger.info(f"{self.i_context} hasMermaidClassDiagramFile {filename}") + return filename + async def _create_mermaid_class(self, ns_class_name) -> str: """Generates a Mermaid class diagram for a specific class using data from the `graph_db` graph repository. diff --git a/metagpt/actions/rebuild_sequence_view.py b/metagpt/actions/rebuild_sequence_view.py index ef0b88889..da57af52d 100644 --- a/metagpt/actions/rebuild_sequence_view.py +++ b/metagpt/actions/rebuild_sequence_view.py @@ -5,6 +5,7 @@ @Author : mashenquan @File : rebuild_sequence_view.py @Desc : Reconstruct sequence view information through reverse engineering. + Implement RFC197, https://deepwisdom.feishu.cn/wiki/VyK0wfq56ivuvjklMKJcmHQknGt """ from __future__ import annotations @@ -93,7 +94,10 @@ class RebuildSequenceView(Action): """ graph_repo_pathname = self.context.git_repo.workdir / GRAPH_REPO_FILE_REPO / self.context.git_repo.workdir.name self.graph_db = await DiGraphRepository.load_from(str(graph_repo_pathname.with_suffix(".json"))) - entries = await self._search_main_entry() + if not self.i_context: + entries = await self._search_main_entry() + else: + entries = [SPO(subject=self.i_context, predicate="", object_="")] for entry in entries: await self._rebuild_main_sequence_view(entry) while await self._merge_sequence_view(entry): @@ -139,7 +143,7 @@ class RebuildSequenceView(Action): use_case_blocks = [] for c in classes: use_cases = await self._get_class_use_cases(c.subject) - use_case_blocks.extend(use_cases) + use_case_blocks.append(use_cases) prompt_blocks = ["## Use Cases\n" + "\n".join(use_case_blocks)] block = "## Participants\n" for p in participants: @@ -238,6 +242,15 @@ class RebuildSequenceView(Action): class_view = await self._get_uml_class_view(ns_class_name) source_code = await self._get_source_code(ns_class_name) + # prompt_blocks = [ + # "## Instruction\n" + # "You are a python code to UML 2.0 Use Case translator.\n" + # 'The generated UML 2.0 Use Case must include the roles or entities listed in "Participants".\n' + # "The functional descriptions of Actors and Use Cases in the generated UML 2.0 Use Case must not " + # 'conflict with the information in "Mermaid Class Views".\n' + # 'The section under `if __name__ == "__main__":` of "Source Code" contains information about external ' + # "system interactions with the internal system.\n" + # ] prompt_blocks = [] block = "## Participants\n" for p in participants: @@ -253,6 +266,38 @@ class RebuildSequenceView(Action): prompt_blocks.append(block) prompt = "\n---\n".join(prompt_blocks) + # class _UseCase(BaseModel): + # description: str = Field(default="...", description="Describes about what the use case to do") + # inputs: List[str] = Field(default=["input name 1", "input name 2"], + # description="Lists the input names of the use case from external sources") + # outputs: List[str] = Field(default=["output name 1", "output name 2"], + # description="Lists the output names of the use case to external sources") + # actors: List[str] = Field(default=["actor name 1", "actor name 2"], + # description="Lists the participant actors of the use case") + # steps: List[str] = Field(default=["Step 1", "Step 2"], + # description="Lists the steps about how the use case works step by step") + # reason: str = Field(default="Because ...", + # description="Explaining under what circumstances would the external system execute this use case.") + # + # + # class _UseCaseList(BaseModel): + # description: str = Field(default="...", + # description="A summary explains what the whole source code want to do") + # use_cases: List[_UseCase] = Field(default=[ + # { + # "description": "Describes about what the use case to do", + # "inputs": ["input name 1", "input name 2"], + # "outputs": ["output name 1", "output name 2"], + # "actors": ["actor name 1", "actor name 2"], + # "steps": ["Step 1", "Step 2"], + # "reason": "Because ..." + # } + # ], description="List all use cases.") + # relationship: List[str] = Field(default=["use case 1 ..."], + # description="Lists all the descriptions of relationship among these use cases") + + # rsp = await ActionNode.from_pydantic(_UseCaseList).fill(context=prompt, llm=self.llm) + rsp = await self.llm.aask( msg=prompt, system_msgs=[ @@ -452,6 +497,8 @@ class RebuildSequenceView(Action): "metagpt/management/skill_manager.py", then the returned value will be "/User/xxx/github/MetaGPT/metagpt/management/skill_manager.py" """ + if re.match(r"^/.+", pathname): + return pathname files = list_files(root=root) postfix = "/" + str(pathname) for i in files: diff --git a/metagpt/repo_parser.py b/metagpt/repo_parser.py index bbbbb955d..15842fdfb 100644 --- a/metagpt/repo_parser.py +++ b/metagpt/repo_parser.py @@ -950,12 +950,10 @@ class RepoParser(BaseModel): for c in class_views: c.package = RepoParser._repair_ns(c.package, new_mappings) - for i in range(len(relationship_views)): - v = relationship_views[i] + for _, v in enumerate(relationship_views): v.src = RepoParser._repair_ns(v.src, new_mappings) v.dest = RepoParser._repair_ns(v.dest, new_mappings) - relationship_views[i] = v - return class_views, relationship_views, root_path + return class_views, relationship_views, str(path)[: len(root_path)] @staticmethod def _repair_ns(package: str, mappings: Dict[str, str]) -> str: diff --git a/metagpt/schema.py b/metagpt/schema.py index 67cadd6c3..7906febe0 100644 --- a/metagpt/schema.py +++ b/metagpt/schema.py @@ -773,4 +773,6 @@ class UMLClassView(UMLClassMeta): for j in i.args: arg = UMLClassAttribute(name=j.name, value_type=j.type_, default_value=j.default_) method.args.append(arg) + method.return_type = i.return_args.type_ + class_view.methods.append(method) return class_view diff --git a/tests/metagpt/actions/test_rebuild_class_view.py b/tests/metagpt/actions/test_rebuild_class_view.py index 403109cc0..3731cd598 100644 --- a/tests/metagpt/actions/test_rebuild_class_view.py +++ b/tests/metagpt/actions/test_rebuild_class_view.py @@ -23,6 +23,8 @@ async def test_rebuild(context): context=context, ) await action.run() + rows = await action.graph_db.select() + assert rows assert context.repo.docs.graph_repo.changed_files @@ -45,6 +47,12 @@ def test_align_path(path, direction, diff, want): ("/Users/x/github/MetaGPT/metagpt", "/Users/x/github/MetaGPT/metagpt", "=", "."), ("/Users/x/github/MetaGPT", "/Users/x/github/MetaGPT/metagpt", "-", "metagpt"), ("/Users/x/github/MetaGPT/metagpt", "/Users/x/github/MetaGPT", "+", "metagpt"), + ( + "/Users/x/github/MetaGPT-env/lib/python3.9/site-packages/moviepy", + "/Users/x/github/MetaGPT-env/lib/python3.9/site-packages/", + "+", + "moviepy", + ), ], ) def test_diff_path(path_root, package_root, want_direction, want_diff): diff --git a/tests/metagpt/actions/test_rebuild_sequence_view.py b/tests/metagpt/actions/test_rebuild_sequence_view.py index c258de1ca..1daea22a4 100644 --- a/tests/metagpt/actions/test_rebuild_sequence_view.py +++ b/tests/metagpt/actions/test_rebuild_sequence_view.py @@ -40,7 +40,9 @@ async def test_rebuild(context, mocker): action = RebuildSequenceView( name="RedBean", - i_context=str(Path(__file__).parent.parent.parent.parent / "metagpt"), + i_context=str( + Path(__file__).parent.parent.parent.parent / "metagpt/management/skill_manager.py:__name__:__main__" + ), llm=LLM(), context=context, ) diff --git a/tests/metagpt/roles/test_assistant.py b/tests/metagpt/roles/test_assistant.py index bd0efea35..0f67dff46 100644 --- a/tests/metagpt/roles/test_assistant.py +++ b/tests/metagpt/roles/test_assistant.py @@ -30,6 +30,17 @@ async def test_run(mocker, context): language: str agent_description: str cause_by: str + agent_skills: list + + agent_skills = [ + {"id": 1, "name": "text_to_speech", "type": "builtin", "config": {}, "enabled": True}, + {"id": 2, "name": "text_to_image", "type": "builtin", "config": {}, "enabled": True}, + {"id": 3, "name": "ai_call", "type": "builtin", "config": {}, "enabled": True}, + {"id": 3, "name": "data_analysis", "type": "builtin", "config": {}, "enabled": True}, + {"id": 5, "name": "crawler", "type": "builtin", "config": {"engine": "ddg"}, "enabled": True}, + {"id": 6, "name": "knowledge", "type": "builtin", "config": {}, "enabled": True}, + {"id": 6, "name": "web_search", "type": "builtin", "config": {}, "enabled": True}, + ] inputs = [ { @@ -48,6 +59,7 @@ async def test_run(mocker, context): "language": "English", "agent_description": "chatterbox", "cause_by": any_to_str(TalkAction), + "agent_skills": [], }, { "memory": { @@ -65,24 +77,16 @@ async def test_run(mocker, context): "language": "English", "agent_description": "painter", "cause_by": any_to_str(SkillAction), + "agent_skills": agent_skills, }, ] - agent_skills = [ - {"id": 1, "name": "text_to_speech", "type": "builtin", "config": {}, "enabled": True}, - {"id": 2, "name": "text_to_image", "type": "builtin", "config": {}, "enabled": True}, - {"id": 3, "name": "ai_call", "type": "builtin", "config": {}, "enabled": True}, - {"id": 3, "name": "data_analysis", "type": "builtin", "config": {}, "enabled": True}, - {"id": 5, "name": "crawler", "type": "builtin", "config": {"engine": "ddg"}, "enabled": True}, - {"id": 6, "name": "knowledge", "type": "builtin", "config": {}, "enabled": True}, - {"id": 6, "name": "web_search", "type": "builtin", "config": {}, "enabled": True}, - ] for i in inputs: seed = Input(**i) role = Assistant(language="Chinese", context=context) role.context.kwargs.language = seed.language role.context.kwargs.agent_description = seed.agent_description - role.context.kwargs.agent_skills = agent_skills + role.context.kwargs.agent_skills = seed.agent_skills role.memory = seed.memory # Restore historical conversation content. while True: