Skip to content

Commit

Permalink
feat: +SummarizeCode, refactor project_name
Browse files Browse the repository at this point in the history
  • Loading branch information
莘权 马 committed Dec 8, 2023
1 parent 838b3cf commit 9d84c8f
Show file tree
Hide file tree
Showing 31 changed files with 672 additions and 246 deletions.
65 changes: 18 additions & 47 deletions metagpt/actions/design_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -23,7 +24,6 @@
)
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
Expand All @@ -43,7 +43,7 @@
## 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
Expand All @@ -58,15 +58,15 @@
""",
"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
',
Expand All @@ -77,7 +77,7 @@ class Game{
G->>M: end game
',
"Anything UNCLEAR": "The requirement is clear to me."
}
}}
[/CONTENT]
""",
},
Expand All @@ -96,7 +96,7 @@ class Game{
## 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
Expand All @@ -112,9 +112,9 @@ class Game{
## Implementation approach
We will ...
## project_name
## Project name
```python
"snake_game"
"{project_name}"
```
## File list
Expand Down Expand Up @@ -151,7 +151,7 @@ class Game{

OUTPUT_MAPPING = {
"Implementation approach": (str, ...),
"project_name": (str, ...),
"Project name": (str, ...),
"File list": (List[str], ...),
"Data structures and interfaces": (str, ...),
"Program call flow": (str, ...),
Expand All @@ -173,7 +173,7 @@ class Game{
## 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
Expand Down Expand Up @@ -229,50 +229,21 @@ async def run(self, with_messages, format=CONFIG.prompt_format):

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)
Expand All @@ -296,10 +267,10 @@ async def _update_system_design(self, filename, prds_file_repo, system_design_fi
@staticmethod
async def _save_data_api_design(design_doc):
m = json.loads(design_doc.content)
data_api_design = m.get("Data structures and interface definitions")
data_api_design = m.get("Data structures and interfaces")
if not data_api_design:
return
pathname = CONFIG.git_repo.workdir / Path(DATA_API_DESIGN_FILE_REPO) / Path(design_doc.filename).with_suffix("")
pathname = CONFIG.git_repo.workdir / DATA_API_DESIGN_FILE_REPO / Path(design_doc.filename).with_suffix("")
await WriteDesign._save_mermaid_file(data_api_design, pathname)
logger.info(f"Save class view to {str(pathname)}")

Expand Down
7 changes: 5 additions & 2 deletions metagpt/actions/prepare_documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
@Time : 2023/11/20
@Author : mashenquan
@File : git_repository.py
@File : prepare_documents.py
@Desc: PrepareDocuments Action: initialize project folder and add new requirements to docs/requirements.txt.
RFC 135 2.2.3.5.1.
"""
Expand All @@ -26,7 +26,10 @@ async def run(self, with_messages, **kwargs):
if not CONFIG.git_repo:
# Create and initialize the workspace folder, initialize the Git environment.
project_name = CONFIG.project_name or FileRepository.new_filename()
workdir = Path(CONFIG.project_path or DEFAULT_WORKSPACE_ROOT / project_name)
workdir = CONFIG.project_path
if not workdir and CONFIG.workspace:
workdir = Path(CONFIG.workspace) / project_name
workdir = Path(workdir or DEFAULT_WORKSPACE_ROOT / project_name)
if not CONFIG.inc and workdir.exists():
shutil.rmtree(workdir)
CONFIG.git_repo = GitRepository()
Expand Down
10 changes: 8 additions & 2 deletions metagpt/actions/project_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@
## 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.
Expand All @@ -201,7 +205,7 @@
## 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
"""

Expand Down Expand Up @@ -264,7 +268,9 @@ async def _run_new_tasks(self, context, format=CONFIG.prompt_format):
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
Expand Down
9 changes: 7 additions & 2 deletions metagpt/actions/summarize_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
"""
@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

from metagpt.actions.action import Action
from metagpt.config import CONFIG
from metagpt.const import SYSTEM_DESIGN_FILE_REPO, TASK_FILE_REPO
from metagpt.logs import logger
from metagpt.utils.file_repository import FileRepository

Expand Down Expand Up @@ -95,8 +98,10 @@ async def summarize_code(self, prompt):
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=SYSTEM_DESIGN_FILE_REPO)
task_pathname = Path(self.context.task_filename)
task_doc = await FileRepository.get_file(filename=task_pathname.name, relative_path=TASK_FILE_REPO)
src_file_repo = CONFIG.git_repo.new_file_repository(relative_path=CONFIG.src_workspace)
code_blocks = []
for filename in self.context.codes_filenames:
Expand Down
20 changes: 15 additions & 5 deletions metagpt/actions/write_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
RunCodeResult to standardize and unify parameter passing between WriteCode, RunCode, and DebugError.
"""


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.config import CONFIG
from metagpt.const import CODE_SUMMARIES_FILE_REPO, TEST_OUTPUTS_FILE_REPO
from metagpt.logs import logger
from metagpt.schema import CodingContext, RunCodeResult
from metagpt.schema import CodingContext, Document, RunCodeResult
from metagpt.utils.common import CodeParser
from metagpt.utils.file_repository import FileRepository

Expand Down Expand Up @@ -50,6 +50,8 @@
# Debug logs
```text
{logs}
{summary_log}
```
-----
Expand Down Expand Up @@ -90,18 +92,26 @@ async def run(self, *args, **kwargs) -> CodingContext:
test_doc = await FileRepository.get_file(
filename="test_" + coding_context.filename + ".json", relative_path=TEST_OUTPUTS_FILE_REPO
)
summary_doc = None
if coding_context.design_doc.filename:
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)
logs = test_detail.stderr
prompt = PROMPT_TEMPLATE.format(
design=coding_context.design_doc.content,
tasks=coding_context.task_doc.content,
code=coding_context.code_doc.content,
tasks=coding_context.task_doc.content if coding_context.task_doc else "",
code=coding_context.code_doc.content if coding_context.code_doc else "",
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)
if not coding_context.code_doc:
coding_context.code_doc = Document(filename=coding_context.filename, root_path=CONFIG.src_workspace)
coding_context.code_doc.content = code
return coding_context
3 changes: 2 additions & 1 deletion metagpt/actions/write_code_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,11 @@ async def run(self, *args, **kwargs) -> CodingContext:
k = CONFIG.code_review_k_times or 1
for i in range(k):
format_example = FORMAT_EXAMPLE.format(filename=self.context.code_doc.filename)
task_content = self.context.task_doc.content if self.context.task_doc else ""
context = "\n----------\n".join(
[
"```text\n" + self.context.design_doc.content + "```\n",
"```text\n" + self.context.task_doc.content + "```\n",
"```text\n" + task_content + "```\n",
"```python\n" + self.context.code_doc.content + "```\n",
]
)
Expand Down
Loading

0 comments on commit 9d84c8f

Please sign in to comment.