mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-04-29 10:56:22 +02:00
Transfer Action usage to ActionNode for subsequent structured reasoning opportunities
- Modifided actions: project_management / design_api / write_prd
This commit is contained in:
parent
5d7c228539
commit
c0bcf57caf
15 changed files with 438 additions and 820 deletions
|
|
@ -27,18 +27,22 @@ class Action(ABC):
|
|||
self.context = context
|
||||
self.prefix = "" # aask*时会加上prefix,作为system_message
|
||||
self.profile = "" # FIXME: USELESS
|
||||
self.desc = "" # FIXME: USELESS
|
||||
self.content = ""
|
||||
self.instruct_content = None
|
||||
self.env = None
|
||||
self.desc = "" # for skill manager
|
||||
self.nodes = ...
|
||||
|
||||
def set_env(self, env):
|
||||
self.env = env
|
||||
# Output, useless
|
||||
# self.content = ""
|
||||
# self.instruct_content = None
|
||||
# self.env = None
|
||||
|
||||
# def set_env(self, env):
|
||||
# self.env = env
|
||||
|
||||
def set_prefix(self, prefix, profile):
|
||||
"""Set prefix for later usage"""
|
||||
self.prefix = prefix
|
||||
self.profile = profile
|
||||
self.llm.system_prompt = prefix
|
||||
|
||||
def __str__(self):
|
||||
return self.__class__.__name__
|
||||
|
|
@ -62,10 +66,6 @@ class Action(ABC):
|
|||
system_msgs: Optional[list[str]] = None,
|
||||
format="markdown", # compatible to original format
|
||||
) -> ActionOutput:
|
||||
"""Append default prefix"""
|
||||
if not system_msgs:
|
||||
system_msgs = []
|
||||
system_msgs.append(self.prefix)
|
||||
content = await self.llm.aask(prompt, system_msgs)
|
||||
logger.debug(content)
|
||||
output_class = ActionOutput.create_model_class(output_class_name, output_data_mapping)
|
||||
|
|
|
|||
|
|
@ -5,25 +5,44 @@
|
|||
@Author : alexanderwu
|
||||
@File : action_node.py
|
||||
"""
|
||||
from typing import Dict, Type, List, Any, Tuple
|
||||
import re
|
||||
from typing import Dict, Type, List, Any, Tuple, Optional
|
||||
import json
|
||||
|
||||
from pydantic import BaseModel, create_model, root_validator, validator
|
||||
# , model_validator, field_validator
|
||||
from tenacity import wait_random_exponential, stop_after_attempt, retry
|
||||
|
||||
from metagpt.actions import ActionOutput
|
||||
from metagpt.llm import BaseGPTAPI
|
||||
from metagpt.logs import logger
|
||||
from metagpt.utils.common import OutputParser
|
||||
from metagpt.utils.custom_decoder import CustomDecoder
|
||||
|
||||
CONSTRAINT = """
|
||||
- Language: Please use the same language as the user input.
|
||||
- Format: output wrapped inside [CONTENT][/CONTENT] as format example, nothing else.
|
||||
"""
|
||||
|
||||
SIMPLE_TEMPLATE = """
|
||||
## example
|
||||
## context
|
||||
{context}
|
||||
|
||||
## format example
|
||||
{example}
|
||||
|
||||
## instruction
|
||||
## nodes: "<node>: <type> # <comment>"
|
||||
{instruction}
|
||||
|
||||
## constraint
|
||||
{constraint}
|
||||
|
||||
## action
|
||||
Fill in the above nodes based on the context. Answer in format example.
|
||||
"""
|
||||
|
||||
|
||||
def dict_to_markdown(d, prefix="###", postfix="\n"):
|
||||
def dict_to_markdown(d, prefix="-", postfix="\n"):
|
||||
markdown_str = ""
|
||||
for key, value in d.items():
|
||||
markdown_str += f"{prefix} {key}: {value}{postfix}"
|
||||
|
|
@ -32,22 +51,26 @@ def dict_to_markdown(d, prefix="###", postfix="\n"):
|
|||
|
||||
class ActionNode:
|
||||
"""ActionNode is a tree of nodes."""
|
||||
# 应该是定义子任务,收集子任务结果,并且父任务同时执行吗?
|
||||
# 初期只提供两种模式,一种是用父任务compile,一种是用子任务逐个执行
|
||||
# 1. context、example、instruction-nodes、instruction-action
|
||||
# 2. context、example
|
||||
# Action Strgy
|
||||
# - sop: 仅使用一级SOP
|
||||
# - complex: 使用一级SOP+自定义策略填槽
|
||||
mode: str
|
||||
|
||||
# Action Inputs
|
||||
# Action Context
|
||||
context: str # all the context, including all necessary info
|
||||
llm: BaseGPTAPI # LLM with aask interface
|
||||
children: dict[str, "ActionNode"]
|
||||
|
||||
# Action Input
|
||||
key: str # Product Requirement / File list / Code
|
||||
expected_type: Type # such as str / int / float etc.
|
||||
# context: str # everything in the history.
|
||||
instruction: str # the instructions should be followed.
|
||||
example: Any # example for In Context-Learning.
|
||||
|
||||
# Action Outputs
|
||||
# Action Output
|
||||
content: str
|
||||
instruct_content: BaseModel
|
||||
children: dict[str, "ActionNode"]
|
||||
|
||||
def __init__(self, key, expected_type, instruction, example, content="",
|
||||
children=None):
|
||||
|
|
@ -74,9 +97,16 @@ class ActionNode:
|
|||
for node in nodes:
|
||||
self.add_child(node)
|
||||
|
||||
@classmethod
|
||||
def from_children(cls, key, nodes: List["ActionNode"]):
|
||||
"""直接从一系列的子nodes初始化"""
|
||||
obj = cls(key, str, "", "")
|
||||
obj.add_children(nodes)
|
||||
return obj
|
||||
|
||||
def get_children_mapping(self) -> Dict[str, Type]:
|
||||
"""获得子ActionNode的字典,以key索引"""
|
||||
return {k: v.expected_type for k, v in self.children.items()}
|
||||
return {k: (v.expected_type, ...) for k, v in self.children.items()}
|
||||
|
||||
@classmethod
|
||||
def create_model_class(cls, class_name: str, mapping: Dict[str, Type]):
|
||||
|
|
@ -131,6 +161,8 @@ class ActionNode:
|
|||
return self.create_model_class(class_name, mapping)
|
||||
|
||||
def to_dict(self, format_func=None, mode="all") -> Dict:
|
||||
"""将当前节点与子节点都按照node: format的格式组织称字典"""
|
||||
|
||||
# 如果没有提供格式化函数,使用默认的格式化方式
|
||||
if format_func is None:
|
||||
format_func = lambda node: f"{node.instruction}"
|
||||
|
|
@ -165,7 +197,7 @@ class ActionNode:
|
|||
if not tag:
|
||||
return text
|
||||
if to == "json":
|
||||
return f"[{tag}]\n" + "{" + text + "}" + f"\n[/{tag}]"
|
||||
return f"[{tag}]\n" + text + f"\n[/{tag}]"
|
||||
else:
|
||||
return f"[{tag}]\n" + text + f"\n[/{tag}]"
|
||||
|
||||
|
|
@ -187,31 +219,73 @@ class ActionNode:
|
|||
format_func = lambda i: i.example
|
||||
return self._compile_f(to, mode, tag, format_func)
|
||||
|
||||
def compile(self, mode="children") -> Tuple[str, str]:
|
||||
def compile(self, context, to="json", mode="children", template=SIMPLE_TEMPLATE) -> str:
|
||||
"""
|
||||
mode: all/root/children
|
||||
mode="children": 编译所有子节点为一个统一模板,包括instruction与example
|
||||
mode="all": NotImplemented
|
||||
mode="root": NotImplemented
|
||||
"""
|
||||
self.instruction = self.compile_instruction(to="json", mode=mode)
|
||||
self.example = self.compile_example(to="json", tag="CONTENT", mode=mode)
|
||||
# prompt = template.format(example=self.example, instruction=self.instruction)
|
||||
return self.instruction, self.example
|
||||
|
||||
def run(self):
|
||||
"""运行这个ActionNode,可以采用不同策略,比如只运行子节点"""
|
||||
# FIXME: json instruction会带来 "Project name": "web_2048 # 项目名称使用下划线",
|
||||
self.instruction = self.compile_instruction(to="markdown", mode=mode)
|
||||
self.example = self.compile_example(to=to, tag="CONTENT", mode=mode)
|
||||
prompt = template.format(context=context, example=self.example, instruction=self.instruction,
|
||||
constraint=CONSTRAINT)
|
||||
return prompt
|
||||
|
||||
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
|
||||
async def _aask_v1(
|
||||
self,
|
||||
prompt: str,
|
||||
output_class_name: str,
|
||||
output_data_mapping: dict,
|
||||
system_msgs: Optional[list[str]] = None,
|
||||
format="markdown", # compatible to original format
|
||||
) -> ActionOutput:
|
||||
content = await self.llm.aask(prompt, system_msgs)
|
||||
logger.debug(content)
|
||||
output_class = ActionOutput.create_model_class(output_class_name, output_data_mapping)
|
||||
|
||||
if format == "json":
|
||||
pattern = r"\[CONTENT\](\s*\{.*?\}\s*)\[/CONTENT\]"
|
||||
matches = re.findall(pattern, content, re.DOTALL)
|
||||
|
||||
for match in matches:
|
||||
if match:
|
||||
content = match
|
||||
break
|
||||
|
||||
parsed_data = CustomDecoder(strict=False).decode(content)
|
||||
|
||||
else: # using markdown parser
|
||||
parsed_data = OutputParser.parse_data_with_mapping(content, output_data_mapping)
|
||||
|
||||
logger.debug(parsed_data)
|
||||
instruct_content = output_class(**parsed_data)
|
||||
return ActionOutput(content, instruct_content)
|
||||
|
||||
def get(self, key):
|
||||
return self.instruct_content.dict()[key]
|
||||
|
||||
async def fill(self, context, llm, to="json"):
|
||||
"""运行这个ActionNode,并且填槽,可以采用不同策略,比如只运行子节点"""
|
||||
self.llm = llm
|
||||
prompt = self.compile(context=context, to=to)
|
||||
mapping = self.get_children_mapping()
|
||||
|
||||
class_name = f"{self.key}_AN"
|
||||
# 需要传入llm,并且实际在ActionNode中执行。需要规划好具体的执行方法
|
||||
raise NotImplementedError
|
||||
output = await self._aask_v1(prompt, class_name, mapping, format=to)
|
||||
self.content = output.content
|
||||
self.instruct_content = output.instruct_content
|
||||
return self
|
||||
|
||||
|
||||
def action_node_from_tuple_example():
|
||||
# 示例:列表中包含元组
|
||||
list_of_tuples = [
|
||||
("key1", str, "Instruction 1", "Example 1", "Content 1", {"child1": ...}),
|
||||
("key2", int, "Instruction 2", "Example 2", "Content 2"),
|
||||
("key3", int, "Instruction 3", "Example 3")
|
||||
("key1", int, "Instruction 1", "Example 1")
|
||||
]
|
||||
|
||||
# 从列表中创建 ActionNode 实例
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@
|
|||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
# from typing import List
|
||||
|
||||
from metagpt.actions import Action, ActionOutput
|
||||
from metagpt.actions.design_api_an import DESIGN_API_NODE, SIMPLE_TEMPLATE
|
||||
from metagpt.actions.design_api_an import DESIGN_API_NODE
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.const import (
|
||||
DATA_API_DESIGN_FILE_REPO,
|
||||
|
|
@ -26,166 +26,15 @@ from metagpt.const import (
|
|||
from metagpt.logs import logger
|
||||
from metagpt.schema import Document, Documents
|
||||
from metagpt.utils.file_repository import FileRepository
|
||||
from metagpt.utils.get_template import get_template
|
||||
# from metagpt.utils.get_template import get_template
|
||||
from metagpt.utils.mermaid import mermaid_to_file
|
||||
|
||||
templates = {
|
||||
"json": {
|
||||
"PROMPT_TEMPLATE": """
|
||||
# Context
|
||||
{context}
|
||||
|
||||
## Format example
|
||||
{format_example}
|
||||
-----
|
||||
Role: You are an architect; the goal is to design a SOTA PEP8-compliant python system
|
||||
Language: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.
|
||||
Requirement: Fill in the following missing information based on the context, each section name is a key in json
|
||||
|
||||
## Implementation approach: Provide as Plain text. Analyze the difficult points of the requirements, select appropriate open-source frameworks.
|
||||
|
||||
## Project name: Constant text.
|
||||
|
||||
## File list: Provided as Python list[str], the list of files needed (including HTML & CSS IF NEEDED) to write the program. Only need relative paths. ALWAYS write a main.py or app.py here
|
||||
|
||||
## Data structures and interfaces: 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. Try to clarify it.
|
||||
|
||||
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]
|
||||
{{
|
||||
"Implementation approach": "We will ...",
|
||||
"Project name": "{project_name}",
|
||||
"File list": ["main.py"],
|
||||
"Data structures and interfaces": '
|
||||
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}
|
||||
|
||||
## 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
|
||||
Language: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.
|
||||
Requirement: Fill in the following missing information based on the context, note that all sections are response with code form separately
|
||||
ATTENTION: Output carefully referenced "Format example" in format.
|
||||
|
||||
## Implementation approach: Provide as Plain text. Analyze the difficult points of the requirements, select the appropriate open-source framework.
|
||||
|
||||
## Project name: Constant text.
|
||||
|
||||
## File list: Provided as Python list[str], the list of code files (including HTML & CSS IF NEEDED) to write the program. Only need relative paths. ALWAYS write a main.py or app.py here
|
||||
|
||||
## Data structures and interfaces: 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. Try to clarify it.
|
||||
|
||||
""",
|
||||
"FORMAT_EXAMPLE": """
|
||||
---
|
||||
## Implementation approach
|
||||
We will ...
|
||||
|
||||
## Project name
|
||||
```python
|
||||
"{project_name}"
|
||||
```
|
||||
|
||||
## File list
|
||||
```python
|
||||
[
|
||||
"main.py",
|
||||
]
|
||||
```
|
||||
|
||||
## Data structures and interfaces
|
||||
```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 = {
|
||||
"Implementation approach": (str, ...),
|
||||
"Project name": (str, ...),
|
||||
"File list": (List[str], ...),
|
||||
"Data structures and interfaces": (str, ...),
|
||||
"Program call flow": (str, ...),
|
||||
"Anything UNCLEAR": (str, ...),
|
||||
}
|
||||
|
||||
MERGE_PROMPT = """
|
||||
## Old Design
|
||||
NEW_REQ_TEMPLATE = """
|
||||
### Legacy Content
|
||||
{old_design}
|
||||
|
||||
## Context
|
||||
### New Requirements
|
||||
{context}
|
||||
|
||||
-----
|
||||
Role: You are an architect; The goal is to incrementally update the "Old Design" based on the information provided by the "Context," aiming to design a SOTA PEP8-compliant python system; make the best use of good open source tools
|
||||
Language: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.
|
||||
Requirement: Fill in the following missing information based on the context, note that all sections are response with code form separately
|
||||
ATTENTION: Output carefully referenced "Old Design" in format.
|
||||
|
||||
## Implementation approach: Provide as Plain text. Analyze the difficult points of the requirements, select the appropriate open-source framework.
|
||||
|
||||
## Project name: Constant text "{project_name}".
|
||||
|
||||
## File list: Provided as Python list[str], the list of code files (including HTML & CSS IF NEEDED) to write the program. Only need relative paths. ALWAYS write a main.py or app.py here
|
||||
|
||||
## Data structures and interfaces: 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. Try to clarify it.
|
||||
|
||||
output a properly formatted JSON, wrapped inside [CONTENT][/CONTENT] like "Old Design" format,
|
||||
and only output the json inside this tag, nothing else
|
||||
"""
|
||||
|
||||
|
||||
|
|
@ -228,30 +77,16 @@ class WriteDesign(Action):
|
|||
# leaving room for global optimization in subsequent steps.
|
||||
return ActionOutput(content=changed_files.json(), instruct_content=changed_files)
|
||||
|
||||
async def _new_system_design_bakup(self, context, format=CONFIG.prompt_format):
|
||||
prompt_template, format_example = get_template(templates, format)
|
||||
format_example = format_example.format(project_name=CONFIG.project_name)
|
||||
prompt = prompt_template.format(context=context, format_example=format_example)
|
||||
system_design = await self._aask_v1(prompt, "system_design", OUTPUT_MAPPING, format=format)
|
||||
return system_design
|
||||
|
||||
async def _new_system_design(self, context, format=CONFIG.prompt_format):
|
||||
instruction, example = DESIGN_API_NODE.compile()
|
||||
prompt = SIMPLE_TEMPLATE.format(context=context, example=example, instruction=instruction)
|
||||
# prompt_template, format_example = get_template(templates, format)
|
||||
# format_example = format_example.format(project_name=CONFIG.project_name)
|
||||
# prompt = prompt_template.format(context=context, format_example=format_example)
|
||||
system_design = await self._aask_v1(prompt, "system_design", OUTPUT_MAPPING, format=format)
|
||||
return system_design
|
||||
node = await DESIGN_API_NODE.fill(context=context, llm=self.llm, to=format)
|
||||
return node
|
||||
|
||||
async def _merge(self, prd_doc, system_design_doc, format=CONFIG.prompt_format):
|
||||
prompt = MERGE_PROMPT.format(
|
||||
old_design=system_design_doc.content, context=prd_doc.content, project_name=CONFIG.project_name
|
||||
context = NEW_REQ_TEMPLATE.format(
|
||||
old_design=system_design_doc.content, context=prd_doc.content
|
||||
)
|
||||
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
|
||||
system_design_doc.content = system_design.instruct_content.json(ensure_ascii=False)
|
||||
node = await DESIGN_API_NODE.fill(context=context, llm=self.llm, to=format)
|
||||
system_design_doc.content = node.instruct_content.json(ensure_ascii=False)
|
||||
return system_design_doc
|
||||
|
||||
async def _update_system_design(self, filename, prds_file_repo, system_design_file_repo) -> Document:
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
@File : design_api_an.py
|
||||
"""
|
||||
from metagpt.actions.action_node import ActionNode
|
||||
from metagpt.utils.mermaid import MMC1, MMC2
|
||||
from metagpt.logs import logger
|
||||
|
||||
IMPLEMENTATION_APPROACH = ActionNode(
|
||||
|
|
@ -32,60 +33,10 @@ FILE_LIST = ActionNode(
|
|||
DATA_STRUCTURES_AND_INTERFACES = ActionNode(
|
||||
key="Data structures and interfaces",
|
||||
expected_type=str,
|
||||
instruction="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. "
|
||||
instruction="Use mermaid classDiagram code syntax, including classes, method(__init__ etc.) 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.",
|
||||
example=""" classDiagram
|
||||
class User {
|
||||
+int id
|
||||
+str username
|
||||
+str email
|
||||
+str password
|
||||
__init__(id: int, username: str, email: str, password: str)
|
||||
follow(user: User): void
|
||||
like(content: Content): void
|
||||
comment(content: Content, text: str): Comment
|
||||
}
|
||||
class Content {
|
||||
+int id
|
||||
+User author
|
||||
+str title
|
||||
+str body
|
||||
+datetime created_at
|
||||
+list likes
|
||||
+list comments
|
||||
__init__(id: int, author: User, title: str, body: str)
|
||||
get_likes(): list
|
||||
get_comments(): list
|
||||
}
|
||||
class Comment {
|
||||
+int id
|
||||
+User author
|
||||
+str text
|
||||
+datetime created_at
|
||||
__init__(id: int, author: User, text: str)
|
||||
}
|
||||
class Leaderboard {
|
||||
+list top_contents
|
||||
update(): void
|
||||
}
|
||||
class SearchEngine {
|
||||
+str query
|
||||
search(): list
|
||||
}
|
||||
class RecommendationEngine {
|
||||
+User user
|
||||
recommend(): list
|
||||
}
|
||||
class TaskQueue {
|
||||
+str task_name
|
||||
enqueue(task: function): void
|
||||
}
|
||||
User "1" -- "*" Content: creates
|
||||
Content "1" -- "*" Comment: includes
|
||||
User "1" -- "*" Comment: writes
|
||||
User "1" -- "*" User: follows
|
||||
Content "1" -- "*" User: liked_by"""
|
||||
example=MMC1
|
||||
)
|
||||
|
||||
PROGRAM_CALL_FLOW = ActionNode(
|
||||
|
|
@ -93,10 +44,7 @@ PROGRAM_CALL_FLOW = ActionNode(
|
|||
expected_type=str,
|
||||
instruction="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.",
|
||||
example="""sequenceDiagram
|
||||
participant M as Main
|
||||
...
|
||||
G->>M: end game"""
|
||||
example=MMC2
|
||||
)
|
||||
|
||||
ANYTHING_UNCLEAR = ActionNode(
|
||||
|
|
@ -106,40 +54,21 @@ ANYTHING_UNCLEAR = ActionNode(
|
|||
example="Clarification needed on third-party API integration, ..."
|
||||
)
|
||||
|
||||
ACTION_NODES = [
|
||||
NODES = [
|
||||
IMPLEMENTATION_APPROACH,
|
||||
PROJECT_NAME,
|
||||
# PROJECT_NAME,
|
||||
FILE_LIST,
|
||||
DATA_STRUCTURES_AND_INTERFACES,
|
||||
PROGRAM_CALL_FLOW,
|
||||
ANYTHING_UNCLEAR
|
||||
]
|
||||
|
||||
DESIGN_API_NODE = ActionNode("DesignAPI", str, "", "")
|
||||
DESIGN_API_NODE.add_children(ACTION_NODES)
|
||||
|
||||
SIMPLE_TEMPLATE = """
|
||||
## context
|
||||
{context}
|
||||
|
||||
## example
|
||||
{example}
|
||||
|
||||
## instruction-nodes: "<node>: <type> # <comment>"
|
||||
{instruction}
|
||||
|
||||
## instruction-action
|
||||
Role: You are an architect; the goal is to design a SOTA software system
|
||||
Language: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.
|
||||
Requirement: Fill in the above missing instruction-nodes based on the context
|
||||
now, output wrapped inside [CONTENT][/CONTENT] as example, nothing else.
|
||||
"""
|
||||
DESIGN_API_NODE = ActionNode.from_children("DesignAPI", NODES)
|
||||
|
||||
|
||||
def main():
|
||||
instruction, example = DESIGN_API_NODE.compile()
|
||||
text = SIMPLE_TEMPLATE.format(context="", example=example, instruction=instruction)
|
||||
logger.info(text)
|
||||
prompt = DESIGN_API_NODE.compile(context="")
|
||||
logger.info(prompt)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@
|
|||
3. According to the design in Section 2.2.3.5.4 of RFC 135, add incremental iteration functionality.
|
||||
"""
|
||||
import json
|
||||
from typing import List
|
||||
# from typing import List
|
||||
|
||||
from metagpt.actions import ActionOutput
|
||||
from metagpt.actions.action import Action
|
||||
from metagpt.actions.project_management_an import PM_NODE
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.const import (
|
||||
PACKAGE_REQUIREMENTS_FILENAME,
|
||||
|
|
@ -24,189 +25,14 @@ from metagpt.const import (
|
|||
from metagpt.logs import logger
|
||||
from metagpt.schema import Document, Documents
|
||||
from metagpt.utils.file_repository import FileRepository
|
||||
from metagpt.utils.get_template import get_template
|
||||
# from metagpt.utils.get_template import get_template
|
||||
|
||||
templates = {
|
||||
"json": {
|
||||
"PROMPT_TEMPLATE": """
|
||||
# Context
|
||||
{context}
|
||||
|
||||
## 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
|
||||
Language: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.
|
||||
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: Output carefully referenced "Format example" in format.
|
||||
|
||||
## Required Python third-party packages: Provide Python list[str] in requirements.txt format
|
||||
|
||||
## Required Other language third-party packages: Provide Python list[str] in requirements.txt format
|
||||
|
||||
## 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
|
||||
|
||||
## Full API spec: Use OpenAPI 3.0. Describe all APIs that may be used by both frontend and backend.
|
||||
|
||||
## Shared Knowledge: Anything that should be public like utils' functions, config's variables details that should make clear first.
|
||||
|
||||
## Anything UNCLEAR: Provide as Plain text. Try to clarify it. 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 ..."
|
||||
],
|
||||
"Logic Analysis": [
|
||||
["game.py", "Contains..."]
|
||||
],
|
||||
"Task list": [
|
||||
"game.py"
|
||||
],
|
||||
"Full API spec": """
|
||||
openapi: 3.0.0
|
||||
...
|
||||
description: A JSON object ...
|
||||
""",
|
||||
"Shared Knowledge": """
|
||||
'game.py' contains ...
|
||||
""",
|
||||
"Anything UNCLEAR": "We need ... how to start."
|
||||
}
|
||||
''',
|
||||
},
|
||||
"markdown": {
|
||||
"PROMPT_TEMPLATE": """
|
||||
# Context
|
||||
{context}
|
||||
|
||||
## 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
|
||||
|
||||
## 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
|
||||
|
||||
## Full API spec: Use OpenAPI 3.0. Describe all APIs that may be used by both frontend and backend.
|
||||
|
||||
## Shared Knowledge: Anything that should be public like utils' functions, config's variables details that should make clear first.
|
||||
|
||||
## Anything UNCLEAR: Provide as Plain text. Try to clarify it. 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
|
||||
[
|
||||
["index.js", "Contains ..."],
|
||||
["main.py", "Contains ..."],
|
||||
]
|
||||
```
|
||||
|
||||
## Task list
|
||||
```python
|
||||
[
|
||||
"index.js",
|
||||
"main.py",
|
||||
]
|
||||
```
|
||||
|
||||
## Shared Knowledge
|
||||
```python
|
||||
"""
|
||||
'game.py' contains ...
|
||||
"""
|
||||
```
|
||||
|
||||
## 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, ...),
|
||||
"Anything UNCLEAR": (str, ...),
|
||||
}
|
||||
|
||||
MERGE_PROMPT = """
|
||||
# Context
|
||||
{context}
|
||||
|
||||
## Old Tasks
|
||||
NEW_REQ_TEMPLATE = """
|
||||
### Legacy Content
|
||||
{old_tasks}
|
||||
-----
|
||||
|
||||
## Format example
|
||||
{format_example}
|
||||
-----
|
||||
Role: You are a project manager; The goal is to merge the new PRD/technical design content from 'Context' into 'Old Tasks.' Based on this merged result, break down tasks, give a task list, and analyze task dependencies to start with the prerequisite modules.
|
||||
Requirements: Based on the context, fill in the following missing information, each section name is a key in json. Here the granularity of the task is a file, if there are any missing files, you can supplement them
|
||||
Attention: Use '##' to split sections, not '#', and '## <SECTION_NAME>' SHOULD WRITE BEFORE the code and triple quote.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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" format,
|
||||
and only output the json inside this tag, nothing else
|
||||
### New Requirements
|
||||
{context}
|
||||
"""
|
||||
|
||||
|
||||
|
|
@ -262,18 +88,16 @@ class WriteTasks(Action):
|
|||
return task_doc
|
||||
|
||||
async def _run_new_tasks(self, context, format=CONFIG.prompt_format):
|
||||
prompt_template, format_example = get_template(templates, format)
|
||||
prompt = prompt_template.format(context=context, format_example=format_example)
|
||||
rsp = await self._aask_v1(prompt, "task", OUTPUT_MAPPING, format=format)
|
||||
return rsp
|
||||
node = await PM_NODE.fill(context, self.llm, format)
|
||||
# prompt_template, format_example = get_template(templates, format)
|
||||
# prompt = prompt_template.format(context=context, format_example=format_example)
|
||||
# rsp = await self._aask_v1(prompt, "task", OUTPUT_MAPPING, format=format)
|
||||
return node
|
||||
|
||||
async def _merge(self, system_design_doc, task_doc, format=CONFIG.prompt_format) -> Document:
|
||||
_, format_example = get_template(templates, format)
|
||||
prompt = MERGE_PROMPT.format(context=system_design_doc.content, old_tasks=task_doc.content,
|
||||
format_example=format_example)
|
||||
rsp = await self._aask_v1(prompt, "task", OUTPUT_MAPPING, format=format)
|
||||
task_doc.content = rsp.instruct_content.json(ensure_ascii=False)
|
||||
return task_doc
|
||||
context = NEW_REQ_TEMPLATE.format(context=system_design_doc.content, old_tasks=task_doc.content)
|
||||
node = await PM_NODE.fill(context, self.llm, format)
|
||||
return node
|
||||
|
||||
@staticmethod
|
||||
async def _update_requirements(doc):
|
||||
|
|
|
|||
82
metagpt/actions/project_management_an.py
Normal file
82
metagpt/actions/project_management_an.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Time : 2023/12/14 15:28
|
||||
@Author : alexanderwu
|
||||
@File : project_management_an.py
|
||||
"""
|
||||
from metagpt.actions.action_node import ActionNode
|
||||
from metagpt.logs import logger
|
||||
|
||||
REQUIRED_PYTHON_PACKAGES = ActionNode(
|
||||
key="Required Python packages",
|
||||
expected_type=list[str],
|
||||
instruction="Provide required Python packages in requirements.txt format.",
|
||||
example=["flask==1.1.2", "bcrypt==3.2.0"]
|
||||
)
|
||||
|
||||
REQUIRED_OTHER_LANGUAGE_PACKAGES = ActionNode(
|
||||
key="Required Other language third-party packages",
|
||||
expected_type=list[str],
|
||||
instruction="List down the required packages for languages other than Python.",
|
||||
example=["No third-party dependencies required"]
|
||||
)
|
||||
|
||||
LOGIC_ANALYSIS = ActionNode(
|
||||
key="Logic Analysis",
|
||||
expected_type=list[list[str]],
|
||||
instruction="Provide a list of files with the classes/methods/functions to be implemented, "
|
||||
"including dependency analysis and imports.",
|
||||
example=[["game.py", "Contains Game class and ... functions"],
|
||||
["main.py", "Contains main function, from game import Game"]]
|
||||
)
|
||||
|
||||
TASK_LIST = ActionNode(
|
||||
key="Task list",
|
||||
expected_type=list[str],
|
||||
instruction="Break down the tasks into a list of filenames, prioritized by dependency order.",
|
||||
example=["game.py", "main.py"]
|
||||
)
|
||||
|
||||
FULL_API_SPEC = ActionNode(
|
||||
key="Full API spec",
|
||||
expected_type=str,
|
||||
instruction="Describe all APIs using OpenAPI 3.0 spec that may be used by both frontend and backend.",
|
||||
example="openapi: 3.0.0 ..."
|
||||
)
|
||||
|
||||
SHARED_KNOWLEDGE = ActionNode(
|
||||
key="Shared Knowledge",
|
||||
expected_type=str,
|
||||
instruction="Detail any shared knowledge, like common utility functions or configuration variables.",
|
||||
example="'game.py' contains functions shared across the project."
|
||||
)
|
||||
|
||||
ANYTHING_UNCLEAR_PM = ActionNode(
|
||||
key="Anything UNCLEAR",
|
||||
expected_type=str,
|
||||
instruction="Mention any unclear aspects in the project management context and try to clarify them.",
|
||||
example="Clarification needed on how to start and initialize third-party libraries."
|
||||
)
|
||||
|
||||
NODES = [
|
||||
REQUIRED_PYTHON_PACKAGES,
|
||||
REQUIRED_OTHER_LANGUAGE_PACKAGES,
|
||||
LOGIC_ANALYSIS,
|
||||
TASK_LIST,
|
||||
FULL_API_SPEC,
|
||||
SHARED_KNOWLEDGE,
|
||||
ANYTHING_UNCLEAR_PM
|
||||
]
|
||||
|
||||
|
||||
PM_NODE = ActionNode.from_children("PM_NODE", NODES)
|
||||
|
||||
|
||||
def main():
|
||||
prompt = PM_NODE.compile(context="")
|
||||
logger.info(prompt)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -14,9 +14,11 @@ from __future__ import annotations
|
|||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
# from typing import List
|
||||
|
||||
from metagpt.actions import Action, ActionOutput
|
||||
from metagpt.actions.action_node import ActionNode
|
||||
from metagpt.actions.write_prd_an import WRITE_PRD_NODE, WP_ISSUE_TYPE_NODE, WP_IS_RELATIVE_NODE
|
||||
from metagpt.actions.fix_bug import FixBug
|
||||
from metagpt.actions.search_and_summarize import SearchAndSummarize
|
||||
from metagpt.config import CONFIG
|
||||
|
|
@ -31,293 +33,26 @@ from metagpt.logs import logger
|
|||
from metagpt.schema import Document, Documents, Message, BugFixContext
|
||||
from metagpt.utils.common import CodeParser
|
||||
from metagpt.utils.file_repository import FileRepository
|
||||
from metagpt.utils.get_template import get_template
|
||||
# from metagpt.utils.get_template import get_template
|
||||
from metagpt.utils.mermaid import mermaid_to_file
|
||||
|
||||
templates = {
|
||||
"json": {
|
||||
"PROMPT_TEMPLATE": """
|
||||
# Context
|
||||
{{
|
||||
"Original Requirements": "{requirements}",
|
||||
"Search Information": ""
|
||||
}}
|
||||
CONTEXT_TEMPLATE = """
|
||||
### Project Name
|
||||
{project_name}
|
||||
|
||||
## Format example
|
||||
{format_example}
|
||||
-----
|
||||
Role: You are a professional product manager; the goal is to design a concise, usable, efficient product
|
||||
Language: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.
|
||||
Requirements: According to the context, fill in the following missing information, note that each sections are returned in Python code triple quote form seperatedly.
|
||||
ATTENTION: Output carefully referenced "Format example" in format.
|
||||
|
||||
## YOU NEED TO FULFILL THE BELOW JSON DOC
|
||||
|
||||
{{
|
||||
"Language": "", # str, use the same language as the user requirement. en_us / zh_cn etc.
|
||||
"Original Requirements": "", # str, place the polished complete original requirements here
|
||||
"Project Name": "{project_name}", # str, if it's empty, name it with snake case style, like game_2048 / web_2048 / simple_crm etc.
|
||||
"Search Information": "",
|
||||
"Requirements": "",
|
||||
"Product Goals": [], # Provided as Python list[str], up to 3 clear, orthogonal product goals.
|
||||
"User Stories": [], # Provided as Python list[str], up to 5 scenario-based user stories
|
||||
"Competitive Analysis": [], # Provided as Python list[str], up to 8 competitive product analyses
|
||||
# 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.
|
||||
"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": "", # Provide as Plain text.
|
||||
"Requirement Pool": [["P0","P0 requirement"],["P1","P1 requirement"]], # Provided as Python list[list[str], the parameters are requirement description, priority(P0/P1/P2), respectively, comply with PEP standards
|
||||
"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. Try to clarify it.
|
||||
}}
|
||||
|
||||
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]
|
||||
{{
|
||||
"Language": "",
|
||||
"Original Requirements": "",
|
||||
"Project Name": "{project_name}",
|
||||
"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
|
||||
## Original Requirements
|
||||
### Original Requirements
|
||||
{requirements}
|
||||
|
||||
## 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
|
||||
Language: Please use the same language as the user requirement to answer, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.
|
||||
Requirements: According to the context, fill in the following missing information, note that each sections are returned in Python code triple quote form seperatedly.
|
||||
ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. AND '## <SECTION_NAME>' SHOULD WRITE BEFORE the code and triple quote. Output carefully referenced "Format example" in format.
|
||||
|
||||
## Language: Provide as Plain text, use the same language as the user requirement.
|
||||
|
||||
## Original Requirements: Provide as Plain text, place the polished complete original requirements here
|
||||
|
||||
## Product Goals: Provided as Python list[str], up to 3 clear, orthogonal product goals.
|
||||
|
||||
## User Stories: Provided as Python list[str], up to 5 scenario-based user stories
|
||||
|
||||
## 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.
|
||||
|
||||
## Requirement Pool: Provided as Python list[list[str], the parameters are requirement description, priority(P0/P1/P2), respectively, comply with PEP standards
|
||||
|
||||
## 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. Try to clarify it.
|
||||
""",
|
||||
"FORMAT_EXAMPLE": """
|
||||
---
|
||||
## Original Requirements
|
||||
The user ...
|
||||
|
||||
## 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.
|
||||
---
|
||||
""",
|
||||
},
|
||||
}
|
||||
|
||||
OUTPUT_MAPPING = {
|
||||
"Language": (str, ...),
|
||||
"Original Requirements": (str, ...),
|
||||
"Project Name": (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, ...),
|
||||
}
|
||||
|
||||
IS_RELATIVE_PROMPT = """
|
||||
## PRD:
|
||||
{old_prd}
|
||||
|
||||
## New Requirement:
|
||||
{requirements}
|
||||
|
||||
___
|
||||
You are a professional product manager; You need to assess whether the new requirements are relevant to the existing PRD to determine whether to merge the new requirements into this PRD.
|
||||
Is the newly added requirement in "New Requirement" related to the PRD?
|
||||
Respond with `YES` if it is related, `NO` if it is not, and provide the reasons. Return the response in JSON format.
|
||||
### Search Information
|
||||
-
|
||||
"""
|
||||
|
||||
MERGE_PROMPT = """
|
||||
# Context
|
||||
## Original Requirements
|
||||
{requirements}
|
||||
|
||||
|
||||
## Old PRD
|
||||
NEW_REQ_TEMPLATE = """
|
||||
### Legacy Content
|
||||
{old_prd}
|
||||
-----
|
||||
Role: You are a professional product manager; The goal is to incorporate the newly added requirements from the "Original Requirements" into the existing Product Requirements Document (PRD) in the "Old PRD" in order to design a concise, usable, and efficient product.
|
||||
Language: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.
|
||||
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
|
||||
ATTENTION: Output carefully referenced "Old PRD" in format.
|
||||
|
||||
## YOU NEED TO FULFILL THE BELOW JSON DOC
|
||||
|
||||
{{
|
||||
"Language": "", # str, use the same language as the user requirement. en_us / zh_cn etc.
|
||||
"Original Requirements": "", # str, place the polished complete original requirements here
|
||||
"Project Name": "{project_name}", # str, if it's empty, name it with snake case style, like game_2048 / web_2048 / simple_crm etc.
|
||||
"Search Information": "",
|
||||
"Requirements": "",
|
||||
"Product Goals": [], # Provided as Python list[str], up to 3 clear, orthogonal product goals.
|
||||
"User Stories": [], # Provided as Python list[str], up to 5 scenario-based user stories
|
||||
"Competitive Analysis": [], # Provided as Python list[str], up to 8 competitive product analyses
|
||||
# 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.
|
||||
"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": "", # Provide as Plain text.
|
||||
"Requirement Pool": [["P0","P0 requirement"],["P1","P1 requirement"]], # Provided as Python list[list[str], the parameters are requirement description, priority(P0/P1/P2), respectively, comply with PEP standards
|
||||
"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. Try to clarify it.
|
||||
}}
|
||||
|
||||
output a properly formatted JSON, wrapped inside [CONTENT][/CONTENT] like "Old PRD" format,
|
||||
and only output the json inside this tag, nothing else
|
||||
"""
|
||||
|
||||
IS_BUGFIX_PROMPT = """
|
||||
{content}
|
||||
|
||||
___
|
||||
You are a professional product manager; You need to determine whether the above content describes a requirement or provides feedback about a bug.
|
||||
Respond with `YES` if it is a feedback about a bug, `NO` if it is not, and provide the reasons. Return the response in JSON format like below:
|
||||
|
||||
```json
|
||||
{{
|
||||
"is_bugfix": ..., # `YES` or `NO`
|
||||
"reason": ..., # reason string
|
||||
}}
|
||||
```
|
||||
### New Requirements
|
||||
{requirements}
|
||||
"""
|
||||
|
||||
|
||||
|
|
@ -335,7 +70,7 @@ class WritePRD(Action):
|
|||
await docs_file_repo.save(filename=REQUIREMENT_FILENAME, content="")
|
||||
bug_fix = BugFixContext(filename=BUGFIX_FILENAME)
|
||||
return Message(content=bug_fix.json(), instruct_content=bug_fix,
|
||||
role=self.profile,
|
||||
role="",
|
||||
cause_by=FixBug,
|
||||
sent_from=self,
|
||||
send_to="Alex", # the name of Engineer
|
||||
|
|
@ -353,7 +88,7 @@ class WritePRD(Action):
|
|||
if not prd_doc:
|
||||
continue
|
||||
change_files.docs[prd_doc.filename] = prd_doc
|
||||
logger.info(f"REWRITE PRD:{prd_doc.filename}")
|
||||
logger.info(f"rewrite prd: {prd_doc.filename}")
|
||||
# If there is no existing PRD, generate one using 'docs/requirement.txt'.
|
||||
if not change_files.docs:
|
||||
prd_doc = await self._update_prd(
|
||||
|
|
@ -367,47 +102,32 @@ class WritePRD(Action):
|
|||
# optimization in subsequent steps.
|
||||
return ActionOutput(content=change_files.json(), instruct_content=change_files)
|
||||
|
||||
async def _run_new_requirement(self, requirements, format=CONFIG.prompt_format, *args, **kwargs) -> ActionOutput:
|
||||
sas = SearchAndSummarize()
|
||||
# rsp = await sas.run(context=requirements, system_text=SEARCH_AND_SUMMARIZE_SYSTEM_EN_US)
|
||||
rsp = ""
|
||||
info = f"### Search Results\n{sas.result}\n\n### Search Summary\n{rsp}"
|
||||
if sas.result:
|
||||
logger.info(sas.result)
|
||||
logger.info(rsp)
|
||||
|
||||
# logger.info(format)
|
||||
prompt_template, format_example = get_template(templates, format)
|
||||
async def _run_new_requirement(self, requirements, format=CONFIG.prompt_format) -> ActionOutput:
|
||||
# sas = SearchAndSummarize()
|
||||
# # rsp = await sas.run(context=requirements, system_text=SEARCH_AND_SUMMARIZE_SYSTEM_EN_US)
|
||||
# rsp = ""
|
||||
# info = f"### Search Results\n{sas.result}\n\n### Search Summary\n{rsp}"
|
||||
# if sas.result:
|
||||
# logger.info(sas.result)
|
||||
# logger.info(rsp)
|
||||
project_name = CONFIG.project_name if CONFIG.project_name else ""
|
||||
format_example = format_example.format(project_name=project_name)
|
||||
# logger.info(prompt_template)
|
||||
# logger.info(format_example)
|
||||
prompt = prompt_template.format(
|
||||
requirements=requirements, search_information=info, format_example=format_example, project_name=project_name
|
||||
)
|
||||
# logger.info(prompt)
|
||||
# prd = await self._aask_v1(prompt, "prd", OUTPUT_MAPPING)
|
||||
prd = await self._aask_v1(prompt, "prd", OUTPUT_MAPPING, format=format)
|
||||
await self._rename_workspace(prd)
|
||||
return prd
|
||||
context = CONTEXT_TEMPLATE.format(requirements=requirements, project_name=project_name)
|
||||
node = await WRITE_PRD_NODE.fill(context=context, llm=self.llm, to=format)
|
||||
await self._rename_workspace(node)
|
||||
return node
|
||||
|
||||
async def _is_relative_to(self, new_requirement_doc, old_prd_doc) -> bool:
|
||||
prompt = IS_RELATIVE_PROMPT.format(old_prd=old_prd_doc.content, requirements=new_requirement_doc.content)
|
||||
res = await self._aask(prompt=prompt)
|
||||
logger.info(f"REQ-RELATIVE: [{new_requirement_doc.root_relative_path}, {old_prd_doc.root_relative_path}]: {res}")
|
||||
if "YES" in res:
|
||||
return True
|
||||
return False
|
||||
async def _is_relative(self, new_requirement_doc, old_prd_doc) -> bool:
|
||||
context = NEW_REQ_TEMPLATE.format(old_prd=old_prd_doc.content, requirements=new_requirement_doc.content)
|
||||
node = await WP_IS_RELATIVE_NODE.fill(context, self.llm)
|
||||
return node.get("is_relative") == "YES"
|
||||
|
||||
async def _merge(self, new_requirement_doc, prd_doc, format=CONFIG.prompt_format) -> Document:
|
||||
if not CONFIG.project_name:
|
||||
CONFIG.project_name = Path(CONFIG.project_path).name
|
||||
prompt = MERGE_PROMPT.format(
|
||||
requirements=new_requirement_doc.content, old_prd=prd_doc.content, project_name=CONFIG.project_name
|
||||
)
|
||||
prd = await self._aask_v1(prompt, "prd", OUTPUT_MAPPING, format=format)
|
||||
prd_doc.content = prd.instruct_content.json(ensure_ascii=False)
|
||||
await self._rename_workspace(prd)
|
||||
prompt = NEW_REQ_TEMPLATE.format(requirements=new_requirement_doc.content, old_prd=prd_doc.content)
|
||||
node = await WRITE_PRD_NODE.fill(context=prompt, llm=self.llm, to=format)
|
||||
prd_doc.content = node.instruct_content.json(ensure_ascii=False)
|
||||
await self._rename_workspace(node)
|
||||
return prd_doc
|
||||
|
||||
async def _update_prd(self, requirement_doc, prd_doc, prds_file_repo, *args, **kwargs) -> Document | None:
|
||||
|
|
@ -418,7 +138,7 @@ class WritePRD(Action):
|
|||
filename=FileRepository.new_filename() + ".json",
|
||||
content=prd.instruct_content.json(ensure_ascii=False),
|
||||
)
|
||||
elif await self._is_relative_to(requirement_doc, prd_doc):
|
||||
elif await self._is_relative(requirement_doc, prd_doc):
|
||||
new_prd_doc = await self._merge(requirement_doc, prd_doc)
|
||||
else:
|
||||
return None
|
||||
|
|
@ -453,17 +173,13 @@ class WritePRD(Action):
|
|||
return
|
||||
|
||||
if not CONFIG.project_name:
|
||||
if isinstance(prd, ActionOutput):
|
||||
if isinstance(prd, ActionOutput) or isinstance(prd, ActionNode):
|
||||
ws_name = prd.instruct_content.dict()["Project Name"]
|
||||
else:
|
||||
ws_name = CodeParser.parse_str(block="Project Name", text=prd)
|
||||
CONFIG.project_name = ws_name
|
||||
CONFIG.git_repo.rename_root(CONFIG.project_name)
|
||||
|
||||
async def _is_bugfix(self, content):
|
||||
prompt = IS_BUGFIX_PROMPT.format(content=content)
|
||||
res = await self._aask(prompt=prompt)
|
||||
logger.info(f"IS_BUGFIX:{res}")
|
||||
if "YES" in res:
|
||||
return True
|
||||
return False
|
||||
async def _is_bugfix(self, context) -> bool:
|
||||
node = await WP_ISSUE_TYPE_NODE.fill(context, self.llm)
|
||||
return node.get("issue_type") == "BUG"
|
||||
|
|
|
|||
153
metagpt/actions/write_prd_an.py
Normal file
153
metagpt/actions/write_prd_an.py
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Time : 2023/12/14 11:40
|
||||
@Author : alexanderwu
|
||||
@File : write_prd_an.py
|
||||
"""
|
||||
|
||||
from metagpt.actions.action_node import ActionNode
|
||||
from metagpt.logs import logger
|
||||
|
||||
LANGUAGE = ActionNode(
|
||||
key="Language",
|
||||
expected_type=str,
|
||||
instruction="Provide the language used in the project, typically matching the user's requirement language.",
|
||||
example="en_us"
|
||||
)
|
||||
|
||||
ORIGINAL_REQUIREMENTS = ActionNode(
|
||||
key="Original Requirements",
|
||||
expected_type=str,
|
||||
instruction="Place the polished, complete original requirements here.",
|
||||
example="The game should have a leaderboard and multiple difficulty levels."
|
||||
)
|
||||
|
||||
PROJECT_NAME = ActionNode(
|
||||
key="Project Name",
|
||||
expected_type=str,
|
||||
instruction="Name the project using snake case style, like 'game_2048' or 'simple_crm'.",
|
||||
example="game_2048"
|
||||
)
|
||||
|
||||
PRODUCT_GOALS = ActionNode(
|
||||
key="Product Goals",
|
||||
expected_type=list[str],
|
||||
instruction="Provide up to three clear, orthogonal product goals.",
|
||||
example=["Create an engaging user experience",
|
||||
"Ensure high performance",
|
||||
"Provide customizable features"]
|
||||
)
|
||||
|
||||
USER_STORIES = ActionNode(
|
||||
key="User Stories",
|
||||
expected_type=list[str],
|
||||
instruction="Provide up to five scenario-based user stories.",
|
||||
example=["As a user, I want to be able to choose difficulty levels",
|
||||
"As a player, I want to see my score after each game"]
|
||||
)
|
||||
|
||||
COMPETITIVE_ANALYSIS = ActionNode(
|
||||
key="Competitive Analysis",
|
||||
expected_type=list[str],
|
||||
instruction="Provide analyses for up to seven competitive products.",
|
||||
example=["Python Snake Game: Simple interface, lacks advanced features"]
|
||||
)
|
||||
|
||||
COMPETITIVE_QUADRANT_CHART = ActionNode(
|
||||
key="Competitive Quadrant Chart",
|
||||
expected_type=str,
|
||||
instruction="Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1",
|
||||
example="""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]"""
|
||||
)
|
||||
|
||||
REQUIREMENT_ANALYSIS = ActionNode(
|
||||
key="Requirement Analysis",
|
||||
expected_type=str,
|
||||
instruction="Provide a detailed analysis of the requirements.",
|
||||
example="The product should be user-friendly and performance-optimized."
|
||||
)
|
||||
|
||||
REQUIREMENT_POOL = ActionNode(
|
||||
key="Requirement Pool",
|
||||
expected_type=list[list[str]],
|
||||
instruction="List down the requirements with their priority (P0, P1, P2).",
|
||||
example=[["P0", "High priority requirement"], ["P1", "Medium priority requirement"]]
|
||||
)
|
||||
|
||||
UI_DESIGN_DRAFT = ActionNode(
|
||||
key="UI Design draft",
|
||||
expected_type=str,
|
||||
instruction="Provide a simple description of UI elements, functions, style, and layout.",
|
||||
example="Basic function description with a simple style and layout."
|
||||
)
|
||||
|
||||
ANYTHING_UNCLEAR = ActionNode(
|
||||
key="Anything UNCLEAR",
|
||||
expected_type=str,
|
||||
instruction="Mention any aspects of the project that are unclear and try to clarify them.",
|
||||
example="..."
|
||||
)
|
||||
|
||||
ISSUE_TYPE = ActionNode(
|
||||
key="issue_type",
|
||||
expected_type=str,
|
||||
instruction="Answer BUG/REQUIREMENT. If it is a bugfix, answer Bug, otherwise answer Requirement",
|
||||
example="BUG"
|
||||
)
|
||||
|
||||
IS_RELATIVE = ActionNode(
|
||||
key="is_relative",
|
||||
expected_type=str,
|
||||
instruction="Answer YES/NO. If the requirement is related to the old PRD, answer YES, otherwise NO",
|
||||
example="YES"
|
||||
)
|
||||
|
||||
REASON = ActionNode(
|
||||
key="reason",
|
||||
expected_type=str,
|
||||
instruction="Explain the reasoning process from question to answer",
|
||||
example="..."
|
||||
)
|
||||
|
||||
|
||||
NODES = [
|
||||
LANGUAGE,
|
||||
ORIGINAL_REQUIREMENTS,
|
||||
PROJECT_NAME,
|
||||
PRODUCT_GOALS,
|
||||
USER_STORIES,
|
||||
COMPETITIVE_ANALYSIS,
|
||||
COMPETITIVE_QUADRANT_CHART,
|
||||
REQUIREMENT_ANALYSIS,
|
||||
REQUIREMENT_POOL,
|
||||
UI_DESIGN_DRAFT,
|
||||
ANYTHING_UNCLEAR
|
||||
]
|
||||
|
||||
WRITE_PRD_NODE = ActionNode.from_children("WritePRD", NODES)
|
||||
WP_ISSUE_TYPE_NODE = ActionNode.from_children("WP_ISSUE_TYPE", [ISSUE_TYPE, REASON])
|
||||
WP_IS_RELATIVE_NODE = ActionNode.from_children("WP_IS_RELATIVE", [IS_RELATIVE, REASON])
|
||||
|
||||
|
||||
def main():
|
||||
prompt = WRITE_PRD_NODE.compile(context="")
|
||||
logger.info(prompt)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue