feat: +SummarizeCode, refactor project_name

This commit is contained in:
莘权 马 2023-12-04 23:04:07 +08:00
parent 838b3cfcc8
commit f7fd3e4ab8
12 changed files with 219 additions and 98 deletions

View file

@ -7,6 +7,7 @@
@Modified By: mashenquan, 2023/11/27.
1. According to Section 2.2.3.1 of RFC 135, replace file data in the message with the file name.
2. According to the design in Section 2.2.3.5.3 of RFC 135, add incremental iteration functionality.
@Modified By: mashenquan, 2023/12/5. Move the generation logic of the project name to WritePRD.
"""
import json
from pathlib import Path
@ -43,7 +44,7 @@ Requirement: Fill in the following missing information based on the context, eac
## Implementation approach: Provide as Plain text. Analyze the difficult points of the requirements, select appropriate open-source frameworks.
## project_name: Provide as Plain text, concise and clear, characters only use a combination of all lowercase and underscores
## project_name: Constant text.
## File list: Provided as Python list[str], the list of files needed (including HTML & CSS IF NEEDED) to write the program. Only need relative paths. ALWAYS write a main.py or app.py here
@ -58,15 +59,15 @@ and only output the json inside this tag, nothing else
""",
"FORMAT_EXAMPLE": """
[CONTENT]
{
{{
"Implementation approach": "We will ...",
"project_name": "snake_game",
"project_name": "{project_name}",
"File list": ["main.py"],
"Data structures and interfaces": '
classDiagram
class Game{
class Game{{
+int score
}
}}
...
Game "1" -- "1" Food: has
',
@ -77,7 +78,7 @@ and only output the json inside this tag, nothing else
G->>M: end game
',
"Anything UNCLEAR": "The requirement is clear to me."
}
}}
[/CONTENT]
""",
},
@ -96,7 +97,7 @@ ATTENTION: Output carefully referenced "Format example" in format.
## Implementation approach: Provide as Plain text. Analyze the difficult points of the requirements, select the appropriate open-source framework.
## project_name: Provide as Plain text, concise and clear, characters only use a combination of all lowercase and underscores
## project_name: Constant text.
## File list: Provided as Python list[str], the list of code files (including HTML & CSS IF NEEDED) to write the program. Only need relative paths. ALWAYS write a main.py or app.py here
@ -114,7 +115,7 @@ We will ...
## project_name
```python
"snake_game"
"{project_name}"
```
## File list
@ -173,7 +174,7 @@ ATTENTION: Output carefully referenced "Old Design" in format.
## Implementation approach: Provide as Plain text. Analyze the difficult points of the requirements, select the appropriate open-source framework.
## project_name: Provide as Plain text, concise and clear, characters only use a combination of all lowercase and underscores
## project_name: Constant text "{project_name}".
## File list: Provided as Python list[str], the list of code files (including HTML & CSS IF NEEDED) to write the program. Only need relative paths. ALWAYS write a main.py or app.py here
@ -229,50 +230,20 @@ class WriteDesign(Action):
async def _new_system_design(self, context, format=CONFIG.prompt_format):
prompt_template, format_example = get_template(templates, format)
format_example = format_example.format(project_name=CONFIG.project_name)
prompt = prompt_template.format(context=context, format_example=format_example)
system_design = await self._aask_v1(prompt, "system_design", OUTPUT_MAPPING, format=format)
self._rename_project_name(system_design=system_design)
await self._rename_workspace(system_design)
return system_design
async def _merge(self, prd_doc, system_design_doc, format=CONFIG.prompt_format):
prompt = MERGE_PROMPT.format(old_design=system_design_doc.content, context=prd_doc.content)
prompt = MERGE_PROMPT.format(old_design=system_design_doc.content, context=prd_doc.content,
project_name=CONFIG.project_name)
system_design = await self._aask_v1(prompt, "system_design", OUTPUT_MAPPING, format=format)
# fix Python package name, we can't system_design.instruct_content.python_package_name = "xxx" since "Python
# package name" contain space, have to use setattr
self._rename_project_name(system_design=system_design)
system_design_doc.content = system_design.instruct_content.json(ensure_ascii=False)
return system_design_doc
@staticmethod
def _rename_project_name(system_design):
# fix project_name, we can't system_design.instruct_content.python_package_name = "xxx" since "project_name"
# contain space, have to use setattr
if CONFIG.project_name:
setattr(
system_design.instruct_content,
"project_name",
CONFIG.project_name,
)
return
setattr(
system_design.instruct_content,
"project_name",
system_design.instruct_content.dict()["project_name"].strip().strip("'").strip('"'),
)
@staticmethod
async def _rename_workspace(system_design):
if CONFIG.project_path: # Updating on the old version has already been specified if it's valid. According to
# Section 2.2.3.10 of RFC 135
return
if isinstance(system_design, ActionOutput):
ws_name = system_design.instruct_content.dict()["project_name"]
else:
ws_name = CodeParser.parse_str(block="project_name", text=system_design)
CONFIG.git_repo.rename_root(ws_name)
async def _update_system_design(self, filename, prds_file_repo, system_design_file_repo) -> Document:
prd = await prds_file_repo.get(filename)
old_system_design_doc = await system_design_file_repo.get(filename)

View file

@ -183,6 +183,10 @@ MERGE_PROMPT = """
## Old Tasks
{old_tasks}
-----
## Format example
{format_example}
-----
Role: You are a project manager; The goal is to merge the new PRD/technical design content from 'Context' into 'Old Tasks.' Based on this merged result, break down tasks, give a task list, and analyze task dependencies to start with the prerequisite modules.
Requirements: Based on the context, fill in the following missing information, each section name is a key in json. Here the granularity of the task is a file, if there are any missing files, you can supplement them
Attention: Use '##' to split sections, not '#', and '## <SECTION_NAME>' SHOULD WRITE BEFORE the code and triple quote.
@ -201,7 +205,7 @@ Attention: Use '##' to split sections, not '#', and '## <SECTION_NAME>' SHOULD W
## Anything UNCLEAR: Provide as Plain text. Make clear here. For example, don't forget a main entry. don't forget to init 3rd party libs.
output a properly formatted JSON, wrapped inside [CONTENT][/CONTENT] like "Old Tasks" format,
output a properly formatted JSON, wrapped inside [CONTENT][/CONTENT] like "Format example" format,
and only output the json inside this tag, nothing else
"""
@ -264,7 +268,9 @@ class WriteTasks(Action):
return rsp
async def _merge(self, system_design_doc, task_doc, format=CONFIG.prompt_format) -> Document:
prompt = MERGE_PROMPT.format(context=system_design_doc.content, old_tasks=task_doc.content)
_, format_example = get_template(templates, format)
prompt = MERGE_PROMPT.format(context=system_design_doc.content, old_tasks=task_doc.content,
format_example=format_example)
rsp = await self._aask_v1(prompt, "task", OUTPUT_MAPPING, format=format)
task_doc.content = rsp.instruct_content.json(ensure_ascii=False)
return task_doc

View file

@ -3,7 +3,9 @@
"""
@Author : alexanderwu
@File : summarize_code.py
@Modified By: mashenquan, 2023/12/5. Archive the summarization content of issue discovery for use in WriteCode.
"""
from pathlib import Path
from tenacity import retry, stop_after_attempt, wait_fixed
@ -95,8 +97,10 @@ class SummarizeCode(Action):
return code_rsp
async def run(self):
design_doc = await FileRepository.get_file(self.context.design_filename)
task_doc = await FileRepository.get_file(self.context.task_filename)
design_pathname = Path(self.context.design_filename)
design_doc = await FileRepository.get_file(filename=design_pathname.name, relative_path=design_pathname.parent)
task_pathname = Path(self.context.task_filename)
task_doc = await FileRepository.get_file(filename=task_pathname.name, relative_path=task_pathname.parent)
src_file_repo = CONFIG.git_repo.new_file_repository(relative_path=CONFIG.src_workspace)
code_blocks = []
for filename in self.context.codes_filenames:

View file

@ -19,7 +19,7 @@
from tenacity import retry, stop_after_attempt, wait_fixed
from metagpt.actions.action import Action
from metagpt.const import TEST_OUTPUTS_FILE_REPO
from metagpt.const import TEST_OUTPUTS_FILE_REPO, CODE_SUMMARIES_FILE_REPO
from metagpt.logs import logger
from metagpt.schema import CodingContext, RunCodeResult
from metagpt.utils.common import CodeParser
@ -50,6 +50,8 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenc
# Debug logs
```text
{logs}
{summary_log}
```
-----
@ -90,6 +92,8 @@ class WriteCode(Action):
test_doc = await FileRepository.get_file(
filename="test_" + coding_context.filename + ".json", relative_path=TEST_OUTPUTS_FILE_REPO
)
summary_doc = await FileRepository.get_file(filename=coding_context.design_doc.filename,
relative_path=CODE_SUMMARIES_FILE_REPO)
logs = ""
if test_doc:
test_detail = RunCodeResult.loads(test_doc.content)
@ -100,6 +104,7 @@ class WriteCode(Action):
code=coding_context.code_doc.content,
logs=logs,
filename=self.context.filename,
summary_log=summary_doc.content if summary_doc else ""
)
logger.info(f"Writing {coding_context.filename}..")
code = await self.write_code(prompt)

View file

@ -8,6 +8,7 @@
1. According to Section 2.2.3.1 of RFC 135, replace file data in the message with the file name.
2. According to the design in Section 2.2.3.5.2 of RFC 135, add incremental iteration functionality.
3. Move the document storage operations related to WritePRD from the save operation of WriteDesign.
@Modified By: mashenquan, 2023/12/5. Move the generation logic of the project name to WritePRD.
"""
from __future__ import annotations
@ -27,6 +28,7 @@ from metagpt.const import (
)
from metagpt.logs import logger
from metagpt.schema import Document, Documents
from metagpt.utils.common import CodeParser
from metagpt.utils.file_repository import FileRepository
from metagpt.utils.get_template import get_template
from metagpt.utils.mermaid import mermaid_to_file
@ -53,7 +55,7 @@ ATTENTION: Output carefully referenced "Format example" in format.
{{
"Language": "", # str, use the same language as the user requirement. en_us / zh_cn etc.
"Original Requirements": "", # str, place the polished complete original requirements here
"project_name": "", # str, name it like game_2048 / web_2048 / simple_crm etc.
"project_name": "{project_name}", # str, if it's empty, name it with snake case style, like game_2048 / web_2048 / simple_crm etc.
"Search Information": "",
"Requirements": "",
"Product Goals": [], # Provided as Python list[str], up to 3 clear, orthogonal product goals.
@ -85,9 +87,10 @@ and only output the json inside this tag, nothing else
""",
"FORMAT_EXAMPLE": """
[CONTENT]
{
{{
"Language": "",
"Original Requirements": "",
"project_name": "{project_name}",
"Search Information": "",
"Requirements": "",
"Product Goals": [],
@ -111,7 +114,7 @@ and only output the json inside this tag, nothing else
"Requirement Pool": [["P0","P0 requirement"],["P1","P1 requirement"]],
"UI Design draft": "",
"Anything UNCLEAR": "",
}
}}
[/CONTENT]
""",
},
@ -228,6 +231,7 @@ There are no unclear points.
OUTPUT_MAPPING = {
"Language": (str, ...),
"Original Requirements": (str, ...),
"project_name": (str, ...),
"Product Goals": (List[str], ...),
"User Stories": (List[str], ...),
"Competitive Analysis": (List[str], ...),
@ -270,7 +274,7 @@ ATTENTION: Output carefully referenced "Old PRD" in format.
{{
"Language": "", # str, use the same language as the user requirement. en_us / zh_cn etc.
"Original Requirements": "", # str, place the polished complete original requirements here
"project_name": "", # str, name it like game_2048 / web_2048 / simple_crm etc.
"project_name": "{project_name}", # str, if it's empty, name it with snake case style, like game_2048 / web_2048 / simple_crm etc.
"Search Information": "",
"Requirements": "",
"Product Goals": [], # Provided as Python list[str], up to 3 clear, orthogonal product goals.
@ -343,14 +347,18 @@ class WritePRD(Action):
# logger.info(format)
prompt_template, format_example = get_template(templates, format)
project_name = CONFIG.project_name if CONFIG.project_name else ""
format_example = format_example.format(project_name=project_name)
# logger.info(prompt_template)
# logger.info(format_example)
prompt = prompt_template.format(
requirements=requirements, search_information=info, format_example=format_example
requirements=requirements, search_information=info, format_example=format_example,
project_name=project_name
)
# logger.info(prompt)
# prd = await self._aask_v1(prompt, "prd", OUTPUT_MAPPING)
prd = await self._aask_v1(prompt, "prd", OUTPUT_MAPPING, format=format)
await self._rename_workspace(prd)
return prd
async def _is_relative_to(self, new_requirement_doc, old_prd_doc) -> bool:
@ -366,9 +374,13 @@ class WritePRD(Action):
return False
async def _merge(self, new_requirement_doc, prd_doc, format=CONFIG.prompt_format) -> Document:
prompt = MERGE_PROMPT.format(requirements=new_requirement_doc.content, old_prd=prd_doc.content)
if not CONFIG.project_name:
CONFIG.project_name = Path(CONFIG.project_path).name
prompt = MERGE_PROMPT.format(requirements=new_requirement_doc.content, old_prd=prd_doc.content,
project_name=CONFIG.project_name)
prd = await self._aask_v1(prompt, "prd", OUTPUT_MAPPING, format=format)
prd_doc.content = prd.instruct_content.json(ensure_ascii=False)
await self._rename_workspace(prd)
return prd_doc
async def _update_prd(self, requirement_doc, prd_doc, prds_file_repo, *args, **kwargs) -> Document | None:
@ -404,3 +416,19 @@ class WritePRD(Action):
@staticmethod
async def _save_pdf(prd_doc):
await FileRepository.save_as(doc=prd_doc, with_suffix=".md", relative_path=PRD_PDF_FILE_REPO)
@staticmethod
async def _rename_workspace(prd):
if CONFIG.project_path: # Updating on the old version has already been specified if it's valid. According to
# Section 2.2.3.10 of RFC 135
if not CONFIG.project_name:
CONFIG.project_name = Path(CONFIG.project_path).name
return
if not CONFIG.project_name:
if isinstance(prd, ActionOutput):
ws_name = prd.instruct_content.dict()["project_name"]
else:
ws_name = CodeParser.parse_str(block="project_name", text=prd)
CONFIG.project_name = ws_name
CONFIG.git_repo.rename_root(CONFIG.project_name)