update increment development, add bug fix function

This commit is contained in:
mannaandpoem 2023-11-24 18:22:06 +08:00
parent 1ea3c0c9f3
commit 8ff833f1e3
7 changed files with 334 additions and 80 deletions

View file

@ -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"

View file

@ -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], ...),

View file

@ -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, ...),
}

View 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

View file

@ -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()

View file

@ -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]:

View file

@ -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__":