mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-11 15:15:18 +02:00
feat: rebuild sequence view pass
This commit is contained in:
parent
aa0909525e
commit
3a2f162fdd
1 changed files with 164 additions and 97 deletions
|
|
@ -62,6 +62,77 @@ class RebuildSequenceView(Action):
|
|||
pass
|
||||
await self.graph_db.save()
|
||||
|
||||
# @retry(
|
||||
# wait=wait_random_exponential(min=1, max=20),
|
||||
# stop=stop_after_attempt(6),
|
||||
# after=general_after_log(logger),
|
||||
# )
|
||||
async def _rebuild_main_sequence_view(self, entry):
|
||||
filename = entry.subject.split(":", 1)[0]
|
||||
rows = await self.graph_db.select(predicate=GraphKeyword.IS, object_=GraphKeyword.CLASS)
|
||||
classes = []
|
||||
prefix = filename + ":"
|
||||
for r in rows:
|
||||
if prefix in r.subject:
|
||||
classes.append(r)
|
||||
await self._rebuild_use_case(r.subject)
|
||||
participants = set()
|
||||
class_details = []
|
||||
class_views = []
|
||||
for c in classes:
|
||||
detail = await self._get_class_detail(c.subject)
|
||||
if not detail:
|
||||
continue
|
||||
class_details.append(detail)
|
||||
view = await self._get_uml_class_view(c.subject)
|
||||
if view:
|
||||
class_views.append(view)
|
||||
|
||||
actors = await self._get_participants(c.subject)
|
||||
participants.update(set(actors))
|
||||
|
||||
use_case_blocks = []
|
||||
for c in classes:
|
||||
use_cases = await self._get_class_use_cases(c.subject)
|
||||
use_case_blocks.extend(use_cases)
|
||||
prompt_blocks = ["## Use Cases\n" + "\n".join(use_case_blocks)]
|
||||
block = "## Participants\n"
|
||||
for p in participants:
|
||||
block += f"- {p}\n"
|
||||
prompt_blocks.append(block)
|
||||
block = "## Mermaid Class Views\n```mermaid\n"
|
||||
block += "\n\n".join([c.get_mermaid() for c in class_views])
|
||||
block += "\n```\n"
|
||||
prompt_blocks.append(block)
|
||||
block = "## Source Code\n```python\n"
|
||||
block += await self._get_source_code(filename)
|
||||
block += "\n```\n"
|
||||
prompt_blocks.append(block)
|
||||
prompt = "\n---\n".join(prompt_blocks)
|
||||
|
||||
rsp = await self.llm.aask(
|
||||
msg=prompt,
|
||||
system_msgs=[
|
||||
"You are a python code to Mermaid Sequence Diagram translator in function detail.",
|
||||
"Translate the given markdown text to a Mermaid Sequence Diagram.",
|
||||
"Return the merged Mermaid sequence diagram in a markdown code block format.",
|
||||
],
|
||||
)
|
||||
sequence_view = rsp.removeprefix("```mermaid").removesuffix("```")
|
||||
await self.graph_db.insert(
|
||||
subject=entry.subject, predicate=GraphKeyword.HAS_SEQUENCE_VIEW, object_=sequence_view
|
||||
)
|
||||
for c in classes:
|
||||
await self.graph_db.insert(subject=entry.subject, predicate=GraphKeyword.HAS_PARTICIPANT, object_=c.subject)
|
||||
|
||||
async def _merge_sequence_view(self, entry) -> bool:
|
||||
new_participant = await self._search_new_participant(entry)
|
||||
if not new_participant:
|
||||
return False
|
||||
|
||||
await self._merge_participant(entry, new_participant)
|
||||
return True
|
||||
|
||||
async def _search_main_entry(self) -> List:
|
||||
rows = await self.graph_db.select(predicate=GraphKeyword.HAS_PAGE_INFO)
|
||||
tag = "__name__:__main__"
|
||||
|
|
@ -71,6 +142,11 @@ class RebuildSequenceView(Action):
|
|||
entries.append(r)
|
||||
return entries
|
||||
|
||||
@retry(
|
||||
wait=wait_random_exponential(min=1, max=20),
|
||||
stop=stop_after_attempt(6),
|
||||
after=general_after_log(logger),
|
||||
)
|
||||
async def _rebuild_use_case(self, ns_class_name):
|
||||
rows = await self.graph_db.select(subject=ns_class_name, predicate=GraphKeyword.HAS_CLASS_USE_CASE)
|
||||
if rows:
|
||||
|
|
@ -106,13 +182,13 @@ class RebuildSequenceView(Action):
|
|||
"You are a python code to UML 2.0 Use Case translator.",
|
||||
'The generated UML 2.0 Use Case must include the roles or entities listed in "Participants".',
|
||||
'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".',
|
||||
#'Only steps that involve input, output, and interactive operations with the external system at the same time can be considered as independent use cases.',
|
||||
"Only steps that involve input, output, and interactive operations with the external system at the same time can be considered as independent use cases, steps that do not meet any one condition should be incorporated into other use cases.",
|
||||
# 'Only steps that involve input, output, and interactive operations with the external system at the same time can be considered as independent use cases.',
|
||||
# "Only steps that involve input, output, and interactive operations with the external system at the same time can be considered as independent use cases, steps that do not meet any one condition should be incorporated into other use cases.",
|
||||
'The section under `if __name__ == "__main__":` of "Source Code" contains information about external system interactions with the internal system.',
|
||||
"Return a markdown JSON object with:\n"
|
||||
'- a "description" key to explain what the whole source code want to do;\n'
|
||||
'- a "use_cases" key list all use cases, each use case in the list should including a `description` key describes about what the use case to do, a `inputs` key lists the input names of the use case from external sources, a `outputs` key lists the output names of the use case to external sources, a `actors` key lists the participant actors of the use case, a `steps` key lists the steps about how the use case works step by step, a `reason` key explaining under what circumstances would the external system execute this use case.\n'
|
||||
'- a "relationship" key lists the descriptions of relationship among these use cases.\n',
|
||||
'- a "relationship" key lists all the descriptions of relationship among these use cases.\n',
|
||||
],
|
||||
)
|
||||
|
||||
|
|
@ -123,71 +199,75 @@ class RebuildSequenceView(Action):
|
|||
subject=ns_class_name, predicate=GraphKeyword.HAS_CLASS_USE_CASE, object_=detail.model_dump_json()
|
||||
)
|
||||
|
||||
async def _rebuild_main_sequence_view(self, entry):
|
||||
filename = entry.subject.split(":", 1)[0]
|
||||
rows = await self.graph_db.select(predicate=GraphKeyword.IS, object_=GraphKeyword.CLASS)
|
||||
classes = []
|
||||
prefix = filename + ":"
|
||||
for r in rows:
|
||||
if prefix in r.subject:
|
||||
classes.append(r)
|
||||
await self._rebuild_use_case(r.subject)
|
||||
participants = set()
|
||||
class_details = []
|
||||
class_views = []
|
||||
for c in classes:
|
||||
detail = await self._get_class_detail(c.subject)
|
||||
if not detail:
|
||||
continue
|
||||
class_details.append(detail)
|
||||
participants.update(set(detail.compositions))
|
||||
participants.update(set(detail.aggregations))
|
||||
view = await self._get_uml_class_view(c.subject)
|
||||
if view:
|
||||
class_views.append(view)
|
||||
@retry(
|
||||
wait=wait_random_exponential(min=1, max=20),
|
||||
stop=stop_after_attempt(6),
|
||||
after=general_after_log(logger),
|
||||
)
|
||||
async def _rebuild_sequence_view(self, ns_class_name):
|
||||
await self._rebuild_use_case(ns_class_name)
|
||||
|
||||
use_case_blocks = []
|
||||
for c in classes:
|
||||
use_cases = await self._get_class_use_cases(c.subject)
|
||||
use_case_blocks.extend(use_cases)
|
||||
prompt_blocks = ["\n".join(use_case_blocks)]
|
||||
block = "## Participants\n"
|
||||
for p in participants:
|
||||
block += f"- {p}\n"
|
||||
prompt_blocks.append(block)
|
||||
prompts_blocks = []
|
||||
use_case_markdown = await self._get_class_use_cases(ns_class_name)
|
||||
if not use_case_markdown: # external class
|
||||
await self.graph_db.insert(subject=ns_class_name, predicate=GraphKeyword.HAS_SEQUENCE_VIEW, object_="")
|
||||
return
|
||||
block = f"## Use Cases\n{use_case_markdown}"
|
||||
prompts_blocks.append(block)
|
||||
|
||||
participants = await self._get_participants(ns_class_name)
|
||||
block = "## Participants\n" + "\n".join([f"- {s}" for s in participants])
|
||||
prompts_blocks.append(block)
|
||||
|
||||
view = await self._get_uml_class_view(ns_class_name)
|
||||
block = "## Mermaid Class Views\n```mermaid\n"
|
||||
block += "\n\n".join([c.get_mermaid() for c in class_views])
|
||||
block += view.get_mermaid()
|
||||
block += "\n```\n"
|
||||
prompt_blocks.append(block)
|
||||
prompts_blocks.append(block)
|
||||
|
||||
block = "## Source Code\n```python\n"
|
||||
block += await self._get_source_code(filename)
|
||||
block += await self._get_source_code(ns_class_name)
|
||||
block += "\n```\n"
|
||||
prompt_blocks.append(block)
|
||||
prompt = "\n---\n".join(prompt_blocks)
|
||||
prompts_blocks.append(block)
|
||||
prompt = "\n---\n".join(prompts_blocks)
|
||||
|
||||
sequence_view = await self.llm.aask(
|
||||
msg=prompt, system_msgs=["You are a python code to Mermaid Sequence Diagram translator in function detail"]
|
||||
rsp = await self.llm.aask(
|
||||
prompt,
|
||||
system_msgs=[
|
||||
"You are a Mermaid Sequence Diagram translator in function detail.",
|
||||
"Translate the markdown text to a Mermaid Sequence Diagram.",
|
||||
"Return a markdown mermaid code block.",
|
||||
],
|
||||
)
|
||||
|
||||
sequence_view = rsp.removeprefix("```mermaid").removesuffix("```")
|
||||
await self.graph_db.insert(
|
||||
subject=entry.subject, predicate=GraphKeyword.HAS_SEQUENCE_VIEW, object_=sequence_view
|
||||
subject=ns_class_name, predicate=GraphKeyword.HAS_SEQUENCE_VIEW, object_=sequence_view
|
||||
)
|
||||
for c in classes:
|
||||
await self.graph_db.insert(subject=entry.subject, predicate=GraphKeyword.HAS_PARTICIPANT, object_=c.subject)
|
||||
return sequence_view
|
||||
|
||||
async def _get_class_use_cases(self, ns_class_name) -> List[str]:
|
||||
async def _get_participants(self, ns_class_name) -> List[str]:
|
||||
participants = set()
|
||||
detail = await self._get_class_detail(ns_class_name)
|
||||
if not detail:
|
||||
return []
|
||||
participants.update(set(detail.compositions))
|
||||
participants.update(set(detail.aggregations))
|
||||
return list(participants)
|
||||
|
||||
async def _get_class_use_cases(self, ns_class_name) -> str:
|
||||
block = ""
|
||||
rows = await self.graph_db.select(subject=ns_class_name, predicate=GraphKeyword.HAS_CLASS_USE_CASE)
|
||||
use_cases = []
|
||||
for r in rows:
|
||||
for i, r in enumerate(rows):
|
||||
detail = SQVUseCaseDetails.model_validate_json(r.object_)
|
||||
for i in detail.use_cases:
|
||||
md = f"## Use Cases: {i.description}\n"
|
||||
md += "### Inputs:\n" + "".join([f"- {i}\n" for i in i.inputs])
|
||||
md += "### Outputs:\n" + "".join([f"- {i}\n" for i in i.outputs])
|
||||
md += "### Actors:\n" + "".join([f"- {i}\n" for i in i.actors])
|
||||
md += "### Steps:\n" + "".join([f"- {i}\n" for i in i.steps])
|
||||
use_cases.append(md)
|
||||
return use_cases
|
||||
block += f"\n### {i + 1}. {detail.description}"
|
||||
for j, use_case in enumerate(detail.use_cases):
|
||||
block += f"\n#### {i + 1}.{j + 1}. {use_case.description}\n"
|
||||
block += "\n##### Inputs\n" + "\n".join([f"- {s}" for s in use_case.inputs])
|
||||
block += "\n##### Outputs\n" + "\n".join([f"- {s}" for s in use_case.outputs])
|
||||
block += "\n##### Actors\n" + "\n".join([f"- {s}" for s in use_case.actors])
|
||||
block += "\n##### Steps\n" + "\n".join([f"- {s}" for s in use_case.steps])
|
||||
block += "\n#### Use Case Relationship\n" + "\n".join([f"- {s}" for s in detail.relationship])
|
||||
return block + "\n"
|
||||
|
||||
async def _get_class_detail(self, ns_class_name) -> DotClassInfo | None:
|
||||
rows = await self.graph_db.select(subject=ns_class_name, predicate=GraphKeyword.HAS_DETAIL)
|
||||
|
|
@ -216,19 +296,6 @@ class RebuildSequenceView(Action):
|
|||
filename=filename, lineno=code_block_info.lineno, end_lineno=code_block_info.end_lineno
|
||||
)
|
||||
|
||||
@retry(
|
||||
wait=wait_random_exponential(min=1, max=20),
|
||||
stop=stop_after_attempt(6),
|
||||
after=general_after_log(logger),
|
||||
)
|
||||
async def _merge_sequence_view(self, entry) -> bool:
|
||||
new_participant = await self._search_new_participant(entry)
|
||||
if not new_participant:
|
||||
return False
|
||||
|
||||
await self._merge_participant(entry, new_participant)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _get_full_filename(root: str | Path, pathname: str | Path) -> Path | None:
|
||||
files = list_files(root=root)
|
||||
|
|
@ -261,6 +328,11 @@ class RebuildSequenceView(Action):
|
|||
return p
|
||||
return None
|
||||
|
||||
@retry(
|
||||
wait=wait_random_exponential(min=1, max=20),
|
||||
stop=stop_after_attempt(6),
|
||||
after=general_after_log(logger),
|
||||
)
|
||||
async def _merge_participant(self, entry: SPO, class_name: str):
|
||||
rows = await self.graph_db.select(predicate=GraphKeyword.IS, object_=GraphKeyword.CLASS)
|
||||
participants = []
|
||||
|
|
@ -268,7 +340,7 @@ class RebuildSequenceView(Action):
|
|||
_, name = split_namespace(r.subject)
|
||||
if name == class_name:
|
||||
participants.append(r)
|
||||
if len(participants) == 0:
|
||||
if len(participants) == 0: # external participants
|
||||
await self.graph_db.insert(
|
||||
subject=entry.subject, predicate=GraphKeyword.HAS_PARTICIPANT, object_=concat_namespace("?", class_name)
|
||||
)
|
||||
|
|
@ -281,33 +353,28 @@ class RebuildSequenceView(Action):
|
|||
return
|
||||
|
||||
participant = participants[0]
|
||||
await self._rebuild_use_case(participant.subject)
|
||||
|
||||
participants = set()
|
||||
detail = await self._get_class_detail(participant.subject)
|
||||
if not detail:
|
||||
await self._rebuild_sequence_view(participant.subject)
|
||||
sequence_views = await self.graph_db.select(
|
||||
subject=participant.subject, predicate=GraphKeyword.HAS_SEQUENCE_VIEW
|
||||
)
|
||||
if not sequence_views: # external class
|
||||
return
|
||||
participants.update(set(detail.compositions))
|
||||
participants.update(set(detail.aggregations))
|
||||
view = await self._get_uml_class_view(participant.subject)
|
||||
use_cases = await self._get_class_use_cases(participant.subject)
|
||||
prompt_blocks = ["\n".join(use_cases)]
|
||||
block = "## Participants\n"
|
||||
for p in participants:
|
||||
block += f"- {p}\n"
|
||||
prompt_blocks.append(block)
|
||||
block = "## Mermaid Class Views\n```mermaid\n"
|
||||
block += view.get_mermaid()
|
||||
block += "\n```\n"
|
||||
prompt_blocks.append(block)
|
||||
block = "## Source Code\n```python\n"
|
||||
block += await self._get_source_code(participant.subject)
|
||||
block += "\n```\n"
|
||||
prompt_blocks.append(block)
|
||||
block = "## Legacy Sequence View\n"
|
||||
rows = await self.graph_db.select(subject=entry.subject, predicate=GraphKeyword.HAS_SEQUENCE_VIEW)
|
||||
block += rows[0].object_
|
||||
prompt_blocks.append(block)
|
||||
prompt = "\n---\n".join(prompt_blocks)
|
||||
prompt = f"```mermaid\n{sequence_views[0].object_}\n```\n---\n```mermaid\n{rows[0].object_}\n```"
|
||||
|
||||
self.llm.aask(prompt, system_msgs=["You are a tool to cooperator"])
|
||||
rsp = await self.llm.aask(
|
||||
prompt,
|
||||
system_msgs=[
|
||||
"You are a tool to merge sequence diagrams into one.",
|
||||
"Participants with the same name are considered identical.",
|
||||
"Return the merged Mermaid sequence diagram in a markdown code block format.",
|
||||
],
|
||||
)
|
||||
|
||||
sequence_view = rsp.removeprefix("```mermaid").removesuffix("```")
|
||||
await self.graph_db.insert(
|
||||
subject=entry.subject, predicate=GraphKeyword.HAS_SEQUENCE_VIEW, object_=sequence_view
|
||||
)
|
||||
await self.graph_db.insert(
|
||||
subject=entry.subject, predicate=GraphKeyword.HAS_PARTICIPANT, object_=participant.subject
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue