add increment development function

This commit is contained in:
mannaandpoem 2023-11-21 14:38:19 +08:00
parent adf40e6783
commit 8a73d5cadb
12 changed files with 943 additions and 28 deletions

View file

@ -15,6 +15,7 @@ from metagpt.actions.design_api import WriteDesign
from metagpt.actions.design_api_review import DesignReview
from metagpt.actions.design_filenames import DesignFilenames
from metagpt.actions.project_management import AssignTasks, WriteTasks
from metagpt.actions.refine import Refine
from metagpt.actions.research import CollectLinks, WebBrowseAndSummarize, ConductResearch
from metagpt.actions.run_code import RunCode
from metagpt.actions.search_and_summarize import SearchAndSummarize

10
metagpt/actions/refine.py Normal file
View file

@ -0,0 +1,10 @@
from metagpt.actions import Action
# 增量开发动作的基类
class Refine(Action):
def __init__(self, name="Refine", context=None, llm=None):
super().__init__(name, context, llm)
def run(self, *args, **kwargs):
raise NotImplementedError

View file

@ -0,0 +1,231 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import shutil
from pathlib import Path
from typing import List
from metagpt.actions import Action, ActionOutput
from metagpt.config import CONFIG
from metagpt.const import WORKSPACE_ROOT
from metagpt.logs import logger
from metagpt.utils.common import CodeParser
from metagpt.utils.get_template import get_template
from metagpt.utils.json_to_markdown import json_to_markdown
from metagpt.utils.mermaid import mermaid_to_file
templates = {
"json": {
"PROMPT_TEMPLATE": """
# Context
{context}
## Legacy Design
{legacy}
## 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
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.
## Difference Description: Provided as Python list[str], the list of differences between the new design and the legacy design
## Implementation approach: Provide as Plain text. Analyze the difficult points of the requirements, select the appropriate open-source framework.
## Python package name: Provide as Python str with python triple quoto, concise and clear, characters only use a combination of all lowercase and underscores
## File list: Provided as Python list[str], the list of ONLY REQUIRED files needed to write the program(LESS IS MORE!). Only need relative paths, comply with PEP8 standards. ALWAYS write a main.py or app.py here
## Data structures and interface definitions: Use mermaid classDiagram code syntax, including classes (INCLUDING __init__ method) and functions (with type annotations), CLEARLY MARK the RELATIONSHIPS between classes, and comply with PEP8 standards. The data structures SHOULD BE VERY DETAILED and the API should be comprehensive with a complete design.
## Program call flow: Use sequenceDiagram code syntax, COMPLETE and VERY DETAILED, using CLASSES AND API DEFINED ABOVE accurately, covering the CRUD AND INIT of each object, SYNTAX MUST BE CORRECT.
## Anything UNCLEAR: Provide as Plain text. Make clear here.
output a properly formatted JSON, wrapped inside [CONTENT][/CONTENT] like format example,
and only output the json inside this tag, nothing else
""",
"FORMAT_EXAMPLE": """
[CONTENT]
{
"Difference Description": ["The ..."],
"Implementation approach": "We will ...",
"Python package name": "snake_game",
"File list": ["main.py"],
"Data structures and interface definitions": '
classDiagram
class Game{
+int score
}
...
Game "1" -- "1" Food: has
',
"Program call flow": '
sequenceDiagram
participant M as Main
...
G->>M: end game
',
"Anything UNCLEAR": "The requirement is clear to me."
}
[/CONTENT]
""",
},
"markdown": {
"PROMPT_TEMPLATE": """
# Context
{context}
## Legacy Design
{legacy}
## 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
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.
## Difference Description: Provided as Python list[str], the list of differences between the new design and the legacy design
## Implementation approach: Provide as Plain text. Analyze the difficult points of the requirements, select the appropriate open-source framework.
## Python package name: Provide as Python str with python triple quoto, concise and clear, characters only use a combination of all lowercase and underscores
## File list: Provided as Python list[str], the list of ONLY REQUIRED files needed to write the program(LESS IS MORE!). Only need relative paths, comply with PEP8 standards. ALWAYS write a main.py or app.py here
## Data structures and interface definitions: Use mermaid classDiagram code syntax, including classes (INCLUDING __init__ method) and functions (with type annotations), CLEARLY MARK the RELATIONSHIPS between classes, and comply with PEP8 standards. The data structures SHOULD BE VERY DETAILED and the API should be comprehensive with a complete design.
## Program call flow: Use sequenceDiagram code syntax, COMPLETE and VERY DETAILED, using CLASSES AND API DEFINED ABOVE accurately, covering the CRUD AND INIT of each object, SYNTAX MUST BE CORRECT.
## Anything UNCLEAR: Provide as Plain text. Make clear here.
""",
"FORMAT_EXAMPLE": """
---
## Difference Description
```python
[
"The ...",
]
```
## Implementation approach
We will ...
## Python package name
```python
"snake_game"
```
## File list
```python
[
"main.py",
]
```
## Data structures and interface definitions
```mermaid
classDiagram
class Game{
+int score
}
...
Game "1" -- "1" Food: has
```
## Program call flow
```mermaid
sequenceDiagram
participant M as Main
...
G->>M: end game
```
## Anything UNCLEAR
The requirement is clear to me.
---
""",
},
}
OUTPUT_MAPPING = {
"Difference Description": (List[str], ...),
"Implementation approach": (str, ...),
"Python package name": (str, ...),
"File list": (List[str], ...),
"Data structures and interface definitions": (str, ...),
"Program call flow": (str, ...),
"Anything UNCLEAR": (str, ...),
}
class RefineDesign(Action):
def __init__(self, name, context=None, llm=None):
super().__init__(name, context, llm)
self.desc = (
"Based on the PRD, think about the system design, and design the corresponding APIs, "
"data structures, library tables, processes, and paths. Please provide your design, feedback "
"clearly and in detail."
)
def recreate_workspace(self, workspace: Path):
try:
shutil.rmtree(workspace)
except FileNotFoundError:
pass # Folder does not exist, but we don't care
workspace.mkdir(parents=True, exist_ok=True)
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"]:
quadrant_chart = context[-1].instruct_content.dict()["Competitive Quadrant Chart"]
await mermaid_to_file(quadrant_chart, resources_path / "competitive_analysis")
if context[-1].instruct_content:
logger.info(f"Saving PRD to {prd_file}")
prd_file.write_text(json_to_markdown(context[-1].instruct_content.dict()))
async def _save_system_design(self, docs_path, resources_path, system_design):
data_api_design = system_design.instruct_content.dict()[
"Data structures and interface definitions"
] # CodeParser.parse_code(block="Data structures and interface definitions", text=content)
seq_flow = system_design.instruct_content.dict()[
"Program call flow"
] # CodeParser.parse_code(block="Program call flow", text=content)
await mermaid_to_file(data_api_design, resources_path / "data_api_design")
await mermaid_to_file(seq_flow, resources_path / "seq_flow")
system_design_file = docs_path / "system_design.md"
logger.info(f"Saving System Designs to {system_design_file}")
system_design_file.write_text((json_to_markdown(system_design.instruct_content.dict())))
async def _save(self, context, system_design):
if isinstance(system_design, ActionOutput):
ws_name = system_design.instruct_content.dict()["Python package name"]
else:
ws_name = CodeParser.parse_str(block="Python package name", text=system_design)
workspace = WORKSPACE_ROOT / ws_name
self.recreate_workspace(workspace)
docs_path = workspace / "docs"
resources_path = workspace / "resources"
docs_path.mkdir(parents=True, exist_ok=True)
resources_path.mkdir(parents=True, exist_ok=True)
await self._save_prd(docs_path, resources_path, context)
await self._save_system_design(docs_path, resources_path, system_design)
async def run(self, context, legacy, format=CONFIG.prompt_format):
prompt_template, format_example = get_template(templates, format)
prompt = prompt_template.format(context=context, legacy=legacy, format_example=format_example)
# system_design = await self._aask(prompt)
system_design = await self._aask_v1(prompt, "system_design", OUTPUT_MAPPING, format=format)
# fix Python package name, we can't system_design.instruct_content.python_package_name = "xxx" since "Python package name" contain space, have to use setattr
setattr(
system_design.instruct_content,
"Python package name",
system_design.instruct_content.dict()["Python package name"].strip().strip("'").strip('"'),
)
await self._save(context, system_design)
return system_design

View file

@ -0,0 +1,260 @@
from typing import List
from metagpt.actions import Refine, ActionOutput, SearchAndSummarize
from metagpt.config import CONFIG
from metagpt.logs import logger
from metagpt.utils.get_template import get_template
increment_template = {
"json": {
"PROMPT_TEMPLATE": """
# Context
## User's New Requirements
{new_requirements}
## Difference Description
{difference_description}
## Legacy PRD
{legacy}
## Search Information
{search_information}
## mermaid quadrantChart code syntax example. DONT USE QUOTO IN CODE DUE TO INVALID SYNTAX. Replace the <Campain X> with REAL COMPETITOR NAME
```mermaid
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
"Campaign: A": [0.3, 0.6]
"Campaign B": [0.45, 0.23]
"Campaign C": [0.57, 0.69]
"Campaign D": [0.78, 0.34]
"Campaign E": [0.40, 0.34]
"Campaign F": [0.35, 0.78]
"Our Target Product": [0.5, 0.6]
```
## 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.
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
## 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
## Competitive Analysis: Provided as Python list[str], up to 7 competitive product analyses, consider as similar competitors as possible
## Competitive Quadrant Chart: Use mermaid quadrantChart code syntax. up to 14 competitive products. Translation: Distribute these competitor scores evenly between 0 and 1, trying to conform to a normal distribution centered around 0.5 as much as possible.
## Requirement Analysis: Provide as Plain text. Be simple. LESS IS MORE. Make your requirements less dumb. Delete the parts unnessasery.
## Requirement Pool: Provided as Python list[list[str], the parameters are requirement description, priority(P0/P1/P2), respectively, comply with PEP standards; no more than 5 requirements and consider to make its difficulty lower
## UI Design draft: Provide as Plain text. Be simple. Describe the elements and functions, also provide a simple style description and layout description.
## Anything UNCLEAR: Provide as Plain text. Make clear here.
output a properly formatted JSON, wrapped inside [CONTENT][/CONTENT] like format example,
and only output the json inside this tag, nothing else
""",
"FORMAT_EXAMPLE": """
[CONTENT]
{
"New Requirements": "",
"Difference Description": "",
"Search Information": "",
"Requirements": "",
"Product Goals": [],
"User Stories": [],
"Competitive Analysis": [],
"Competitive Quadrant Chart": "quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]",
"Requirement Analysis": "",
"Requirement Pool": [["P0","P0 requirement"],["P1","P1 requirement"]],
"UI Design draft": "",
"Anything UNCLEAR": "",
}
[/CONTENT]
""",
},
"markdown": {
"PROMPT_TEMPLATE": """
# Context
You need to refine the requirements based on the new requirements and the existing requirements' output.
## User's New Requirements
{new_requirements}
## Difference Description
{difference_description}
## Legacy PRD
{legacy}
## Search Information
{search_information}
## mermaid quadrantChart code syntax example. DONT USE QUOTO IN CODE DUE TO INVALID SYNTAX. Replace the <Campain X> with REAL COMPETITOR NAME
```mermaid
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
"Campaign: A": [0.3, 0.6]
"Campaign B": [0.45, 0.23]
"Campaign C": [0.57, 0.69]
"Campaign D": [0.78, 0.34]
"Campaign E": [0.40, 0.34]
"Campaign F": [0.35, 0.78]
"Our Target Product": [0.5, 0.6]
```
## Format example
{format_example}
-----
Role: You are a professional product manager; the goal is to design a concise, usable, 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.
## 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
## 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
## Competitive Analysis: Provided as Python list[str], up to 7 competitive product analyses, consider as similar competitors as possible
## Competitive Quadrant Chart: Use mermaid quadrantChart code syntax. up to 14 competitive products. Translation: Distribute these competitor scores evenly between 0 and 1, trying to conform to a normal distribution centered around 0.5 as much as possible.
## Requirement Analysis: Provide as Plain text. Be simple. LESS IS MORE. Make your requirements less dumb. Delete the parts unnessasery.
## Requirement Pool: Provided as Python list[list[str], the parameters are requirement description, priority(P0/P1/P2), respectively, comply with PEP standards; no more than 5 requirements and consider to make its difficulty lower
## UI Design draft: Provide as Plain text. Be simple. Describe the elements and functions, also provide a simple style description and layout description.
## Anything UNCLEAR: Provide as Plain text. Make clear here.
""",
"FORMAT_EXAMPLE": """
---
## New Requirements
The boss ...
## Difference Description
```python
[
"...",
]
## Product Goals
```python
[
"Create a ...",
]
```
## User Stories
```python
[
"As a user, ...",
]
```
## Competitive Analysis
```python
[
"Python Snake Game: ...",
]
```
## Competitive Quadrant Chart
```mermaid
quadrantChart
title Reach and engagement of campaigns
...
"Our Target Product": [0.6, 0.7]
```
## Requirement Analysis
The product should be a ...
## Requirement Pool
```python
[
["End game ...", "P0"]
]
```
## UI Design draft
Give a basic function description, and a draft
## Anything UNCLEAR
There are no unclear points.
---
""",
},
}
INCREMENT_OUTPUT_MAPPING = {
"New Requirements": (str, ...),
# "Major Enhancements": (List[str], ...),
"Difference Description": (List[str], ...),
"Product Goals": (List[str], ...),
"User Stories": (List[str], ...),
"Competitive Analysis": (List[str], ...),
"Competitive Quadrant Chart": (str, ...),
"Requirement Analysis": (str, ...),
"Requirement Pool": (List[List[str]], ...),
"UI Design draft": (str, ...),
"Anything UNCLEAR": (str, ...),
}
# 对于产品经理增量开发的动作是RefinePDR输出是结合新需求和已有需求输出的新的PDR
class RefinePRD(Refine):
def __init__(self, name="RefinePRD", context=None, llm=None):
super().__init__(name, context, llm)
async def run(self, new_requirements, difference_description, legacy, format=CONFIG.prompt_format, *args, **kwargs):
sas = SearchAndSummarize()
rsp = ""
info = f"### Search Results\n{sas.result}\n\n### Search Summary\n{rsp}"
if sas.result:
logger.info(sas.result)
logger.info(rsp)
prompt_template, format_example = get_template(increment_template, format)
prompt = prompt_template.format(
new_requirements=new_requirements, difference_description=difference_description, legacy=legacy, search_information=info,
format_example=format_example
)
logger.debug(prompt)
prd = await self._aask_v1(prompt, "prd", INCREMENT_OUTPUT_MAPPING, format=format)
return prd

View file

@ -0,0 +1,208 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from typing import List
from metagpt.actions.action import Action
from metagpt.config import CONFIG
from metagpt.const import WORKSPACE_ROOT
from metagpt.utils.common import CodeParser
from metagpt.utils.get_template import get_template
from metagpt.utils.json_to_markdown import json_to_markdown
templates = {
"json": {
"PROMPT_TEMPLATE": """
# Context
{context}
## Legacy Design
{legacy}
## 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
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.
## Required Python third-party packages: Provided in requirements.txt format
## Required Other language third-party packages: Provided in requirements.txt format
## Full API spec: Use OpenAPI 3.0. Describe all APIs that may be used by both frontend and backend.
## 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,
and only output the json inside this tag, nothing else
""",
"FORMAT_EXAMPLE": '''
{
"Required Python third-party packages": [
"flask==1.1.2",
"bcrypt==3.2.0"
],
"Required Other language third-party packages": [
"No third-party ..."
],
"Full API spec": """
openapi: 3.0.0
...
description: A JSON object ...
""",
"Logic Analysis": [
["game.py","Contains..."]
],
"Task list": [
"game.py"
],
"Shared Knowledge": """
'game.py' contains ...
""",
"Difference Description": """
The ...
""",
"Anything UNCLEAR": "We need ... how to start."
}
''',
},
"markdown": {
"PROMPT_TEMPLATE": """
# Context
{context}
## Legacy Design
{legacy}
## 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
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.
## Required Python third-party packages: Provided in requirements.txt format
## Required Other language third-party packages: Provided in requirements.txt format
## Full API spec: Use OpenAPI 3.0. Describe all APIs that may be used by both frontend and backend.
## 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.
""",
"FORMAT_EXAMPLE": '''
---
## Required Python third-party packages
```python
"""
flask==1.1.2
bcrypt==3.2.0
"""
```
## Required Other language third-party packages
```python
"""
No third-party ...
"""
```
## Full API spec
```python
"""
openapi: 3.0.0
...
description: A JSON object ...
"""
```
## Logic Analysis
```python
[
["game.py", "Contains ..."],
]
```
## Task list
```python
[
"game.py",
]
```
## Shared Knowledge
```python
"""
'game.py' contains ...
"""
```
## Difference Description
```python
"""
The ...
"""
```
## Anything UNCLEAR
We need ... how to start.
---
''',
},
}
OUTPUT_MAPPING = {
"Required Python third-party packages": (List[str], ...),
"Required Other language third-party packages": (List[str], ...),
"Full API spec": (str, ...),
"Logic Analysis": (List[List[str]], ...),
"Task list": (List[str], ...),
"Shared Knowledge": (str, ...),
"Difference Description": (str, ...),
"Anything UNCLEAR": (str, ...),
}
class RefineTasks(Action):
def __init__(self, name="CreateTasks", context=None, llm=None):
super().__init__(name, context, llm)
def _save(self, context, rsp):
if context[-1].instruct_content:
ws_name = context[-1].instruct_content.dict()["Python package name"]
else:
ws_name = CodeParser.parse_str(block="Python package name", text=context[-1].content)
file_path = WORKSPACE_ROOT / ws_name / "docs/api_spec_and_tasks.md"
file_path.write_text(json_to_markdown(rsp.instruct_content.dict()))
# Write requirements.txt
requirements_path = WORKSPACE_ROOT / ws_name / "requirements.txt"
requirements_path.write_text("\n".join(rsp.instruct_content.dict().get("Required Python third-party packages")))
async def run(self, context, legacy, format=CONFIG.prompt_format):
prompt_template, format_example = get_template(templates, format)
prompt = prompt_template.format(context=context, legacy=legacy, format_example=format_example)
rsp = await self._aask_v1(prompt, "task", OUTPUT_MAPPING, format=format)
self._save(context, rsp)
return rsp
class AssignTasks(Action):
async def run(self, *args, **kwargs):
# Here you should implement the actual action
pass

View file

@ -7,6 +7,7 @@
"""
from metagpt.actions import WriteDesign
from metagpt.actions.action import Action
from metagpt.actions.refine_design_api import RefineDesign
from metagpt.const import WORKSPACE_ROOT
from metagpt.logs import logger
from metagpt.schema import Message
@ -55,7 +56,14 @@ class WriteCode(Action):
if self._is_invalid(filename):
return
design = [i for i in context if i.cause_by == WriteDesign][0]
# FIXME: 需要适配increment
# design = [i for i in context if i.cause_by == WriteDesign][0]
design = []
for i in context:
if i.cause_by == WriteDesign:
design.append(i)
elif i.cause_by == RefineDesign:
design.append(i)
ws_name = CodeParser.parse_str(block="Python package name", text=design.content)
ws_path = WORKSPACE_ROOT / ws_name

View file

@ -6,9 +6,13 @@
@File : architect.py
"""
from metagpt.actions import WritePRD
from metagpt.actions import WritePRD, ActionOutput
from metagpt.actions.design_api import WriteDesign
from metagpt.actions.refine_design_api import RefineDesign
from metagpt.actions.refine_prd import RefinePRD
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
class Architect(Role):
@ -23,17 +27,43 @@ class Architect(Role):
"""
def __init__(
self,
name: str = "Bob",
profile: str = "Architect",
goal: str = "Design a concise, usable, complete python system",
constraints: str = "Try to specify good open source tools as much as possible",
self,
name: str = "Bob",
profile: str = "Architect",
goal: str = "Design a concise, usable, complete python system",
constraints: str = "Try to specify good open source tools as much as possible",
legacy: str = "",
increment: bool = False,
) -> None:
"""Initializes the Architect with given attributes."""
super().__init__(name, profile, goal, constraints)
self.legacy = legacy
self.increment = increment
# Initialize actions specific to the Architect role
self._init_actions([WriteDesign])
# Set events or actions the Architect should watch or be aware of
self._watch({WritePRD})
if self.increment:
self._init_actions([RefineDesign])
self._watch({RefinePRD})
else:
self._init_actions([WriteDesign])
self._watch({WritePRD})
async def _act(self) -> Message:
if self.increment:
logger.info(f"{self._setting}: ready to RefineDesign")
response = await self._rc.todo.run(self._rc.history, self.legacy)
else:
logger.info(f"{self._setting}: ready to WriteDesign")
response = await self._rc.todo.run(self._rc.history)
if isinstance(response, ActionOutput):
msg = Message(content=response.content, instruct_content=response.instruct_content,
role=self.profile, cause_by=type(self._rc.todo))
else:
msg = Message(content=response, role=self.profile, cause_by=type(self._rc.todo))
self._rc.memory.add(msg)
logger.debug(f"{response}")
return msg

View file

@ -11,6 +11,8 @@ from collections import OrderedDict
from pathlib import Path
from metagpt.actions import WriteCode, WriteCodeReview, WriteDesign, WriteTasks
from metagpt.actions.refine_design_api import RefineDesign
from metagpt.actions.refine_project_management import RefineTasks
from metagpt.const import WORKSPACE_ROOT
from metagpt.logs import logger
from metagpt.roles import Role
@ -68,14 +70,21 @@ class Engineer(Role):
constraints: str = "The code should conform to standards like PEP8 and be modular and maintainable",
n_borg: int = 1,
use_code_review: bool = False,
legacy: str = "",
increment: bool = False,
) -> None:
"""Initializes the Engineer role with given attributes."""
super().__init__(name, profile, goal, constraints)
self._init_actions([WriteCode])
self.use_code_review = use_code_review
if self.use_code_review:
self.legacy = legacy
self.increment = increment
if self.use_code_review or self.increment:
self._init_actions([WriteCode, WriteCodeReview])
self._watch([WriteTasks])
if self.increment:
self._watch([RefineTasks])
else:
self._watch([WriteTasks])
self.todos = []
self.n_borg = n_borg
@ -96,7 +105,10 @@ class Engineer(Role):
return CodeParser.parse_str(block="Python package name", text=system_design_msg.content)
def get_workspace(self) -> Path:
msg = self._rc.memory.get_by_action(WriteDesign)[-1]
if self.increment:
msg = self._rc.memory.get_by_action(RefineDesign)[-1]
else:
msg = self._rc.memory.get_by_action(WriteDesign)[-1]
if not msg:
return WORKSPACE_ROOT / "src"
workspace = self.parse_workspace(msg)
@ -167,6 +179,55 @@ class Engineer(Role):
)
return msg
async def _act_increment(self, legacy) -> Message:
code_msg_all = [] # gather all code info, will pass to qa_engineer for tests later
flag = True
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])
for m in msg:
context.append(m.content)
context_str = "\n".join(context)
# Refine code or Write code
if flag and self.increment:
code = legacy
flag = False
else:
code = await WriteCode().run(context=context_str, filename=todo)
# 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)
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_sp_precision(self) -> Message:
code_msg_all = [] # gather all code info, will pass to qa_engineer for tests later
for todo in self.todos:
@ -207,7 +268,12 @@ class Engineer(Role):
async def _act(self) -> Message:
"""Determines the mode of action based on whether code review is used."""
logger.info(f"{self._setting}: ready to WriteCode")
if self.increment:
logger.info(f"{self._setting}: ready to RefineWriteCode")
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)
return await self._act_sp()

View file

@ -5,8 +5,11 @@
@Author : alexanderwu
@File : product_manager.py
"""
from metagpt.actions import BossRequirement, WritePRD
from metagpt.actions import BossRequirement, WritePRD, ActionOutput
from metagpt.actions.refine_prd import RefinePRD
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
class ProductManager(Role):
@ -26,6 +29,9 @@ class ProductManager(Role):
profile: str = "Product Manager",
goal: str = "Efficiently create a successful product",
constraints: str = "",
difference_description: str = "",
legacy: str = "",
increment: bool = False,
) -> None:
"""
Initializes the ProductManager role with given attributes.
@ -37,5 +43,31 @@ class ProductManager(Role):
constraints (str): Constraints or limitations for the product manager.
"""
super().__init__(name, profile, goal, constraints)
self._init_actions([WritePRD])
self.difference_description = difference_description
self.legacy = legacy
self.increment = increment
if self.increment:
self._init_actions([RefinePRD])
else:
self._init_actions([WritePRD])
self._watch([BossRequirement])
async def _act(self) -> Message:
if self.increment:
logger.info(f"{self._setting}: ready to RefinePRD")
response = await self._rc.todo.run(self._rc.history, self.difference_description, self.legacy)
else:
logger.info(f"{self._setting}: ready to WritePRD")
response = await self._rc.todo.run(self._rc.history)
if isinstance(response, ActionOutput):
msg = Message(content=response.content, instruct_content=response.instruct_content,
role=self.profile, cause_by=type(self._rc.todo))
else:
msg = Message(content=response, role=self.profile, cause_by=type(self._rc.todo))
self._rc.memory.add(msg)
logger.debug(f"{response}")
return msg

View file

@ -5,9 +5,13 @@
@Author : alexanderwu
@File : project_manager.py
"""
from metagpt.actions import WriteTasks
from metagpt.actions import WriteTasks, ActionOutput
from metagpt.actions.design_api import WriteDesign
from metagpt.actions.refine_design_api import RefineDesign
from metagpt.actions.refine_project_management import RefineTasks
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
class ProjectManager(Role):
@ -27,6 +31,8 @@ class ProjectManager(Role):
profile: str = "Project Manager",
goal: str = "Improve team efficiency and deliver with quality and quantity",
constraints: str = "",
increment: bool = False,
legacy: str = "",
) -> None:
"""
Initializes the ProjectManager role with given attributes.
@ -38,5 +44,31 @@ class ProjectManager(Role):
constraints (str): Constraints or limitations for the project manager.
"""
super().__init__(name, profile, goal, constraints)
self._init_actions([WriteTasks])
self._watch([WriteDesign])
self.increment = increment
self.legacy = legacy
if self.increment:
self._init_actions([RefineTasks])
self._watch([RefineDesign])
else:
self._init_actions([WriteTasks])
self._watch([WriteDesign])
async def _act(self) -> Message:
if self.increment:
logger.info(f"{self._setting}: ready to RefineTasks")
response = await self._rc.todo.run(self._rc.history, self.legacy)
else:
logger.info(f"{self._setting}: ready to WriteTasks")
response = await self._rc.todo.run(self._rc.history)
if isinstance(response, ActionOutput):
msg = Message(content=response.content, instruct_content=response.instruct_content,
role=self.profile, cause_by=type(self._rc.todo))
else:
msg = Message(content=response, role=self.profile, cause_by=type(self._rc.todo))
self._rc.memory.add(msg)
logger.debug(f"{response}")
return msg

View file

@ -205,6 +205,7 @@ class Role:
# history=self.history)
logger.info(f"{self._setting}: ready to {self._rc.todo}")
response = await self._rc.todo.run(self._rc.important_memory)
# logger.info(response)
if isinstance(response, ActionOutput):

View file

@ -1,6 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import asyncio
import os
import fire
@ -16,26 +17,57 @@ from metagpt.team import Team
async def startup(
idea: str,
difference_description: str = "",
path: str = "",
investment: float = 3.0,
n_round: int = 5,
code_review: bool = False,
run_tests: bool = False,
implement: bool = True,
increment: bool = False,
):
"""Run a startup. Be a boss."""
company = Team()
company.hire(
[
ProductManager(),
Architect(),
ProjectManager(),
]
)
if increment:
# 读取文件
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))
with open(prd_path, 'r', encoding='utf-8') as f:
legacy_prd = f.read()
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()
# 遍历文件夹,获取所有代码文件
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'
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'
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)])
else:
company.hire([ProductManager(), Architect(), ProjectManager()])
# if implement or code_review
if implement or code_review:
if (implement or code_review) and not increment:
# 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:
# developing features: run tests on the spot and identify bugs
@ -49,11 +81,14 @@ async def startup(
def main(
idea: str,
difference_description: str = "",
path: str = "",
investment: float = 3.0,
n_round: int = 5,
code_review: bool = True,
run_tests: bool = False,
implement: bool = True,
increment: bool = False,
):
"""
We are a software startup comprised of AI. By investing in us,
@ -65,7 +100,8 @@ def main(
:param code_review: Whether to use code review.
:return:
"""
asyncio.run(startup(idea, investment, n_round, code_review, run_tests, implement))
asyncio.run(
startup(idea, difference_description, path, investment, n_round, code_review, run_tests, implement, increment))
if __name__ == "__main__":