mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-11 15:15:18 +02:00
update increment development, add bug fix function
This commit is contained in:
parent
1ea3c0c9f3
commit
8ff833f1e3
7 changed files with 334 additions and 80 deletions
|
|
@ -1,5 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
|
@ -25,7 +26,7 @@ templates = {
|
|||
## Format example
|
||||
{format_example}
|
||||
-----
|
||||
Role: You are an architect; the goal is to design a SOTA PEP8-compliant python system; make the best use of good open source tools
|
||||
Role: You are an architect; the goal is to perform incremental development and design a state-of-the-art (SOTA) PEP8-compliant Python system based on the context and the provided difference descriptions. Make the best use of good open source tools.
|
||||
Requirement: Fill in the following missing information based on the context, each section name is a key in json
|
||||
Max Output: 8192 chars or 2048 tokens. Try to use them up.
|
||||
|
||||
|
|
@ -83,7 +84,7 @@ and only output the json inside this tag, nothing else
|
|||
## Format example
|
||||
{format_example}
|
||||
-----
|
||||
Role: You are an architect; the goal is to design a SOTA PEP8-compliant python system; make the best use of good open source tools
|
||||
Role: You are an architect; the goal is to perform incremental development and design a state-of-the-art (SOTA) PEP8-compliant Python system based on the context and the provided difference descriptions. Make the best use of good open source tools.
|
||||
Requirement: Fill in the following missing information based on the context, note that all sections are response with code form separately
|
||||
Max Output: 8192 chars or 2048 tokens. Try to use them up.
|
||||
Attention: Use '##' to split sections, not '#', and '## <SECTION_NAME>' SHOULD WRITE BEFORE the code and triple quote.
|
||||
|
|
@ -179,6 +180,25 @@ class RefineDesign(Action):
|
|||
pass # Folder does not exist, but we don't care
|
||||
workspace.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def create_or_increment_workspace(self, workspace: Path):
|
||||
# 如果工作空间已存在,添加数字以区分
|
||||
original_workspace = workspace
|
||||
index = 1
|
||||
while workspace.exists():
|
||||
ws_name_match = re.match(r'^(.*)_([\d]+)$', original_workspace.name)
|
||||
if ws_name_match:
|
||||
base_name, existing_index = ws_name_match.groups()
|
||||
index = int(existing_index)
|
||||
index += 1
|
||||
workspace = original_workspace.parent / f"{base_name}_{index}"
|
||||
else:
|
||||
workspace = original_workspace.parent / f"{original_workspace.name}_{index}"
|
||||
index += 1
|
||||
|
||||
# 创建工作空间,包括所有必要的父文件夹
|
||||
workspace.mkdir(parents=True, exist_ok=True)
|
||||
return workspace
|
||||
|
||||
async def _save_prd(self, docs_path, resources_path, context):
|
||||
prd_file = docs_path / "prd.md"
|
||||
if context[-1].instruct_content and context[-1].instruct_content.dict()["Competitive Quadrant Chart"]:
|
||||
|
|
@ -208,6 +228,7 @@ class RefineDesign(Action):
|
|||
else:
|
||||
ws_name = CodeParser.parse_str(block="Python package name", text=system_design)
|
||||
workspace = WORKSPACE_ROOT / ws_name
|
||||
# workspace = self.create_or_increment_workspace(workspace)
|
||||
self.recreate_workspace(workspace)
|
||||
docs_path = workspace / "docs"
|
||||
resources_path = workspace / "resources"
|
||||
|
|
|
|||
|
|
@ -43,13 +43,15 @@ quadrantChart
|
|||
## Format example
|
||||
{format_example}
|
||||
-----
|
||||
Role: You are a professional product manager; the goal is to design a concise, usable, efficient product based on the new requirements, the difference description and Legacy PRD.
|
||||
Role: You are a professional Product Manager tasked with overseeing incremental development and crafting Product Requirements Documents (PRDs) for a concise, usable, and efficient product.
|
||||
Requirements: According to the context, fill in the following missing information, each section name is a key in json ,If the requirements are unclear, ensure minimum viability and avoid excessive design
|
||||
|
||||
## New Requirements: Provide as Plain text and place the new requirements here
|
||||
|
||||
## Difference Description: Provide as Python list[str], up to 5 clear, difference descriptions. If the requirement itself is simple, the difference description should also be simple
|
||||
|
||||
## Incremental Development Plan: Provide as Python list[str], up to 5 clear, incremental development plans. If the requirement itself is simple, the incremental development plan should also be simple
|
||||
|
||||
## Product Goals: Provided as Python list[str], up to 3 clear, orthogonal product goals. If the requirement itself is simple, the goal should also be simple
|
||||
|
||||
## User Stories: Provided as Python list[str], up to 5 scenario-based user stories, If the requirement itself is simple, the user stories should also be less
|
||||
|
|
@ -72,9 +74,10 @@ and only output the json inside this tag, nothing else
|
|||
[CONTENT]
|
||||
{
|
||||
"New Requirements": "",
|
||||
"Difference Description": "",
|
||||
"Difference Description": [],
|
||||
"Search Information": "",
|
||||
"Requirements": "",
|
||||
"Incremental Development Plan": [],
|
||||
"Product Goals": [],
|
||||
"User Stories": [],
|
||||
"Competitive Analysis": [],
|
||||
|
|
@ -138,7 +141,7 @@ quadrantChart
|
|||
## Format example
|
||||
{format_example}
|
||||
-----
|
||||
Role: You are a professional product manager; the goal is to design a concise, usable, efficient product
|
||||
Role: You are a professional Product Manager tasked with overseeing incremental development and crafting Product Requirements Documents (PRDs) for a concise, usable, and efficient product.
|
||||
Requirements: According to the context, fill in the following missing information, note that each sections are returned in Python code triple quote form seperatedly. If the requirements are unclear, ensure minimum viability and avoid excessive design
|
||||
ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. AND '## <SECTION_NAME>' SHOULD WRITE BEFORE the code and triple quote. Output carefully referenced "Format example" in format.
|
||||
|
||||
|
|
@ -146,6 +149,8 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. AND '## <SECTION_NAME>' SHOULD W
|
|||
|
||||
## Difference Description: Provide as Python list[str], up to 5 clear, difference descriptions. If the requirement itself is simple, the difference description should also be simple
|
||||
|
||||
## Incremental Development Plan: Provide as Python list[str], up to 5 clear, incremental development plans. If the requirement itself is simple, the incremental development plan should also be simple
|
||||
|
||||
## Product Goals: Provided as Python list[str], up to 3 clear, orthogonal product goals. If the requirement itself is simple, the goal should also be simple
|
||||
|
||||
## User Stories: Provided as Python list[str], up to 5 scenario-based user stories, If the requirement itself is simple, the user stories should also be less
|
||||
|
|
@ -172,6 +177,11 @@ The boss ...
|
|||
"...",
|
||||
]
|
||||
|
||||
## Incremental Development Plan
|
||||
[
|
||||
"It ...",
|
||||
]
|
||||
|
||||
## Product Goals
|
||||
```python
|
||||
[
|
||||
|
|
@ -225,6 +235,7 @@ INCREMENT_OUTPUT_MAPPING = {
|
|||
"New Requirements": (str, ...),
|
||||
# "Major Enhancements": (List[str], ...),
|
||||
"Difference Description": (List[str], ...),
|
||||
"Incremental Development Plan": (List[str], ...),
|
||||
"Product Goals": (List[str], ...),
|
||||
"User Stories": (List[str], ...),
|
||||
"Competitive Analysis": (List[str], ...),
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ templates = {
|
|||
## Format example
|
||||
{format_example}
|
||||
-----
|
||||
Role: You are a project manager; the goal is to break down tasks according to PRD/technical design, give a task list, and analyze task dependencies to start with the prerequisite modules
|
||||
Role: You are a project manager; the goal is to perform incremental development based on the context and difference descriptions and the legacy. Break down tasks according to PRD/technical design, provide 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.
|
||||
|
||||
|
|
@ -31,14 +31,14 @@ Attention: Use '##' to split sections, not '#', and '## <SECTION_NAME>' SHOULD W
|
|||
|
||||
## Full API spec: Use OpenAPI 3.0. Describe all APIs that may be used by both frontend and backend.
|
||||
|
||||
## Difference Description: Please provide a detailed description of the differences between this project and its predecessors or similar projects that can include changes in technology, architecture.
|
||||
|
||||
## Logic Analysis: Provided as a Python list[list[str]. the first is filename, the second is class/method/function should be implemented in this file. Analyze the dependencies between the files, which work should be done first
|
||||
|
||||
## Task list: Provided as Python list[str]. Each str is a filename, the more at the beginning, the more it is a prerequisite dependency, should be done first
|
||||
|
||||
## Shared Knowledge: Anything that should be public like utils' functions, config's variables details that should make clear first.
|
||||
|
||||
## Difference Description: Please provide a detailed description of the differences between this project and its predecessors or similar projects that can include changes in technology, architecture.
|
||||
|
||||
## 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 format example,
|
||||
|
|
@ -58,6 +58,9 @@ and only output the json inside this tag, nothing else
|
|||
...
|
||||
description: A JSON object ...
|
||||
""",
|
||||
"Difference Description": """
|
||||
The ...
|
||||
""",
|
||||
"Logic Analysis": [
|
||||
["game.py","Contains..."]
|
||||
],
|
||||
|
|
@ -67,9 +70,6 @@ and only output the json inside this tag, nothing else
|
|||
"Shared Knowledge": """
|
||||
'game.py' contains ...
|
||||
""",
|
||||
"Difference Description": """
|
||||
The ...
|
||||
""",
|
||||
"Anything UNCLEAR": "We need ... how to start."
|
||||
}
|
||||
''',
|
||||
|
|
@ -85,7 +85,7 @@ and only output the json inside this tag, nothing else
|
|||
## Format example
|
||||
{format_example}
|
||||
-----
|
||||
Role: You are a project manager; the goal is to break down tasks according to PRD/technical design, give a task list, and analyze task dependencies to start with the prerequisite modules
|
||||
Role: You are a project manager; the goal is to perform incremental development based on the context and difference descriptions and the legacy. Break down tasks according to PRD/technical design, provide a task list, and analyze task dependencies to start with the prerequisite modules.
|
||||
Requirements: Based on the context, fill in the following missing information, note that all sections are returned in Python code triple quote form seperatedly. 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.
|
||||
|
||||
|
|
@ -95,14 +95,14 @@ Attention: Use '##' to split sections, not '#', and '## <SECTION_NAME>' SHOULD W
|
|||
|
||||
## Full API spec: Use OpenAPI 3.0. Describe all APIs that may be used by both frontend and backend.
|
||||
|
||||
## Difference Description: Please provide a detailed description of the differences between this project and its predecessors or similar projects that can include changes in technology, architecture.
|
||||
|
||||
## Logic Analysis: Provided as a Python list[list[str]. the first is filename, the second is class/method/function should be implemented in this file. Analyze the dependencies between the files, which work should be done first
|
||||
|
||||
## Task list: Provided as Python list[str]. Each str is a filename, the more at the beginning, the more it is a prerequisite dependency, should be done first
|
||||
|
||||
## Shared Knowledge: Anything that should be public like utils' functions, config's variables details that should make clear first.
|
||||
|
||||
## Difference Description: Please provide a detailed description of the differences between this project and its predecessors or similar projects that can include changes in technology, architecture.
|
||||
|
||||
## 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.
|
||||
|
||||
""",
|
||||
|
|
@ -132,6 +132,13 @@ description: A JSON object ...
|
|||
"""
|
||||
```
|
||||
|
||||
## Difference Description
|
||||
```python
|
||||
"""
|
||||
The ...
|
||||
"""
|
||||
```
|
||||
|
||||
## Logic Analysis
|
||||
```python
|
||||
[
|
||||
|
|
@ -153,13 +160,6 @@ description: A JSON object ...
|
|||
"""
|
||||
```
|
||||
|
||||
## Difference Description
|
||||
```python
|
||||
"""
|
||||
The ...
|
||||
"""
|
||||
```
|
||||
|
||||
## Anything UNCLEAR
|
||||
We need ... how to start.
|
||||
---
|
||||
|
|
@ -170,10 +170,10 @@ OUTPUT_MAPPING = {
|
|||
"Required Python third-party packages": (List[str], ...),
|
||||
"Required Other language third-party packages": (List[str], ...),
|
||||
"Full API spec": (str, ...),
|
||||
"Difference Description": (str, ...),
|
||||
"Logic Analysis": (List[List[str]], ...),
|
||||
"Task list": (List[str], ...),
|
||||
"Shared Knowledge": (str, ...),
|
||||
"Difference Description": (str, ...),
|
||||
"Anything UNCLEAR": (str, ...),
|
||||
}
|
||||
|
||||
|
|
|
|||
80
metagpt/actions/write_code_refine.py
Normal file
80
metagpt/actions/write_code_refine.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from metagpt.actions.action import Action
|
||||
from metagpt.logs import logger
|
||||
from metagpt.schema import Message
|
||||
from metagpt.utils.common import CodeParser
|
||||
from tenacity import retry, stop_after_attempt, wait_fixed
|
||||
|
||||
|
||||
PROMPT_TEMPLATE = """
|
||||
NOTICE
|
||||
Role: You are a professional software engineer, and your main task is to conduct incremental development, which includes reviewing existing code, providing modification suggestions, rewriting code, and optimizing the codebase. Existing code and logic that need to be retained must also appear in the code after incremental development, do not omit it. Ensure that the code conforms to the PEP8 standards, is elegantly designed and modularized, easy to read and maintain, and is written in Python 3.9 (or in another programming language).
|
||||
ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced "Format example".
|
||||
|
||||
## Code Review: Based on the following context and legacy code, and following the checklist, provide key, clear, concise, and specific code modification suggestions, up to 5.
|
||||
```
|
||||
1. Check 0: Is the code implemented as per the requirements?
|
||||
2. Check 1: Are there any issues with the code logic?
|
||||
3. Check 2: Does the existing code follow the "Data structures and interface definitions"?
|
||||
4. Check 3: Is there a function in the code that is omitted or not fully implemented that needs to be implemented?
|
||||
5. Check 4: Does the code have unnecessary or lack dependencies?
|
||||
```
|
||||
|
||||
## Incremental Development: {filename} Based on the findings from the "Code Review," context, and the legacy code, conduct incremental development by rewriting, optimizing, and adding new code using triple quotes.
|
||||
-----
|
||||
# Context
|
||||
{context}
|
||||
|
||||
## Legacy Code
|
||||
You are tasked with conducting incremental development in the existing code and creating a new code file, {filename}, based on the provided legacy code and above information.
|
||||
```
|
||||
{code}
|
||||
```
|
||||
-----
|
||||
|
||||
## Format example
|
||||
-----
|
||||
{format_example}
|
||||
-----
|
||||
|
||||
"""
|
||||
|
||||
FORMAT_EXAMPLE = """
|
||||
|
||||
## Code Review
|
||||
1. The code ...
|
||||
2. ...
|
||||
3. ...
|
||||
4. ...
|
||||
5. ...
|
||||
|
||||
## Incremental Development: {filename}
|
||||
```python
|
||||
## {filename} - Incremental Development
|
||||
...
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
|
||||
class WriteCodeRefine(Action):
|
||||
def __init__(self, name="WriteCodeRefine", context: list[Message] = None, llm=None):
|
||||
super().__init__(name, context, llm)
|
||||
|
||||
@retry(stop=stop_after_attempt(2), wait=wait_fixed(1))
|
||||
async def write_code(self, prompt):
|
||||
code_rsp = await self._aask(prompt)
|
||||
code = CodeParser.parse_code(block="", text=code_rsp)
|
||||
return code
|
||||
|
||||
async def run(self, context, code, filename):
|
||||
format_example = FORMAT_EXAMPLE.format(filename=filename)
|
||||
prompt = PROMPT_TEMPLATE.format(context=context, code=code, filename=filename, format_example=format_example)
|
||||
logger.info(f'Code refine {filename}..')
|
||||
code = await self.write_code(prompt)
|
||||
# code_rsp = await self._aask_v1(prompt, "code_rsp", OUTPUT_MAPPING)
|
||||
# self._save(context, filename, code)
|
||||
return code
|
||||
|
||||
|
|
@ -6,13 +6,16 @@
|
|||
@File : engineer.py
|
||||
"""
|
||||
import asyncio
|
||||
import re
|
||||
import shutil
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from metagpt.actions import WriteCode, WriteCodeReview, WriteDesign, WriteTasks
|
||||
from metagpt.actions import WriteCode, WriteCodeReview, WriteDesign, WriteTasks, BossRequirement
|
||||
from metagpt.actions.refine_design_api import RefineDesign
|
||||
from metagpt.actions.refine_project_management import RefineTasks
|
||||
from metagpt.actions.write_code_refine import WriteCodeRefine
|
||||
from metagpt.const import WORKSPACE_ROOT
|
||||
from metagpt.logs import logger
|
||||
from metagpt.roles import Role
|
||||
|
|
@ -72,6 +75,8 @@ class Engineer(Role):
|
|||
use_code_review: bool = False,
|
||||
legacy: str = "",
|
||||
increment: bool = False,
|
||||
bug_msgs: List = None,
|
||||
bug_fix: bool = False,
|
||||
) -> None:
|
||||
"""Initializes the Engineer role with given attributes."""
|
||||
super().__init__(name, profile, goal, constraints)
|
||||
|
|
@ -79,13 +84,20 @@ class Engineer(Role):
|
|||
self.use_code_review = use_code_review
|
||||
self.legacy = legacy
|
||||
self.increment = increment
|
||||
self.bug_msgs = bug_msgs
|
||||
self.bug_fix = bug_fix
|
||||
if self.use_code_review or self.increment:
|
||||
self._init_actions([WriteCode, WriteCodeReview])
|
||||
self._init_actions([WriteCode, WriteCodeReview, WriteCodeRefine])
|
||||
|
||||
if self.increment:
|
||||
self._watch([RefineTasks])
|
||||
self.todos = []
|
||||
elif self.bug_fix:
|
||||
self._watch([BossRequirement])
|
||||
self.todos = []
|
||||
else:
|
||||
self._watch([WriteTasks])
|
||||
self.todos = []
|
||||
self.todos = []
|
||||
self.n_borg = n_borg
|
||||
|
||||
@classmethod
|
||||
|
|
@ -94,6 +106,18 @@ class Engineer(Role):
|
|||
return task_msg.instruct_content.dict().get("Task list")
|
||||
return CodeParser.parse_file_list(block="Task list", text=task_msg.content)
|
||||
|
||||
@classmethod
|
||||
def parse_todos(self, bug_context: List) -> list[str]:
|
||||
for msg in bug_context:
|
||||
if msg.sent_from == "ProjectManager":
|
||||
content = msg.content
|
||||
tasks_section = re.search(r"## Task list\n\n(.*?)(\n\n|$)", content, re.DOTALL)
|
||||
if tasks_section:
|
||||
tasks = tasks_section.group(1).split("\n")
|
||||
tasks = [task.strip("-").strip() for task in tasks]
|
||||
return tasks
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def parse_code(self, code_text: str) -> str:
|
||||
return CodeParser.parse_code(block="", text=code_text)
|
||||
|
|
@ -107,13 +131,17 @@ class Engineer(Role):
|
|||
def get_workspace(self) -> Path:
|
||||
if self.increment:
|
||||
msg = self._rc.memory.get_by_action(RefineDesign)[-1]
|
||||
elif self.bug_fix:
|
||||
msg = self._rc.memory.get_by_action(BossRequirement)[-1]
|
||||
else:
|
||||
msg = self._rc.memory.get_by_action(WriteDesign)[-1]
|
||||
if not msg:
|
||||
return WORKSPACE_ROOT / "src"
|
||||
workspace = self.parse_workspace(msg)
|
||||
workspace = workspace if workspace else "src"
|
||||
# Codes are written in workspace/{package_name}/{package_name}
|
||||
return WORKSPACE_ROOT / workspace / workspace
|
||||
# return self.create_or_increment_workspace(WORKSPACE_ROOT, workspace)
|
||||
|
||||
def recreate_workspace(self):
|
||||
workspace = self.get_workspace()
|
||||
|
|
@ -123,8 +151,7 @@ class Engineer(Role):
|
|||
pass # The folder does not exist, but we don't care
|
||||
workspace.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def write_file(self, filename: str, code: str):
|
||||
workspace = self.get_workspace()
|
||||
def write_file(self, workspace: Path, filename: str, code: str) -> Path:
|
||||
filename = filename.replace('"', "").replace("\n", "")
|
||||
file = workspace / filename
|
||||
file.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
|
@ -134,7 +161,10 @@ class Engineer(Role):
|
|||
def recv(self, message: Message) -> None:
|
||||
self._rc.memory.add(message)
|
||||
if message in self._rc.important_memory:
|
||||
self.todos = self.parse_tasks(message)
|
||||
if not self.bug_fix:
|
||||
self.todos = self.parse_tasks(message)
|
||||
else:
|
||||
self.todos = self.parse_todos(self.bug_msgs)
|
||||
|
||||
async def _act_mp(self) -> Message:
|
||||
# self.recreate_workspace()
|
||||
|
|
@ -161,12 +191,13 @@ class Engineer(Role):
|
|||
|
||||
async def _act_sp(self) -> Message:
|
||||
code_msg_all = [] # gather all code info, will pass to qa_engineer for tests later
|
||||
workspace = self.get_workspace()
|
||||
for todo in self.todos:
|
||||
code = await WriteCode().run(context=self._rc.history, filename=todo)
|
||||
# logger.info(todo)
|
||||
# logger.info(code_rsp)
|
||||
# code = self.parse_code(code_rsp)
|
||||
file_path = self.write_file(todo, code)
|
||||
file_path = self.write_file(workspace, todo, code)
|
||||
msg = Message(content=code, role=self.profile, cause_by=type(self._rc.todo))
|
||||
self._rc.memory.add(msg)
|
||||
|
||||
|
|
@ -181,41 +212,76 @@ class Engineer(Role):
|
|||
|
||||
async def _act_increment(self, legacy) -> Message:
|
||||
code_msg_all = [] # gather all code info, will pass to qa_engineer for tests later
|
||||
workspace = self.get_workspace()
|
||||
flag = True
|
||||
# legacy_codes = legacy.split('---')
|
||||
for todo in self.todos:
|
||||
"""
|
||||
# Select essential information from the historical data to reduce the length of the prompt (summarized from human experience):
|
||||
1. All from Architect
|
||||
2. All from ProjectManager
|
||||
3. Do we need other codes (currently needed)?
|
||||
TODO: The goal is not to need it. After clear task decomposition, based on the design idea, you should be able to write a single file without needing other codes. If you can't, it means you need a clearer definition. This is the key to writing longer code.
|
||||
"""
|
||||
context = []
|
||||
|
||||
if self.increment:
|
||||
msg = self._rc.memory.get_by_actions([RefineDesign, RefineTasks, WriteCode])
|
||||
else:
|
||||
msg = self._rc.memory.get_by_actions([WriteDesign, WriteTasks, WriteCode])
|
||||
|
||||
msg = self._rc.memory.get_by_actions([RefineDesign, RefineTasks, WriteCodeRefine])
|
||||
for m in msg:
|
||||
context.append(m.content)
|
||||
context_str = "\n".join(context)
|
||||
code = legacy
|
||||
|
||||
# Refine code or Write code
|
||||
if flag and self.increment:
|
||||
code = legacy
|
||||
flag = False
|
||||
else:
|
||||
code = await WriteCode().run(context=context_str, filename=todo)
|
||||
# if self.increment and len(legacy_codes) > 0:
|
||||
# code = legacy_codes.pop(0)
|
||||
|
||||
# Code review
|
||||
if self.use_code_review:
|
||||
try:
|
||||
rewrite_code = await WriteCode().run(context=context_str, code=code, filename=todo)
|
||||
code = rewrite_code
|
||||
except Exception as e:
|
||||
logger.error("code review failed!", e)
|
||||
pass
|
||||
file_path = self.write_file(todo, code)
|
||||
try:
|
||||
rewrite_code = await WriteCodeRefine().run(context=context_str, code=code, filename=todo)
|
||||
code = rewrite_code
|
||||
except Exception as e:
|
||||
logger.error("code review failed!", e)
|
||||
pass
|
||||
|
||||
# code = await WriteCode().run(context=context_str, filename=todo)
|
||||
|
||||
file_path = self.write_file(workspace, todo, code)
|
||||
msg = Message(content=code, role=self.profile, cause_by=WriteCode)
|
||||
self._rc.memory.add(msg)
|
||||
|
||||
code_msg = todo + FILENAME_CODE_SEP + str(file_path)
|
||||
code_msg_all.append(code_msg)
|
||||
|
||||
logger.info(f"Done {self.get_workspace()} generating.")
|
||||
msg = Message(
|
||||
content=MSG_SEP.join(code_msg_all), role=self.profile, cause_by=type(self._rc.todo), send_to="QaEngineer"
|
||||
)
|
||||
return msg
|
||||
|
||||
async def _act_bug_fix(self, bug_msgs) -> Message:
|
||||
code_msg_all = [] # gather all code info, will pass to qa_engineer for tests later
|
||||
workspace = self.get_workspace()
|
||||
flag = True
|
||||
# legacy_codes = legacy.split('---')
|
||||
for todo in self.todos:
|
||||
context = []
|
||||
|
||||
for m in bug_msgs:
|
||||
if m.sent_from != "Engineer":
|
||||
context.append(m.content)
|
||||
context.append(m.content)
|
||||
context_str = "\n".join(context)
|
||||
code = [m.content for m in bug_msgs if m.sent_from == "Engineer"]
|
||||
code = "\n".join(code)
|
||||
|
||||
# Refine code or Write code
|
||||
# if self.increment and len(legacy_codes) > 0:
|
||||
# code = legacy_codes.pop(0)
|
||||
|
||||
# Code review
|
||||
try:
|
||||
rewrite_code = await WriteCodeRefine().run(context=context_str, code=code, filename=todo)
|
||||
code = rewrite_code
|
||||
except Exception as e:
|
||||
logger.error("code review failed!", e)
|
||||
pass
|
||||
|
||||
# code = await WriteCode().run(context=context_str, filename=todo)
|
||||
|
||||
file_path = self.write_file(workspace, todo, code)
|
||||
msg = Message(content=code, role=self.profile, cause_by=WriteCode)
|
||||
self._rc.memory.add(msg)
|
||||
|
||||
|
|
@ -230,6 +296,7 @@ class Engineer(Role):
|
|||
|
||||
async def _act_sp_precision(self) -> Message:
|
||||
code_msg_all = [] # gather all code info, will pass to qa_engineer for tests later
|
||||
workspace = self.get_workspace()
|
||||
for todo in self.todos:
|
||||
"""
|
||||
# Select essential information from the historical data to reduce the length of the prompt (summarized from human experience):
|
||||
|
|
@ -253,7 +320,7 @@ class Engineer(Role):
|
|||
except Exception as e:
|
||||
logger.error("code review failed!", e)
|
||||
pass
|
||||
file_path = self.write_file(todo, code)
|
||||
file_path = self.write_file(workspace, todo, code)
|
||||
msg = Message(content=code, role=self.profile, cause_by=WriteCode)
|
||||
self._rc.memory.add(msg)
|
||||
|
||||
|
|
@ -266,14 +333,35 @@ class Engineer(Role):
|
|||
)
|
||||
return msg
|
||||
|
||||
async def _observe(self) -> int:
|
||||
if self.bug_fix:
|
||||
msg = Message(
|
||||
content=self.bug_msgs[0].content + "\n---\n" + self.legacy,
|
||||
role=self.profile,
|
||||
cause_by=BossRequirement,
|
||||
sent_from=self.profile,
|
||||
send_to=self.profile,
|
||||
)
|
||||
self._publish_message(msg)
|
||||
await super()._observe()
|
||||
self._rc.news = [
|
||||
msg for msg in self._rc.news if msg.send_to == self.profile
|
||||
] # only relevant msgs count as observed news
|
||||
return len(self._rc.news)
|
||||
|
||||
async def _act(self) -> Message:
|
||||
"""Determines the mode of action based on whether code review is used."""
|
||||
if self.increment:
|
||||
logger.info(f"{self._setting}: ready to RefineWriteCode")
|
||||
elif self.bug_fix:
|
||||
logger.info(f"{self._setting}: ready to BugFix")
|
||||
else:
|
||||
logger.info(f"{self._setting}: ready to WriteCode")
|
||||
|
||||
if self.use_code_review:
|
||||
return await self._act_sp_precision()
|
||||
elif self.increment:
|
||||
return await self._act_increment(self.legacy)
|
||||
elif self.bug_fix:
|
||||
return await self._act_bug_fix(self.bug_msgs)
|
||||
return await self._act_sp()
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@ from metagpt.actions import (
|
|||
WriteCode,
|
||||
WriteCodeReview,
|
||||
WriteDesign,
|
||||
WriteTest,
|
||||
WriteTest, BossRequirement,
|
||||
)
|
||||
from metagpt.actions.write_code_refine import WriteCodeRefine
|
||||
from metagpt.const import WORKSPACE_ROOT
|
||||
from metagpt.logs import logger
|
||||
from metagpt.roles import Role
|
||||
|
|
@ -32,12 +33,20 @@ class QaEngineer(Role):
|
|||
goal="Write comprehensive and robust tests to ensure codes will work as expected without bugs",
|
||||
constraints="The test code you write should conform to code standard like PEP8, be modular, easy to read and maintain",
|
||||
test_round_allowed=5,
|
||||
legacy="",
|
||||
bug_context="",
|
||||
):
|
||||
super().__init__(name, profile, goal, constraints)
|
||||
self._init_actions(
|
||||
[WriteTest]
|
||||
) # FIXME: a bit hack here, only init one action to circumvent _think() logic, will overwrite _think() in future updates
|
||||
self._watch([WriteCode, WriteCodeReview, WriteTest, RunCode, DebugError])
|
||||
self.legacy = legacy
|
||||
self.bug_context = bug_context
|
||||
if self.bug_context:
|
||||
self._init_actions([WriteTest])
|
||||
self._watch([WriteCode, WriteCodeRefine, WriteTest, RunCode, DebugError])
|
||||
else:
|
||||
self._init_actions(
|
||||
[WriteTest]
|
||||
) # FIXME: a bit hack here, only init one action to circumvent _think() logic, will overwrite _think() in future updates
|
||||
self._watch([WriteCode, WriteCodeReview, WriteTest, RunCode, DebugError])
|
||||
self.test_round = 0
|
||||
self.test_round_allowed = test_round_allowed
|
||||
|
||||
|
|
@ -48,10 +57,14 @@ class QaEngineer(Role):
|
|||
return CodeParser.parse_str(block="Python package name", text=system_design_msg.content)
|
||||
|
||||
def get_workspace(self, return_proj_dir=True) -> Path:
|
||||
msg = self._rc.memory.get_by_action(WriteDesign)[-1]
|
||||
if self.bug_context:
|
||||
msg = self._rc.memory.get_by_action(WriteCodeRefine)[-1]
|
||||
else:
|
||||
msg = self._rc.memory.get_by_action(WriteDesign)[-1]
|
||||
if not msg:
|
||||
return WORKSPACE_ROOT / "src"
|
||||
workspace = self.parse_workspace(msg)
|
||||
workspace = workspace if workspace else "src"
|
||||
# project directory: workspace/{package_name}, which contains package source code folder, tests folder, resources folder, etc.
|
||||
if return_proj_dir:
|
||||
return WORKSPACE_ROOT / workspace
|
||||
|
|
@ -146,6 +159,15 @@ class QaEngineer(Role):
|
|||
self._publish_message(msg)
|
||||
|
||||
async def _observe(self) -> int:
|
||||
if self.bug_context:
|
||||
msg = Message(
|
||||
content=self.bug_context + "\n---\n" + self.legacy,
|
||||
role=self.profile,
|
||||
cause_by=BossRequirement,
|
||||
sent_from=self.profile,
|
||||
send_to=self.profile,
|
||||
)
|
||||
self._publish_message(msg)
|
||||
await super()._observe()
|
||||
self._rc.news = [
|
||||
msg for msg in self._rc.news if msg.send_to == self.profile
|
||||
|
|
@ -166,7 +188,7 @@ class QaEngineer(Role):
|
|||
for msg in self._rc.news:
|
||||
# Decide what to do based on observed msg type, currently defined by human,
|
||||
# might potentially be moved to _think, that is, let the agent decides for itself
|
||||
if msg.cause_by in [WriteCode, WriteCodeReview]:
|
||||
if msg.cause_by in [WriteCode, WriteCodeReview, WriteCodeRefine]:
|
||||
# engineer wrote a code, time to write a test for it
|
||||
await self._write_test(msg)
|
||||
elif msg.cause_by in [WriteTest, DebugError]:
|
||||
|
|
|
|||
68
startup.py
68
startup.py
|
|
@ -2,6 +2,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import asyncio
|
||||
import os
|
||||
import platform
|
||||
|
||||
import fire
|
||||
|
||||
|
|
@ -12,29 +13,32 @@ from metagpt.roles import (
|
|||
ProjectManager,
|
||||
QaEngineer,
|
||||
)
|
||||
from metagpt.schema import Message
|
||||
from metagpt.team import Team
|
||||
from metagpt.utils.special_tokens import MSG_SEP
|
||||
|
||||
|
||||
async def startup(
|
||||
idea: str,
|
||||
difference_description: str = "",
|
||||
path: str = "",
|
||||
project_path: str = "",
|
||||
investment: float = 3.0,
|
||||
n_round: int = 5,
|
||||
code_review: bool = False,
|
||||
run_tests: bool = False,
|
||||
implement: bool = True,
|
||||
increment: bool = False,
|
||||
bug_fix: bool = False,
|
||||
):
|
||||
"""Run a startup. Be a boss."""
|
||||
company = Team()
|
||||
|
||||
if increment:
|
||||
if increment or bug_fix:
|
||||
# 读取文件
|
||||
prd_path = os.path.join(path, 'docs/prd.md')
|
||||
design_path = os.path.join(path, 'docs/system_design.md')
|
||||
api_spec_path = os.path.join(path, 'docs/api_spec_and_tasks.md')
|
||||
code_path = os.path.join(path, os.path.basename(path))
|
||||
prd_path = os.path.join(project_path, 'docs/prd.md')
|
||||
design_path = os.path.join(project_path, 'docs/system_design.md')
|
||||
api_spec_and_tasks_path = os.path.join(project_path, 'docs/api_spec_and_tasks.md')
|
||||
code_path = os.path.join(project_path, os.path.basename(project_path))
|
||||
|
||||
with open(prd_path, 'r', encoding='utf-8') as f:
|
||||
legacy_prd = f.read()
|
||||
|
|
@ -42,34 +46,59 @@ async def startup(
|
|||
with open(design_path, 'r', encoding='utf-8') as f:
|
||||
legacy_design = f.read()
|
||||
|
||||
with open(api_spec_path, 'r', encoding='utf-8') as f:
|
||||
legacy_api_spec = f.read()
|
||||
with open(api_spec_and_tasks_path, 'r', encoding='utf-8') as f:
|
||||
legacy_api_spec_and_tasks = f.read()
|
||||
|
||||
# 遍历文件夹,获取所有代码文件
|
||||
legacy_code = ''
|
||||
for root, dirs, files in os.walk(code_path):
|
||||
filenames = [filename for filename in files if filename.endswith('.py')]
|
||||
legacy_code += f'There are {len(files)} scripts in the current folder: {", ".join(filenames)}\n\n'
|
||||
legacy_code += f'There are {len(files)} scripts in the current folder: {", ".join(filenames)}\n---\n'
|
||||
for file in files:
|
||||
if file.endswith('.py'):
|
||||
with open(os.path.join(root, file), 'r', encoding='utf-8') as f:
|
||||
legacy_code += f.read() + '\n\n'
|
||||
legacy_code += f.read() + '\n---\n'
|
||||
|
||||
company.hire(
|
||||
[ProductManager(difference_description=difference_description, legacy=legacy_prd, increment=increment),
|
||||
Architect(legacy=legacy_design, increment=increment),
|
||||
ProjectManager(legacy=legacy_api_spec, increment=increment)])
|
||||
if bug_fix:
|
||||
boss_msg = Message(
|
||||
content=f"Boss's requirement\n:{idea}\n---\nBoss's difference description:{difference_description}\n---\n",
|
||||
sent_from="Boss",
|
||||
)
|
||||
product_manager_msg = Message(
|
||||
content=f"Product Manager's prd legacy:\n{legacy_prd}\n---\n",
|
||||
sent_from="ProductManager"
|
||||
)
|
||||
architect_msg = Message(
|
||||
content=f"Architect's design legacy:\n{legacy_design}\n---\n",
|
||||
sent_from="Architect"
|
||||
)
|
||||
project_manager_msg = Message(
|
||||
content=f"Project Manager's api spec and tasks legacy:\n{legacy_api_spec_and_tasks}\n---\n",
|
||||
sent_from="ProjectManager"
|
||||
)
|
||||
engineer_msg = Message(
|
||||
content=f"Engineer's code legacy:\n{legacy_code}\n---\n",
|
||||
sent_from="Engineer"
|
||||
)
|
||||
bug_msgs = [boss_msg, product_manager_msg, architect_msg, project_manager_msg, engineer_msg]
|
||||
else:
|
||||
company.hire(
|
||||
[ProductManager(difference_description=difference_description, legacy=legacy_prd, increment=increment),
|
||||
Architect(legacy=legacy_design, increment=increment),
|
||||
ProjectManager(legacy=legacy_api_spec_and_tasks, increment=increment)])
|
||||
else:
|
||||
company.hire([ProductManager(), Architect(), ProjectManager()])
|
||||
|
||||
# if implement or code_review
|
||||
if (implement or code_review) and not increment:
|
||||
if bug_fix:
|
||||
company.hire([Engineer(n_borg=5, bug_msgs=bug_msgs, bug_fix=bug_fix)])
|
||||
elif implement or code_review:
|
||||
# developing features: implement the idea
|
||||
company.hire([Engineer(n_borg=5, use_code_review=code_review)])
|
||||
elif (implement or code_review) and increment:
|
||||
company.hire([Engineer(n_borg=5, use_code_review=code_review, legacy=legacy_code, increment=increment)])
|
||||
|
||||
if run_tests:
|
||||
if run_tests or bug_fix:
|
||||
# developing features: run tests on the spot and identify bugs
|
||||
# (bug fixing capability comes soon!)
|
||||
company.hire([QaEngineer()])
|
||||
|
|
@ -82,13 +111,14 @@ async def startup(
|
|||
def main(
|
||||
idea: str,
|
||||
difference_description: str = "",
|
||||
path: str = "",
|
||||
project_path: str = "",
|
||||
investment: float = 3.0,
|
||||
n_round: int = 5,
|
||||
code_review: bool = True,
|
||||
run_tests: bool = False,
|
||||
implement: bool = True,
|
||||
increment: bool = False,
|
||||
bug_fix: bool = False,
|
||||
):
|
||||
"""
|
||||
We are a software startup comprised of AI. By investing in us,
|
||||
|
|
@ -100,8 +130,10 @@ def main(
|
|||
:param code_review: Whether to use code review.
|
||||
:return:
|
||||
"""
|
||||
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
||||
asyncio.run(
|
||||
startup(idea, difference_description, path, investment, n_round, code_review, run_tests, implement, increment))
|
||||
startup(idea, difference_description, project_path, investment, n_round, code_review, run_tests, implement, increment, bug_fix))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue