mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-05-15 11:02:36 +02:00
feat: new/inc/patch pass
This commit is contained in:
parent
5c416a1f31
commit
ee0b9d2039
21 changed files with 512 additions and 237 deletions
|
|
@ -22,7 +22,6 @@ from metagpt.schema import (
|
|||
SerializationMixin,
|
||||
TestingContext,
|
||||
)
|
||||
from metagpt.utils.project_repo import ProjectRepo
|
||||
|
||||
|
||||
class Action(SerializationMixin, ContextMixin, BaseModel):
|
||||
|
|
@ -36,12 +35,6 @@ class Action(SerializationMixin, ContextMixin, BaseModel):
|
|||
desc: str = "" # for skill manager
|
||||
node: ActionNode = Field(default=None, exclude=True)
|
||||
|
||||
@property
|
||||
def repo(self) -> ProjectRepo:
|
||||
if not self.context.repo:
|
||||
self.context.repo = ProjectRepo(self.context.git_repo)
|
||||
return self.context.repo
|
||||
|
||||
@property
|
||||
def prompt_schema(self):
|
||||
return self.config.prompt_schema
|
||||
|
|
|
|||
|
|
@ -9,13 +9,15 @@
|
|||
2. According to Section 2.2.3.1 of RFC 135, replace file data in the message with the file name.
|
||||
"""
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from metagpt.actions.action import Action
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import RunCodeContext, RunCodeResult
|
||||
from metagpt.utils.common import CodeParser
|
||||
from metagpt.utils.project_repo import ProjectRepo
|
||||
|
||||
PROMPT_TEMPLATE = """
|
||||
NOTICE
|
||||
|
|
@ -47,6 +49,8 @@ Now you should start rewriting the code:
|
|||
|
||||
class DebugError(Action):
|
||||
i_context: RunCodeContext = Field(default_factory=RunCodeContext)
|
||||
repo: Optional[ProjectRepo] = Field(default=None, exclude=True)
|
||||
input_args: Optional[BaseModel] = Field(default=None, exclude=True)
|
||||
|
||||
async def run(self, *args, **kwargs) -> str:
|
||||
output_doc = await self.repo.test_outputs.get(filename=self.i_context.output_filename)
|
||||
|
|
@ -59,9 +63,7 @@ class DebugError(Action):
|
|||
return ""
|
||||
|
||||
logger.info(f"Debug and rewrite {self.i_context.test_filename}")
|
||||
code_doc = await self.repo.with_src_path(self.context.src_workspace).srcs.get(
|
||||
filename=self.i_context.code_filename
|
||||
)
|
||||
code_doc = await self.repo.srcs.get(filename=self.i_context.code_filename)
|
||||
if not code_doc:
|
||||
return ""
|
||||
test_doc = await self.repo.tests.get(filename=self.i_context.test_filename)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import json
|
|||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from metagpt.actions import Action
|
||||
from metagpt.actions.design_api_an import (
|
||||
DATA_STRUCTURES_AND_INTERFACES,
|
||||
|
|
@ -26,6 +28,7 @@ from metagpt.const import DATA_API_DESIGN_FILE_REPO, SEQ_FLOW_FILE_REPO
|
|||
from metagpt.logs import logger
|
||||
from metagpt.schema import AIMessage, Document, Documents, Message
|
||||
from metagpt.utils.mermaid import mermaid_to_file
|
||||
from metagpt.utils.project_repo import ProjectRepo
|
||||
from metagpt.utils.report import DocsReporter, GalleryReporter
|
||||
|
||||
NEW_REQ_TEMPLATE = """
|
||||
|
|
@ -45,21 +48,25 @@ class WriteDesign(Action):
|
|||
"data structures, library tables, processes, and paths. Please provide your design, feedback "
|
||||
"clearly and in detail."
|
||||
)
|
||||
repo: Optional[ProjectRepo] = Field(default=None, exclude=True)
|
||||
input_args: Optional[BaseModel] = Field(default=None, exclude=True)
|
||||
|
||||
async def run(self, with_messages: Message, schema: str = None):
|
||||
# Use `git status` to identify which PRD documents have been modified in the `docs/prd` directory.
|
||||
changed_prds = self.repo.docs.prd.changed_files
|
||||
# Use `git status` to identify which design documents in the `docs/system_designs` directory have undergone
|
||||
# changes.
|
||||
changed_system_designs = self.repo.docs.system_design.changed_files
|
||||
self.input_args = with_messages[0].instruct_content
|
||||
self.repo = ProjectRepo(self.input_args.project_path)
|
||||
changed_prds = self.input_args.changed_prd_filenames
|
||||
changed_system_designs = [
|
||||
str(self.repo.docs.system_design.workdir / i)
|
||||
for i in list(self.repo.docs.system_design.changed_files.keys())
|
||||
]
|
||||
|
||||
# For those PRDs and design documents that have undergone changes, regenerate the design content.
|
||||
changed_files = Documents()
|
||||
for filename in changed_prds.keys():
|
||||
for filename in changed_prds:
|
||||
doc = await self._update_system_design(filename=filename)
|
||||
changed_files.docs[filename] = doc
|
||||
|
||||
for filename in changed_system_designs.keys():
|
||||
for filename in changed_system_designs:
|
||||
if filename in changed_files.docs:
|
||||
continue
|
||||
doc = await self._update_system_design(filename=filename)
|
||||
|
|
@ -68,6 +75,11 @@ class WriteDesign(Action):
|
|||
logger.info("Nothing has changed.")
|
||||
# Wait until all files under `docs/system_designs/` are processed before sending the publish message,
|
||||
# leaving room for global optimization in subsequent steps.
|
||||
kvs = self.input_args.model_dump()
|
||||
kvs["changed_system_design_filenames"] = [
|
||||
str(self.repo.docs.system_design.workdir / i)
|
||||
for i in list(self.repo.docs.system_design.changed_files.keys())
|
||||
]
|
||||
return AIMessage(
|
||||
content="Designing is complete. "
|
||||
+ "\n".join(
|
||||
|
|
@ -75,6 +87,7 @@ class WriteDesign(Action):
|
|||
+ list(self.repo.resources.data_api_design.changed_files.keys())
|
||||
+ list(self.repo.resources.seq_flow.changed_files.keys())
|
||||
),
|
||||
instruct_content=AIMessage.create_instruct_value(kvs=kvs, class_name="WriteDesignOutput"),
|
||||
cause_by=self,
|
||||
)
|
||||
|
||||
|
|
@ -89,14 +102,15 @@ class WriteDesign(Action):
|
|||
return system_design_doc
|
||||
|
||||
async def _update_system_design(self, filename) -> Document:
|
||||
prd = await self.repo.docs.prd.get(filename)
|
||||
old_system_design_doc = await self.repo.docs.system_design.get(filename)
|
||||
root_relative_path = Path(filename).relative_to(self.repo.workdir)
|
||||
prd = await Document.load(filename=filename, project_path=self.repo.workdir)
|
||||
old_system_design_doc = await self.repo.docs.system_design.get(root_relative_path.name)
|
||||
async with DocsReporter(enable_llm_stream=True) as reporter:
|
||||
await reporter.async_report({"type": "design"}, "meta")
|
||||
if not old_system_design_doc:
|
||||
system_design = await self._new_system_design(context=prd.content)
|
||||
doc = await self.repo.docs.system_design.save(
|
||||
filename=filename,
|
||||
filename=prd.filename,
|
||||
content=system_design.instruct_content.model_dump_json(),
|
||||
dependencies={prd.root_relative_path},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from metagpt.logs import logger
|
|||
from metagpt.schema import AIMessage
|
||||
from metagpt.utils.common import any_to_str
|
||||
from metagpt.utils.file_repository import FileRepository
|
||||
from metagpt.utils.project_repo import ProjectRepo
|
||||
|
||||
|
||||
class PrepareDocuments(Action):
|
||||
|
|
@ -36,7 +37,7 @@ class PrepareDocuments(Action):
|
|||
def config(self):
|
||||
return self.context.config
|
||||
|
||||
def _init_repo(self):
|
||||
def _init_repo(self) -> ProjectRepo:
|
||||
"""Initialize the Git environment."""
|
||||
if not self.config.project_path:
|
||||
name = self.config.project_name or FileRepository.new_filename()
|
||||
|
|
@ -47,6 +48,7 @@ class PrepareDocuments(Action):
|
|||
shutil.rmtree(path)
|
||||
self.config.project_path = path
|
||||
self.context.set_repo_dir(path)
|
||||
return ProjectRepo(path)
|
||||
|
||||
async def run(self, with_messages, **kwargs):
|
||||
"""Create and initialize the workspace folder, initialize the Git environment."""
|
||||
|
|
@ -67,10 +69,22 @@ class PrepareDocuments(Action):
|
|||
max_auto_summarize_code=0,
|
||||
)
|
||||
|
||||
self._init_repo()
|
||||
repo = self._init_repo()
|
||||
|
||||
# Write the newly added requirements from the main parameter idea to `docs/requirement.txt`.
|
||||
doc = await self.repo.docs.save(filename=REQUIREMENT_FILENAME, content=with_messages[0].content)
|
||||
await repo.docs.save(filename=REQUIREMENT_FILENAME, content=with_messages[0].content)
|
||||
# Send a Message notification to the WritePRD action, instructing it to process requirements using
|
||||
# `docs/requirement.txt` and `docs/prd/`.
|
||||
return AIMessage(content="", instruct_content=doc, cause_by=self, send_to=self.send_to)
|
||||
return AIMessage(
|
||||
content="",
|
||||
instruct_content=AIMessage.create_instruct_value(
|
||||
kvs={
|
||||
"project_path": str(repo.workdir),
|
||||
"requirements_filename": str(repo.docs.workdir / REQUIREMENT_FILENAME),
|
||||
"prd_filenames": [str(repo.docs.prd.workdir / i) for i in repo.docs.prd.all_files],
|
||||
},
|
||||
class_name="PrepareDocumentsOutput",
|
||||
),
|
||||
cause_by=self,
|
||||
send_to=self.send_to,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,13 +11,17 @@
|
|||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from metagpt.actions.action import Action
|
||||
from metagpt.actions.project_management_an import PM_NODE, REFINED_PM_NODE
|
||||
from metagpt.const import PACKAGE_REQUIREMENTS_FILENAME
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import AIMessage, Document, Documents
|
||||
from metagpt.utils.project_repo import ProjectRepo
|
||||
from metagpt.utils.report import DocsReporter
|
||||
|
||||
NEW_REQ_TEMPLATE = """
|
||||
|
|
@ -32,10 +36,14 @@ NEW_REQ_TEMPLATE = """
|
|||
class WriteTasks(Action):
|
||||
name: str = "CreateTasks"
|
||||
i_context: Optional[str] = None
|
||||
repo: Optional[ProjectRepo] = Field(default=None, exclude=True)
|
||||
input_args: Optional[BaseModel] = Field(default=None, exclude=True)
|
||||
|
||||
async def run(self, with_messages):
|
||||
changed_system_designs = self.repo.docs.system_design.changed_files
|
||||
changed_tasks = self.repo.docs.task.changed_files
|
||||
self.input_args = with_messages[0].instruct_content
|
||||
self.repo = ProjectRepo(self.input_args.project_path)
|
||||
changed_system_designs = self.input_args.changed_system_design_filenames
|
||||
changed_tasks = [str(self.repo.docs.task.workdir / i) for i in list(self.repo.docs.task.changed_files.keys())]
|
||||
change_files = Documents()
|
||||
# Rewrite the system designs that have undergone changes based on the git head diff under
|
||||
# `docs/system_designs/`.
|
||||
|
|
@ -54,6 +62,11 @@ class WriteTasks(Action):
|
|||
logger.info("Nothing has changed.")
|
||||
# Wait until all files under `docs/tasks/` are processed before sending the publish_message, leaving room for
|
||||
# global optimization in subsequent steps.
|
||||
kvs = self.input_args.model_dump()
|
||||
kvs["changed_task_filenames"] = [
|
||||
str(self.repo.docs.task.workdir / i) for i in list(self.repo.docs.task.changed_files.keys())
|
||||
]
|
||||
kvs["python_package_dependency_filename"] = str(self.repo.workdir / PACKAGE_REQUIREMENTS_FILENAME)
|
||||
return AIMessage(
|
||||
content="WBS is completed. "
|
||||
+ "\n".join(
|
||||
|
|
@ -61,12 +74,14 @@ class WriteTasks(Action):
|
|||
+ list(self.repo.docs.task.changed_files.keys())
|
||||
+ list(self.repo.resources.api_spec_and_task.changed_files.keys())
|
||||
),
|
||||
instruct_content=AIMessage.create_instruct_value(kvs=kvs, class_name="WriteTaskOutput"),
|
||||
cause_by=self,
|
||||
)
|
||||
|
||||
async def _update_tasks(self, filename):
|
||||
system_design_doc = await self.repo.docs.system_design.get(filename)
|
||||
task_doc = await self.repo.docs.task.get(filename)
|
||||
root_relative_path = Path(filename).relative_to(self.repo.workdir)
|
||||
system_design_doc = await Document.load(filename=filename, project_path=self.repo.workdir)
|
||||
task_doc = await self.repo.docs.task.get(root_relative_path.name)
|
||||
async with DocsReporter(enable_llm_stream=True) as reporter:
|
||||
await reporter.async_report({"type": "task"}, "meta")
|
||||
if task_doc:
|
||||
|
|
@ -75,7 +90,7 @@ class WriteTasks(Action):
|
|||
else:
|
||||
rsp = await self._run_new_tasks(context=system_design_doc.content)
|
||||
task_doc = await self.repo.docs.task.save(
|
||||
filename=filename,
|
||||
filename=system_design_doc.filename,
|
||||
content=rsp.instruct_content.model_dump_json(),
|
||||
dependencies={system_design_doc.root_relative_path},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,13 +6,16 @@
|
|||
@Modified By: mashenquan, 2023/12/5. Archive the summarization content of issue discovery for use in WriteCode.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic import BaseModel, Field
|
||||
from tenacity import retry, stop_after_attempt, wait_random_exponential
|
||||
|
||||
from metagpt.actions.action import Action
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import CodeSummarizeContext
|
||||
from metagpt.utils.common import get_markdown_code_block_type
|
||||
from metagpt.utils.project_repo import ProjectRepo
|
||||
|
||||
PROMPT_TEMPLATE = """
|
||||
NOTICE
|
||||
|
|
@ -90,6 +93,8 @@ flowchart TB
|
|||
class SummarizeCode(Action):
|
||||
name: str = "SummarizeCode"
|
||||
i_context: CodeSummarizeContext = Field(default_factory=CodeSummarizeContext)
|
||||
repo: Optional[ProjectRepo] = Field(default=None, exclude=True)
|
||||
input_args: Optional[BaseModel] = Field(default=None, exclude=True)
|
||||
|
||||
@retry(stop=stop_after_attempt(2), wait=wait_random_exponential(min=1, max=60))
|
||||
async def summarize_code(self, prompt):
|
||||
|
|
@ -101,11 +106,10 @@ class SummarizeCode(Action):
|
|||
design_doc = await self.repo.docs.system_design.get(filename=design_pathname.name)
|
||||
task_pathname = Path(self.i_context.task_filename)
|
||||
task_doc = await self.repo.docs.task.get(filename=task_pathname.name)
|
||||
src_file_repo = self.repo.with_src_path(self.context.src_workspace).srcs
|
||||
code_blocks = []
|
||||
for filename in self.i_context.codes_filenames:
|
||||
code_doc = await src_file_repo.get(filename)
|
||||
code_block = f"```python\n{code_doc.content}\n```\n-----"
|
||||
code_doc = await self.repo.srcs.get(filename)
|
||||
code_block = f"```{get_markdown_code_block_type(filename)}\n{code_doc.content}\n```\n---\n"
|
||||
code_blocks.append(code_block)
|
||||
format_example = FORMAT_EXAMPLE
|
||||
prompt = PROMPT_TEMPLATE.format(
|
||||
|
|
|
|||
|
|
@ -16,17 +16,18 @@
|
|||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic import BaseModel, Field
|
||||
from tenacity import retry, stop_after_attempt, wait_random_exponential
|
||||
|
||||
from metagpt.actions.action import Action
|
||||
from metagpt.actions.project_management_an import REFINED_TASK_LIST, TASK_LIST
|
||||
from metagpt.actions.write_code_plan_and_change_an import REFINED_TEMPLATE
|
||||
from metagpt.const import BUGFIX_FILENAME, REQUIREMENT_FILENAME
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import CodingContext, Document, RunCodeResult
|
||||
from metagpt.utils.common import CodeParser
|
||||
from metagpt.utils.common import CodeParser, get_markdown_code_block_type
|
||||
from metagpt.utils.project_repo import ProjectRepo
|
||||
from metagpt.utils.report import EditorReporter
|
||||
|
||||
|
|
@ -44,9 +45,7 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenc
|
|||
{task}
|
||||
|
||||
## Legacy Code
|
||||
```Code
|
||||
{code}
|
||||
```
|
||||
|
||||
## Debug logs
|
||||
```text
|
||||
|
|
@ -61,14 +60,14 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenc
|
|||
```
|
||||
|
||||
# Format example
|
||||
## Code: {filename}
|
||||
## Code: {demo_filename}.py
|
||||
```python
|
||||
## {filename}
|
||||
## {demo_filename}.py
|
||||
...
|
||||
```
|
||||
## Code: {filename}
|
||||
## Code: {demo_filename}.js
|
||||
```javascript
|
||||
// {filename}
|
||||
// {demo_filename}.js
|
||||
...
|
||||
```
|
||||
|
||||
|
|
@ -89,6 +88,8 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenc
|
|||
class WriteCode(Action):
|
||||
name: str = "WriteCode"
|
||||
i_context: Document = Field(default_factory=Document)
|
||||
repo: Optional[ProjectRepo] = Field(default=None, exclude=True)
|
||||
input_args: Optional[BaseModel] = Field(default=None, exclude=True)
|
||||
|
||||
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
|
||||
async def write_code(self, prompt) -> str:
|
||||
|
|
@ -97,10 +98,16 @@ class WriteCode(Action):
|
|||
return code
|
||||
|
||||
async def run(self, *args, **kwargs) -> CodingContext:
|
||||
bug_feedback = await self.repo.docs.get(filename=BUGFIX_FILENAME)
|
||||
bug_feedback = None
|
||||
if self.input_args and hasattr(self.input_args, "issue_filename"):
|
||||
bug_feedback = await Document.load(self.input_args.issue_filename)
|
||||
coding_context = CodingContext.loads(self.i_context.content)
|
||||
if not coding_context.code_plan_and_change_doc:
|
||||
coding_context.code_plan_and_change_doc = await self.repo.docs.code_plan_and_change.get(
|
||||
filename=coding_context.task_doc.filename
|
||||
)
|
||||
test_doc = await self.repo.test_outputs.get(filename="test_" + coding_context.filename + ".json")
|
||||
requirement_doc = await self.repo.docs.get(filename=REQUIREMENT_FILENAME)
|
||||
requirement_doc = await Document.load(self.input_args.requirements_filename)
|
||||
summary_doc = None
|
||||
if coding_context.design_doc and coding_context.design_doc.filename:
|
||||
summary_doc = await self.repo.docs.code_summary.get(filename=coding_context.design_doc.filename)
|
||||
|
|
@ -109,29 +116,28 @@ class WriteCode(Action):
|
|||
test_detail = RunCodeResult.loads(test_doc.content)
|
||||
logs = test_detail.stderr
|
||||
|
||||
if bug_feedback:
|
||||
code_context = coding_context.code_doc.content
|
||||
elif self.config.inc:
|
||||
if self.config.inc or bug_feedback:
|
||||
code_context = await self.get_codes(
|
||||
coding_context.task_doc, exclude=self.i_context.filename, project_repo=self.repo, use_inc=True
|
||||
)
|
||||
else:
|
||||
code_context = await self.get_codes(
|
||||
coding_context.task_doc,
|
||||
exclude=self.i_context.filename,
|
||||
project_repo=self.repo.with_src_path(self.context.src_workspace),
|
||||
coding_context.task_doc, exclude=self.i_context.filename, project_repo=self.repo
|
||||
)
|
||||
|
||||
if self.config.inc:
|
||||
prompt = REFINED_TEMPLATE.format(
|
||||
user_requirement=requirement_doc.content if requirement_doc else "",
|
||||
code_plan_and_change=str(coding_context.code_plan_and_change_doc),
|
||||
code_plan_and_change=coding_context.code_plan_and_change_doc.content
|
||||
if coding_context.code_plan_and_change_doc
|
||||
else "",
|
||||
design=coding_context.design_doc.content if coding_context.design_doc else "",
|
||||
task=coding_context.task_doc.content if coding_context.task_doc else "",
|
||||
code=code_context,
|
||||
logs=logs,
|
||||
feedback=bug_feedback.content if bug_feedback else "",
|
||||
filename=self.i_context.filename,
|
||||
demo_filename=Path(self.i_context.filename).stem,
|
||||
summary_log=summary_doc.content if summary_doc else "",
|
||||
)
|
||||
else:
|
||||
|
|
@ -142,6 +148,7 @@ class WriteCode(Action):
|
|||
logs=logs,
|
||||
feedback=bug_feedback.content if bug_feedback else "",
|
||||
filename=self.i_context.filename,
|
||||
demo_filename=Path(self.i_context.filename).stem,
|
||||
summary_log=summary_doc.content if summary_doc else "",
|
||||
)
|
||||
logger.info(f"Writing {coding_context.filename}..")
|
||||
|
|
@ -150,8 +157,9 @@ class WriteCode(Action):
|
|||
code = await self.write_code(prompt)
|
||||
if not coding_context.code_doc:
|
||||
# avoid root_path pydantic ValidationError if use WriteCode alone
|
||||
root_path = self.context.src_workspace if self.context.src_workspace else ""
|
||||
coding_context.code_doc = Document(filename=coding_context.filename, root_path=str(root_path))
|
||||
coding_context.code_doc = Document(
|
||||
filename=coding_context.filename, root_path=str(self.repo.src_relative_path)
|
||||
)
|
||||
coding_context.code_doc.content = code
|
||||
await reporter.async_report(self.repo.workdir / coding_context.code_doc.root_relative_path, "path")
|
||||
return coding_context
|
||||
|
|
@ -178,35 +186,32 @@ class WriteCode(Action):
|
|||
code_filenames = m.get(TASK_LIST.key, []) if not use_inc else m.get(REFINED_TASK_LIST.key, [])
|
||||
codes = []
|
||||
src_file_repo = project_repo.srcs
|
||||
|
||||
# Incremental development scenario
|
||||
if use_inc:
|
||||
src_files = src_file_repo.all_files
|
||||
# Get the old workspace contained the old codes and old workspace are created in previous CodePlanAndChange
|
||||
old_file_repo = project_repo.git_repo.new_file_repository(relative_path=project_repo.old_workspace)
|
||||
old_files = old_file_repo.all_files
|
||||
# Get the union of the files in the src and old workspaces
|
||||
union_files_list = list(set(src_files) | set(old_files))
|
||||
for filename in union_files_list:
|
||||
for filename in src_file_repo.all_files:
|
||||
code_block_type = get_markdown_code_block_type(filename)
|
||||
# Exclude the current file from the all code snippets
|
||||
if filename == exclude:
|
||||
# If the file is in the old workspace, use the old code
|
||||
# Exclude unnecessary code to maintain a clean and focused main.py file, ensuring only relevant and
|
||||
# essential functionality is included for the project’s requirements
|
||||
if filename in old_files and filename != "main.py":
|
||||
if filename != "main.py":
|
||||
# Use old code
|
||||
doc = await old_file_repo.get(filename=filename)
|
||||
doc = await src_file_repo.get(filename=filename)
|
||||
# If the file is in the src workspace, skip it
|
||||
else:
|
||||
continue
|
||||
codes.insert(0, f"-----Now, {filename} to be rewritten\n```{doc.content}```\n=====")
|
||||
codes.insert(
|
||||
0, f"### The name of file to rewrite: `{filename}`\n```{code_block_type}\n{doc.content}```\n"
|
||||
)
|
||||
logger.info(f"Prepare to rewrite `{filename}`")
|
||||
# The code snippets are generated from the src workspace
|
||||
else:
|
||||
doc = await src_file_repo.get(filename=filename)
|
||||
# If the file does not exist in the src workspace, skip it
|
||||
if not doc:
|
||||
continue
|
||||
codes.append(f"----- {filename}\n```{doc.content}```")
|
||||
codes.append(f"### File Name: `{filename}`\n```{code_block_type}\n{doc.content}```\n\n")
|
||||
|
||||
# Normal scenario
|
||||
else:
|
||||
|
|
@ -217,6 +222,7 @@ class WriteCode(Action):
|
|||
doc = await src_file_repo.get(filename=filename)
|
||||
if not doc:
|
||||
continue
|
||||
codes.append(f"----- {filename}\n```{doc.content}```")
|
||||
code_block_type = get_markdown_code_block_type(filename)
|
||||
codes.append(f"### File Name: `{filename}`\n```{code_block_type}\n{doc.content}```\n\n")
|
||||
|
||||
return "\n".join(codes)
|
||||
|
|
|
|||
|
|
@ -5,15 +5,16 @@
|
|||
@Author : mannaandpoem
|
||||
@File : write_code_plan_and_change_an.py
|
||||
"""
|
||||
import os
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from metagpt.actions.action import Action
|
||||
from metagpt.actions.action_node import ActionNode
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import CodePlanAndChangeContext
|
||||
from metagpt.schema import CodePlanAndChangeContext, Document
|
||||
from metagpt.utils.common import get_markdown_code_block_type
|
||||
from metagpt.utils.project_repo import ProjectRepo
|
||||
|
||||
DEVELOPMENT_PLAN = ActionNode(
|
||||
key="Development Plan",
|
||||
|
|
@ -162,9 +163,8 @@ Role: You are a professional engineer; The main goal is to complete incremental
|
|||
{task}
|
||||
|
||||
## Legacy Code
|
||||
```Code
|
||||
{code}
|
||||
```
|
||||
|
||||
|
||||
## Debug logs
|
||||
```text
|
||||
|
|
@ -179,14 +179,14 @@ Role: You are a professional engineer; The main goal is to complete incremental
|
|||
```
|
||||
|
||||
# Format example
|
||||
## Code: {filename}
|
||||
## Code: {demo_filename}.py
|
||||
```python
|
||||
## {filename}
|
||||
## {demo_filename}.py
|
||||
...
|
||||
```
|
||||
## Code: {filename}
|
||||
## Code: {demo_filename}.js
|
||||
```javascript
|
||||
// {filename}
|
||||
// {demo_filename}.js
|
||||
...
|
||||
```
|
||||
|
||||
|
|
@ -211,13 +211,15 @@ WRITE_CODE_PLAN_AND_CHANGE_NODE = ActionNode.from_children("WriteCodePlanAndChan
|
|||
class WriteCodePlanAndChange(Action):
|
||||
name: str = "WriteCodePlanAndChange"
|
||||
i_context: CodePlanAndChangeContext = Field(default_factory=CodePlanAndChangeContext)
|
||||
repo: Optional[ProjectRepo] = Field(default=None, exclude=True)
|
||||
input_args: Optional[BaseModel] = Field(default=None, exclude=True)
|
||||
|
||||
async def run(self, *args, **kwargs):
|
||||
self.llm.system_prompt = "You are a professional software engineer, your primary responsibility is to "
|
||||
"meticulously craft comprehensive incremental development plan and deliver detailed incremental change"
|
||||
prd_doc = await self.repo.docs.prd.get(filename=self.i_context.prd_filename)
|
||||
design_doc = await self.repo.docs.system_design.get(filename=self.i_context.design_filename)
|
||||
task_doc = await self.repo.docs.task.get(filename=self.i_context.task_filename)
|
||||
prd_doc = await Document.load(filename=self.i_context.prd_filename)
|
||||
design_doc = await Document.load(filename=self.i_context.design_filename)
|
||||
task_doc = await Document.load(filename=self.i_context.task_filename)
|
||||
context = CODE_PLAN_AND_CHANGE_CONTEXT.format(
|
||||
requirement=f"```text\n{self.i_context.requirement}\n```",
|
||||
issue=f"```text\n{self.i_context.issue}\n```",
|
||||
|
|
@ -230,8 +232,9 @@ class WriteCodePlanAndChange(Action):
|
|||
return await WRITE_CODE_PLAN_AND_CHANGE_NODE.fill(context=context, llm=self.llm, schema="json")
|
||||
|
||||
async def get_old_codes(self) -> str:
|
||||
self.repo.old_workspace = self.repo.git_repo.workdir / os.path.basename(self.config.project_path)
|
||||
old_file_repo = self.repo.git_repo.new_file_repository(relative_path=self.repo.old_workspace)
|
||||
old_codes = await old_file_repo.get_all()
|
||||
codes = [f"----- {code.filename}\n```{code.content}```" for code in old_codes]
|
||||
old_codes = await self.repo.srcs.get_all()
|
||||
codes = [
|
||||
f"### File Name: `{code.filename}`\n```{get_markdown_code_block_type(code.filename)}\n{code.content}```\n"
|
||||
for code in old_codes
|
||||
]
|
||||
return "\n".join(codes)
|
||||
|
|
|
|||
|
|
@ -7,16 +7,17 @@
|
|||
@Modified By: mashenquan, 2023/11/27. Following the think-act principle, solidify the task parameters when creating the
|
||||
WriteCode object, rather than passing them in when calling the run function.
|
||||
"""
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic import BaseModel, Field
|
||||
from tenacity import retry, stop_after_attempt, wait_random_exponential
|
||||
|
||||
from metagpt.actions import WriteCode
|
||||
from metagpt.actions.action import Action
|
||||
from metagpt.const import REQUIREMENT_FILENAME
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import CodingContext
|
||||
from metagpt.schema import CodingContext, Document
|
||||
from metagpt.utils.common import CodeParser
|
||||
from metagpt.utils.project_repo import ProjectRepo
|
||||
|
||||
PROMPT_TEMPLATE = """
|
||||
# System
|
||||
|
|
@ -126,6 +127,8 @@ or
|
|||
class WriteCodeReview(Action):
|
||||
name: str = "WriteCodeReview"
|
||||
i_context: CodingContext = Field(default_factory=CodingContext)
|
||||
repo: Optional[ProjectRepo] = Field(default=None, exclude=True)
|
||||
input_args: Optional[BaseModel] = Field(default=None, exclude=True)
|
||||
|
||||
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
|
||||
async def write_code_review_and_rewrite(self, context_prompt, cr_prompt, filename):
|
||||
|
|
@ -150,7 +153,7 @@ class WriteCodeReview(Action):
|
|||
code_context = await WriteCode.get_codes(
|
||||
self.i_context.task_doc,
|
||||
exclude=self.i_context.filename,
|
||||
project_repo=self.repo.with_src_path(self.context.src_workspace),
|
||||
project_repo=self.repo,
|
||||
use_inc=self.config.inc,
|
||||
)
|
||||
|
||||
|
|
@ -160,7 +163,7 @@ class WriteCodeReview(Action):
|
|||
"## Code Files\n" + code_context + "\n",
|
||||
]
|
||||
if self.config.inc:
|
||||
requirement_doc = await self.repo.docs.get(filename=REQUIREMENT_FILENAME)
|
||||
requirement_doc = await Document.load(filename=self.input_args.requirements_filename)
|
||||
insert_ctx_list = [
|
||||
"## User New Requirements\n" + str(requirement_doc) + "\n",
|
||||
"## Code Plan And Change\n" + str(self.i_context.code_plan_and_change_doc) + "\n",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ from __future__ import annotations
|
|||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from metagpt.actions import Action, ActionOutput
|
||||
from metagpt.actions.action_node import ActionNode
|
||||
|
|
@ -37,6 +40,7 @@ from metagpt.schema import AIMessage, Document, Documents, Message
|
|||
from metagpt.utils.common import CodeParser
|
||||
from metagpt.utils.file_repository import FileRepository
|
||||
from metagpt.utils.mermaid import mermaid_to_file
|
||||
from metagpt.utils.project_repo import ProjectRepo
|
||||
from metagpt.utils.report import DocsReporter, GalleryReporter
|
||||
|
||||
CONTEXT_TEMPLATE = """
|
||||
|
|
@ -66,10 +70,30 @@ class WritePRD(Action):
|
|||
3. Requirement update: If the requirement is an update, the PRD document will be updated.
|
||||
"""
|
||||
|
||||
repo: Optional[ProjectRepo] = Field(default=None, exclude=True)
|
||||
input_args: Optional[BaseModel] = Field(default=None, exclude=True)
|
||||
|
||||
async def run(self, with_messages, *args, **kwargs) -> Message:
|
||||
"""Run the action."""
|
||||
req: Document = await self.repo.requirement
|
||||
docs: list[Document] = await self.repo.docs.prd.get_all()
|
||||
self.input_args = with_messages[-1].instruct_content
|
||||
if not self.input_args:
|
||||
self.repo = ProjectRepo(self.config.project_path)
|
||||
await self.repo.docs.save(filename=REQUIREMENT_FILENAME, content=with_messages[-1].content)
|
||||
self.input_args = AIMessage.create_instruct_value(
|
||||
kvs={
|
||||
"project_path": self.config.project_path,
|
||||
"requirements_filename": str(self.repo.docs.workdir / REQUIREMENT_FILENAME),
|
||||
"prd_filenames": [str(self.repo.docs.prd.workdir / i) for i in self.repo.docs.prd.all_files],
|
||||
},
|
||||
class_name="PrepareDocumentsOutput",
|
||||
)
|
||||
else:
|
||||
self.repo = ProjectRepo(self.input_args.project_path)
|
||||
req = await Document.load(filename=self.input_args.requirements_filename)
|
||||
docs: list[Document] = [
|
||||
await Document.load(filename=i, project_path=self.repo.workdir) for i in self.input_args.prd_filenames
|
||||
]
|
||||
|
||||
if not req:
|
||||
raise FileNotFoundError("No requirement document found.")
|
||||
|
||||
|
|
@ -82,10 +106,14 @@ class WritePRD(Action):
|
|||
# if requirement is related to other documents, update them, otherwise create a new one
|
||||
if related_docs := await self.get_related_docs(req, docs):
|
||||
logger.info(f"Requirement update detected: {req.content}")
|
||||
await self._handle_requirement_update(req, related_docs)
|
||||
await self._handle_requirement_update(req=req, related_docs=related_docs)
|
||||
else:
|
||||
logger.info(f"New requirement detected: {req.content}")
|
||||
await self._handle_new_requirement(req)
|
||||
kvs = self.input_args.model_dump()
|
||||
kvs["changed_prd_filenames"] = [
|
||||
str(self.repo.docs.prd.workdir / i) for i in list(self.repo.docs.prd.changed_files.keys())
|
||||
]
|
||||
return AIMessage(
|
||||
content="PRD is completed. "
|
||||
+ "\n".join(
|
||||
|
|
@ -93,6 +121,7 @@ class WritePRD(Action):
|
|||
+ list(self.repo.resources.prd.changed_files.keys())
|
||||
+ list(self.repo.resources.competitive_analysis.changed_files.keys())
|
||||
),
|
||||
instruct_content=AIMessage.create_instruct_value(kvs=kvs, class_name="WritePRDOutput"),
|
||||
cause_by=self,
|
||||
)
|
||||
|
||||
|
|
@ -103,6 +132,14 @@ class WritePRD(Action):
|
|||
return AIMessage(
|
||||
content=f"A new issue is received: {BUGFIX_FILENAME}",
|
||||
cause_by=FixBug,
|
||||
instruct_content=AIMessage.create_instruct_value(
|
||||
{
|
||||
"project_path": str(self.repo.workdir),
|
||||
"issue_filename": str(self.repo.docs.workdir / BUGFIX_FILENAME),
|
||||
"requirements_filename": str(self.repo.docs.workdir / REQUIREMENT_FILENAME),
|
||||
},
|
||||
class_name="IssueDetail",
|
||||
),
|
||||
send_to="Alex", # the name of Engineer
|
||||
)
|
||||
|
||||
|
|
@ -128,7 +165,7 @@ class WritePRD(Action):
|
|||
async def _handle_requirement_update(self, req: Document, related_docs: list[Document]) -> ActionOutput:
|
||||
# ... requirement update logic ...
|
||||
for doc in related_docs:
|
||||
await self._update_prd(req, doc)
|
||||
await self._update_prd(req=req, prd_doc=doc)
|
||||
return Documents.from_iterable(documents=related_docs).to_action_output()
|
||||
|
||||
async def _is_bugfix(self, context: str) -> bool:
|
||||
|
|
@ -159,7 +196,7 @@ class WritePRD(Action):
|
|||
async def _update_prd(self, req: Document, prd_doc: Document) -> Document:
|
||||
async with DocsReporter(enable_llm_stream=True) as reporter:
|
||||
await reporter.async_report({"type": "prd"}, "meta")
|
||||
new_prd_doc: Document = await self._merge(req, prd_doc)
|
||||
new_prd_doc: Document = await self._merge(req=req, related_doc=prd_doc)
|
||||
await self.repo.docs.prd.save_doc(doc=new_prd_doc)
|
||||
await self._save_competitive_analysis(new_prd_doc)
|
||||
md = await self.repo.resources.prd.save_pdf(doc=new_prd_doc)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
@Author : alexanderwu
|
||||
@File : write_prd_an.py
|
||||
"""
|
||||
from typing import List
|
||||
from typing import List, Union
|
||||
|
||||
from metagpt.actions.action_node import ActionNode
|
||||
|
||||
|
|
@ -132,7 +132,7 @@ REQUIREMENT_ANALYSIS = ActionNode(
|
|||
|
||||
REFINED_REQUIREMENT_ANALYSIS = ActionNode(
|
||||
key="Refined Requirement Analysis",
|
||||
expected_type=List[str],
|
||||
expected_type=Union[List[str], str],
|
||||
instruction="Review and refine the existing requirement analysis into a string list to align with the evolving needs of the project "
|
||||
"due to incremental development. Ensure the analysis comprehensively covers the new features and enhancements "
|
||||
"required for the refined project scope.",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue