Skip to content

Commit c966138

Browse files
author
莘权 马
committed
feat: +unit test
fixbug: Align to the same root directory in accordance with `class_views` fixbug: The class has lost namespace information. feat: + mock fixbug: project name invalid feat: +mermaid sequence diagram feat: translate from code to mermaid sequence feat: translate from code to mermaid sequence
1 parent 1b06d93 commit c966138

14 files changed

+216
-45
lines changed

metagpt/actions/prepare_documents.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ def _init_repo(self):
3535
if path.exists() and not CONFIG.inc:
3636
shutil.rmtree(path)
3737
CONFIG.project_path = path
38-
CONFIG.project_name = path.name
3938
CONFIG.git_repo = GitRepository(local_path=path, auto_init=True)
4039

4140
async def run(self, with_messages, **kwargs):

metagpt/actions/rebuild_class_view.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,16 @@ async def run(self, with_messages=None, format=CONFIG.prompt_schema):
3333
graph_repo_pathname = CONFIG.git_repo.workdir / GRAPH_REPO_FILE_REPO / CONFIG.git_repo.workdir.name
3434
graph_db = await DiGraphRepository.load_from(str(graph_repo_pathname.with_suffix(".json")))
3535
repo_parser = RepoParser(base_directory=Path(self.context))
36-
class_views, relationship_views = await repo_parser.rebuild_class_views(path=Path(self.context)) # use pylint
36+
# use pylint
37+
class_views, relationship_views, package_root = await repo_parser.rebuild_class_views(path=Path(self.context))
3738
await GraphRepository.update_graph_db_with_class_views(graph_db, class_views)
3839
await GraphRepository.update_graph_db_with_class_relationship_views(graph_db, relationship_views)
39-
symbols = repo_parser.generate_symbols() # use ast
40+
# use ast
41+
direction, diff_path = self._diff_path(path_root=Path(self.context).resolve(), package_root=package_root)
42+
symbols = repo_parser.generate_symbols()
4043
for file_info in symbols:
44+
# Align to the same root directory in accordance with `class_views`.
45+
file_info.file = self._align_root(file_info.file, direction, diff_path)
4146
await GraphRepository.update_graph_db_with_file_info(graph_db, file_info)
4247
await self._create_mermaid_class_views(graph_db=graph_db)
4348
await graph_db.save()
@@ -193,3 +198,20 @@ async def _parse_function_args(method: ClassMethod, ns_name: str, graph_db: Grap
193198
method.args.append(ClassAttribute(name=parts[0].strip()))
194199
continue
195200
method.args.append(ClassAttribute(name=parts[0].strip(), value_type=parts[-1].strip()))
201+
202+
@staticmethod
203+
def _diff_path(path_root: Path, package_root: Path) -> (str, str):
204+
if len(str(path_root)) > len(str(package_root)):
205+
return "+", str(path_root.relative_to(package_root))
206+
if len(str(path_root)) < len(str(package_root)):
207+
return "-", str(package_root.relative_to(path_root))
208+
return "=", "."
209+
210+
@staticmethod
211+
def _align_root(path: str, direction: str, diff_path: str):
212+
if direction == "=":
213+
return path
214+
if direction == "+":
215+
return diff_path + "/" + path
216+
else:
217+
return path[len(diff_path) + 1 :]

metagpt/actions/rebuild_class_view_an.py

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
"""
4+
@Time : 2024/1/4
5+
@Author : mashenquan
6+
@File : rebuild_sequence_view.py
7+
@Desc : Rebuild sequence view info
8+
"""
9+
from __future__ import annotations
10+
11+
from pathlib import Path
12+
from typing import List
13+
14+
from metagpt.actions import Action
15+
from metagpt.config import CONFIG
16+
from metagpt.const import GRAPH_REPO_FILE_REPO
17+
from metagpt.logs import logger
18+
from metagpt.utils.common import aread, list_files
19+
from metagpt.utils.di_graph_repository import DiGraphRepository
20+
from metagpt.utils.graph_repository import GraphKeyword
21+
22+
23+
class RebuildSequenceView(Action):
24+
async def run(self, with_messages=None, format=CONFIG.prompt_schema):
25+
graph_repo_pathname = CONFIG.git_repo.workdir / GRAPH_REPO_FILE_REPO / CONFIG.git_repo.workdir.name
26+
graph_db = await DiGraphRepository.load_from(str(graph_repo_pathname.with_suffix(".json")))
27+
entries = await RebuildSequenceView._search_main_entry(graph_db)
28+
for entry in entries:
29+
await self._rebuild_sequence_view(entry, graph_db)
30+
await graph_db.save()
31+
32+
@staticmethod
33+
async def _search_main_entry(graph_db) -> List:
34+
rows = await graph_db.select(predicate=GraphKeyword.HAS_PAGE_INFO)
35+
tag = "__name__:__main__"
36+
entries = []
37+
for r in rows:
38+
if tag in r.subject or tag in r.object_:
39+
entries.append(r)
40+
return entries
41+
42+
async def _rebuild_sequence_view(self, entry, graph_db):
43+
filename = entry.subject.split(":", 1)[0]
44+
src_filename = RebuildSequenceView._get_full_filename(root=self.context, pathname=filename)
45+
content = await aread(filename=src_filename, encoding="utf-8")
46+
content = f"```python\n{content}\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram."
47+
data = await self.llm.aask(
48+
msg=content, system_msgs=["You are a python code to Mermaid Sequence Diagram translator in function detail"]
49+
)
50+
await graph_db.insert(subject=filename, predicate=GraphKeyword.HAS_SEQUENCE_VIEW, object_=data)
51+
logger.info(data)
52+
53+
@staticmethod
54+
def _get_full_filename(root: str | Path, pathname: str | Path) -> Path | None:
55+
files = list_files(root=root)
56+
postfix = "/" + str(pathname)
57+
for i in files:
58+
if str(i).endswith(postfix):
59+
return i
60+
return None
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
"""
4+
@Time : 2024/1/4
5+
@Author : mashenquan
6+
@File : rebuild_sequence_view_an.py
7+
"""
8+
from metagpt.actions.action_node import ActionNode
9+
from metagpt.utils.mermaid import MMC2
10+
11+
CODE_2_MERMAID_SEQUENCE_DIAGRAM = ActionNode(
12+
key="Program call flow",
13+
expected_type=str,
14+
instruction='Translate the "context" content into "format example" format.',
15+
example=MMC2,
16+
)

metagpt/actions/write_prd.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from __future__ import annotations
1515

1616
import json
17+
import uuid
1718
from pathlib import Path
1819
from typing import Optional
1920

@@ -117,7 +118,7 @@ async def _run_new_requirement(self, requirements, schema=CONFIG.prompt_schema)
117118
# if sas.result:
118119
# logger.info(sas.result)
119120
# logger.info(rsp)
120-
project_name = CONFIG.project_name if CONFIG.project_name else ""
121+
project_name = CONFIG.project_name or ""
121122
context = CONTEXT_TEMPLATE.format(requirements=requirements, project_name=project_name)
122123
exclude = [PROJECT_NAME.key] if project_name else []
123124
node = await WRITE_PRD_NODE.fill(context=context, llm=self.llm, exclude=exclude) # schema=schema
@@ -183,6 +184,8 @@ async def _rename_workspace(prd):
183184
ws_name = CodeParser.parse_str(block="Project Name", text=prd)
184185
if ws_name:
185186
CONFIG.project_name = ws_name
187+
if not CONFIG.project_name: # The LLM failed to provide a project name, and the user didn't provide one either.
188+
CONFIG.project_name = "app" + uuid.uuid4().hex[:16]
186189
CONFIG.git_repo.rename_root(CONFIG.project_name)
187190

188191
async def _is_bugfix(self, context) -> bool:

metagpt/repo_parser.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -240,12 +240,12 @@ async def rebuild_class_views(self, path: str | Path = None):
240240
class_views = await self._parse_classes(class_view_pathname)
241241
relationship_views = await self._parse_class_relationships(class_view_pathname)
242242
packages_pathname = path / "packages.dot"
243-
class_views, relationship_views = RepoParser._repair_namespaces(
243+
class_views, relationship_views, package_root = RepoParser._repair_namespaces(
244244
class_views=class_views, relationship_views=relationship_views, path=path
245245
)
246246
class_view_pathname.unlink(missing_ok=True)
247247
packages_pathname.unlink(missing_ok=True)
248-
return class_views, relationship_views
248+
return class_views, relationship_views, package_root
249249

250250
async def _parse_classes(self, class_view_pathname):
251251
class_views = []
@@ -364,9 +364,9 @@ def _create_path_mapping(path: str | Path) -> Dict[str, str]:
364364
@staticmethod
365365
def _repair_namespaces(
366366
class_views: List[ClassInfo], relationship_views: List[ClassRelationship], path: str | Path
367-
) -> (List[ClassInfo], List[ClassRelationship]):
367+
) -> (List[ClassInfo], List[ClassRelationship], str):
368368
if not class_views:
369-
return []
369+
return [], [], ""
370370
c = class_views[0]
371371
full_key = str(path).lstrip("/").replace("/", ".")
372372
root_namespace = RepoParser._find_root(full_key, c.package)
@@ -388,7 +388,7 @@ def _repair_namespaces(
388388
v.src = RepoParser._repair_ns(v.src, new_mappings)
389389
v.dest = RepoParser._repair_ns(v.dest, new_mappings)
390390
relationship_views[i] = v
391-
return class_views, relationship_views
391+
return class_views, relationship_views, root_path
392392

393393
@staticmethod
394394
def _repair_ns(package, mappings):

metagpt/utils/common.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,3 +550,20 @@ async def read_file_block(filename: str | Path, lineno: int, end_lineno: int):
550550
break
551551
lines.append(line)
552552
return "".join(lines)
553+
554+
555+
def list_files(root: str | Path) -> List[Path]:
556+
files = []
557+
try:
558+
directory_path = Path(root)
559+
if not directory_path.exists():
560+
return []
561+
for file_path in directory_path.iterdir():
562+
if file_path.is_file():
563+
files.append(file_path)
564+
else:
565+
subfolder_files = list_files(root=file_path)
566+
files.extend(subfolder_files)
567+
except Exception as e:
568+
logger.error(f"Error: {e}")
569+
return files

metagpt/utils/graph_repository.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,12 @@ async def update_graph_db_with_file_info(graph_db: "GraphRepository", file_info:
135135
@staticmethod
136136
async def update_graph_db_with_class_views(graph_db: "GraphRepository", class_views: List[ClassInfo]):
137137
for c in class_views:
138-
filename, class_name = c.package.split(":", 1)
138+
filename, _ = c.package.split(":", 1)
139139
await graph_db.insert(subject=filename, predicate=GraphKeyword.IS, object_=GraphKeyword.SOURCE_CODE)
140140
file_types = {".py": "python", ".js": "javascript"}
141141
file_type = file_types.get(Path(filename).suffix, GraphKeyword.NULL)
142142
await graph_db.insert(subject=filename, predicate=GraphKeyword.IS, object_=file_type)
143-
await graph_db.insert(subject=filename, predicate=GraphKeyword.HAS_CLASS, object_=class_name)
143+
await graph_db.insert(subject=filename, predicate=GraphKeyword.HAS_CLASS, object_=c.package)
144144
await graph_db.insert(
145145
subject=c.package,
146146
predicate=GraphKeyword.IS,

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def run(self):
4646
"chromadb==0.4.14",
4747
"gradio==3.0.0",
4848
"grpcio-status==1.48.2",
49+
"mock==5.1.0",
4950
]
5051

5152
extras_require["pyppeteer"] = [

tests/data/graph_db/networkx.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

tests/metagpt/actions/test_rebuild_class_view.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,32 @@ async def test_rebuild():
2626
assert graph_file_repo.changed_files
2727

2828

29+
@pytest.mark.parametrize(
30+
("path", "direction", "diff", "want"),
31+
[
32+
("metagpt/startup.py", "=", ".", "metagpt/startup.py"),
33+
("metagpt/startup.py", "+", "MetaGPT", "MetaGPT/metagpt/startup.py"),
34+
("metagpt/startup.py", "-", "metagpt", "startup.py"),
35+
],
36+
)
37+
def test_align_path(path, direction, diff, want):
38+
res = RebuildClassView._align_root(path=path, direction=direction, diff_path=diff)
39+
assert res == want
40+
41+
42+
@pytest.mark.parametrize(
43+
("path_root", "package_root", "want_direction", "want_diff"),
44+
[
45+
("/Users/x/github/MetaGPT/metagpt", "/Users/x/github/MetaGPT/metagpt", "=", "."),
46+
("/Users/x/github/MetaGPT", "/Users/x/github/MetaGPT/metagpt", "-", "metagpt"),
47+
("/Users/x/github/MetaGPT/metagpt", "/Users/x/github/MetaGPT", "+", "metagpt"),
48+
],
49+
)
50+
def test_diff_path(path_root, package_root, want_direction, want_diff):
51+
direction, diff = RebuildClassView._diff_path(path_root=Path(path_root), package_root=Path(package_root))
52+
assert direction == want_direction
53+
assert diff == want_diff
54+
55+
2956
if __name__ == "__main__":
3057
pytest.main([__file__, "-s"])
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
"""
4+
@Time : 2024/1/4
5+
@Author : mashenquan
6+
@File : test_rebuild_sequence_view.py
7+
"""
8+
from pathlib import Path
9+
10+
import pytest
11+
12+
from metagpt.actions.rebuild_sequence_view import RebuildSequenceView
13+
from metagpt.config import CONFIG
14+
from metagpt.const import GRAPH_REPO_FILE_REPO
15+
from metagpt.llm import LLM
16+
from metagpt.utils.common import aread
17+
from metagpt.utils.file_repository import FileRepository
18+
from metagpt.utils.git_repository import ChangeType
19+
20+
21+
@pytest.mark.asyncio
22+
async def test_rebuild():
23+
# Mock
24+
data = await aread(filename=Path(__file__).parent / "../../data/graph_db/networkx.json")
25+
graph_db_filename = Path(CONFIG.git_repo.workdir.name).with_suffix(".json")
26+
await FileRepository.save_file(
27+
filename=str(graph_db_filename),
28+
relative_path=GRAPH_REPO_FILE_REPO,
29+
content=data,
30+
)
31+
CONFIG.git_repo.add_change({f"{GRAPH_REPO_FILE_REPO}/{graph_db_filename}": ChangeType.UNTRACTED})
32+
CONFIG.git_repo.commit("commit1")
33+
34+
action = RebuildSequenceView(
35+
name="RedBean", context=str(Path(__file__).parent.parent.parent.parent / "metagpt"), llm=LLM()
36+
)
37+
await action.run()
38+
graph_file_repo = CONFIG.git_repo.new_file_repository(relative_path=GRAPH_REPO_FILE_REPO)
39+
assert graph_file_repo.changed_files
40+
41+
42+
@pytest.mark.parametrize(
43+
("root", "pathname", "want"),
44+
[
45+
(Path(__file__).parent.parent.parent, "/".join(__file__.split("/")[-2:]), Path(__file__)),
46+
(Path(__file__).parent.parent.parent, "f/g.txt", None),
47+
],
48+
)
49+
def test_get_full_filename(root, pathname, want):
50+
res = RebuildSequenceView._get_full_filename(root=root, pathname=pathname)
51+
assert res == want
52+
53+
54+
if __name__ == "__main__":
55+
pytest.main([__file__, "-s"])

tests/metagpt/learn/test_skill_loader.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
@File : test_skill_loader.py
77
@Desc : Unit tests.
88
"""
9+
from pathlib import Path
10+
911
import pytest
1012

1113
from metagpt.config import CONFIG
@@ -23,7 +25,8 @@ async def test_suite():
2325
{"id": 6, "name": "knowledge", "type": "builtin", "config": {}, "enabled": True},
2426
{"id": 6, "name": "web_search", "type": "builtin", "config": {}, "enabled": True},
2527
]
26-
loader = await SkillsDeclaration.load()
28+
pathname = Path(__file__).parent / "../../../docs/.well-known/skills.yaml"
29+
loader = await SkillsDeclaration.load(skill_yaml_file_name=pathname)
2730
skills = loader.get_skill_list()
2831
assert skills
2932
assert len(skills) >= 3

0 commit comments

Comments
 (0)