fixbug: write software framework, rfc243

This commit is contained in:
莘权 马 2024-06-17 15:25:40 +08:00
parent 5911d3ce70
commit cf8efc9f67
8 changed files with 211 additions and 74 deletions

View file

@ -25,7 +25,7 @@ from metagpt.roles import Architect
from metagpt.roles.di.team_leader import TeamLeader
from metagpt.schema import AIMessage, UserMessage
from metagpt.strategy.experience_retriever import TRDToolExpRetriever
from metagpt.utils.common import aread, to_markdown_code_block
from metagpt.utils.common import aread
app = typer.Typer(add_completion=False)
@ -34,7 +34,6 @@ class EnvBuilder(BaseModel):
context: Context
user_requirements: List[str]
actors: Dict[str, str]
acknowledge: str
technical_constraint: str
output_dir: Path
@ -52,14 +51,6 @@ The content of "Actor, System, External System" provides an explanation of actor
"""
architect.rc.memory.add(AIMessage(content=msg.format(use_case_actors=use_case_actors)))
# Prepare acknowledge
msg = """
The descriptions of the interfaces used in TRD can be found in the "Acknowledge" section.
## Acknowledge
{acknowledge}
"""
architect.rc.memory.add(AIMessage(content=msg.format(acknowledge=to_markdown_code_block(val=self.acknowledge))))
# Prepare technical requirements
msg = """
"Additional Technical Requirements" specifies the additional technical requirements that the generated software framework code must meet.
@ -76,7 +67,6 @@ async def develop(
context: Context,
user_requirement_filename: str,
actors_filename: str,
acknowledge_filename: str,
constraint_filename: str,
output_dir: str,
):
@ -86,19 +76,17 @@ async def develop(
user_requirements = json.loads(v)
v = await aread(filename=actors_filename)
actors = json.loads(v)
acknowledge = await aread(filename=acknowledge_filename)
technical_constraint = await aread(filename=constraint_filename)
env_builder = EnvBuilder(
context=context,
user_requirements=user_requirements,
actors=actors,
acknowledge=acknowledge,
technical_constraint=technical_constraint,
output_dir=output_dir,
)
env = env_builder.build()
msg = """
根据"User Requirements"中的用户需求写TRD
Given the user requirement of "User Requirements", write out the software framework.
## User Requirements
{user_requirements}
"""
@ -115,7 +103,6 @@ async def develop(
def startup(
user_requirement_filename: str = typer.Argument(..., help="The filename of the user requirements."),
actors_filename: str = typer.Argument(..., help="The filename of UML use case actors description."),
acknowledge_filename: str = typer.Argument(..., help="External interfaces declarations."),
llm_config: str = typer.Option(default="", help="Low-cost LLM config"),
constraint_filename: str = typer.Option(default="", help="What technical dependency constraints are."),
output_dir: str = typer.Option(default="", help="Output directory."),
@ -127,9 +114,7 @@ def startup(
config = Config.default()
ctx = Context(config=config)
asyncio.run(
develop(ctx, user_requirement_filename, actors_filename, acknowledge_filename, constraint_filename, output_dir)
)
asyncio.run(develop(ctx, user_requirement_filename, actors_filename, constraint_filename, output_dir))
if __name__ == "__main__":

View file

@ -70,5 +70,5 @@ class EvaluateAction(Action):
val = evaluations.get(vote, [])
val.append(evaluation)
if len(val) > 1:
return EvaluationData(is_pass=vote, evaluations="\n".join(val))
return EvaluationData(is_pass=vote, conclusion="\n".join(val))
evaluations[vote] = val

View file

@ -8,6 +8,7 @@
"""
import json
import uuid
from datetime import datetime
from pathlib import Path
from typing import Optional, Union, List
@ -22,7 +23,11 @@ from metagpt.utils.common import awrite
@register_tool(tags=["software framework"])
async def save_framework(dir_data: str, output_dir: Optional[Union[str, Path]] = None) -> List[str]:
output_dir = Path(output_dir) if output_dir else DEFAULT_WORKSPACE_ROOT / uuid.uuid4().hex
output_dir = (
Path(output_dir)
if output_dir
else DEFAULT_WORKSPACE_ROOT / (datetime.now().strftime("%Y%m%d%H%M%S") + uuid.uuid4().hex[0:8])
)
output_dir.mkdir(parents=True, exist_ok=True)
json_data = dir_data.removeprefix("```json").removesuffix("```")

View file

@ -91,7 +91,8 @@ PROMPT = """
You are a tool that evaluates the quality of framework code based on the TRD content;
You need to refer to the content of the "Legacy TRD" section to check for any errors or omissions in the framework code found in "Legacy Outputs";
The content of "Actor, System, External System" provides an explanation of actors and systems that appear in UML Use Case diagram;
Information missing from the "Legacy TRD" can be found in the "Acknowledge" section;
Information about the external system missing from the "Legacy TRD" can be found in the "Acknowledge" section;
Which interfaces defined in "Acknowledge" are used in the "Legacy TRD"? Do not implement the interface in "Acknowledge" section until it is used in "Legacy TRD";
Parts not mentioned in the "Legacy TRD" will be handled by other TRDs, therefore, processes not present in the "Legacy TRD" are considered ready;
"Additional Technical Requirements" specifies the additional technical requirements that the generated software framework code must meet;
Return a markdown JSON object with:

View file

@ -116,18 +116,20 @@ PROMPT = """
---
You are a tool that generates software framework code based on TRD.
The content of "Actor, System, External System" provides an explanation of actors and systems that appear in UML Use Case diagram;
The descriptions of the interfaces used in the "TRD" can be found in the "Acknowledge" section;
The descriptions of the interfaces of the external system used in the "TRD" can be found in the "Acknowledge" section; Do not implement the interface of the external system in "Acknowledge" section until it is used in "TRD";
"Legacy Outputs" contains the software framework code generated by you last time, which you can improve by addressing the issues raised in "Evaluation Conclusion";
"Additional Technical Requirements" specifies the additional technical requirements that the generated software framework code must meet;
Develop source code based on the content of the "TRD";
- The `README.md` file should include:
- The folder structure diagram of the entire project;
- Class diagram and sequence diagram in PlantUML format;
- Correspondence between classes, interfaces, and functions with the content in the "TRD" section
- Prerequisites if necessary;
- Installation if necessary;
- Configuration if necessary;
- Usage if necessary;
- The `CLASS.md` file should include the class diagram in PlantUML format;
- The `SEQUENCE.md` file should include the sequence diagram in PlantUML format;
- Code comments using Google style;
Return a markdown JSON object list, each object containing:
- a "path" key with a value specifying its path;

View file

@ -7,16 +7,6 @@
"""
from metagpt.actions import WritePRD
from metagpt.actions.design_api import WriteDesign
from metagpt.actions.requirement_analysis.framework import (
EvaluateFramework,
WriteFramework,
)
from metagpt.actions.requirement_analysis.trd import (
CompressExternalInterfaces,
DetectInteraction,
EvaluateTRD,
WriteTRD,
)
from metagpt.roles.di.role_zero import RoleZero
from metagpt.tools.libs.software_development import write_framework, write_trd
from metagpt.utils.common import tool2name
@ -43,17 +33,6 @@ class Architect(RoleZero):
instruction: str = """Use WriteDesign tool to write a system design document if a system design is required; Use WriteTRD tool to write a TRD if a TRD is required;"""
max_react_loop: int = 1 # FIXME: Read and edit files requires more steps, consider later
# tools: list[str] = [
# "Editor:write,read,write_content",
# "RoleZero",
# "WriteDesign",
# "CompressExternalInterfaces",
# "DetectInteraction",
# "EvaluateTRD",
# "WriteTRD",
# "WriteFramework",
# "EvaluateFramework",
# ]
tools: list[str] = [
"Editor:write,read,write_content",
"RoleZero",
@ -76,15 +55,9 @@ class Architect(RoleZero):
def _update_tool_execution(self):
write_design = WriteDesign()
self.tool_execution_map.update(tool2name(WriteDesign, ["run"], write_design.run))
compress_external_interfaces = CompressExternalInterfaces()
self.tool_execution_map.update(tool2name(CompressExternalInterfaces, ["run"], compress_external_interfaces.run))
detect_interaction = DetectInteraction()
self.tool_execution_map.update(tool2name(DetectInteraction, ["run"], detect_interaction.run))
evaluate_trd = EvaluateTRD()
self.tool_execution_map.update(tool2name(EvaluateTRD, ["run"], evaluate_trd.run))
write_trd = WriteTRD()
self.tool_execution_map.update(tool2name(WriteTRD, ["run"], write_trd.run))
write_framework = WriteFramework()
self.tool_execution_map.update(tool2name(WriteFramework, ["run"], write_framework.run))
evaluate_framework = EvaluateFramework()
self.tool_execution_map.update(tool2name(EvaluateFramework, ["run"], evaluate_framework.run))
self.tool_execution_map.update(
{
write_trd.__name__: write_trd,
write_framework.__name__: write_framework,
}
)

View file

@ -27,8 +27,8 @@ class TRDToolExpRetriever(ExpRetriever):
EXAMPLE: str = """
## example 1
User Requirement: Given some user requirements, write a TRD, and implement the TRD within a software framework.
Explanation: Given a complete user requirement, to write the TRD (Technical Requirements Document), follow these steps: 1. Call `write_trd` to generate the TRD; 2. Call `write_framework` to generate the software framework code based on the TRD.
User Requirement: Given some user requirements, write a software framework.
Explanation: Given a complete user requirement, to write a software framework, you must follow all of the following steps to complete the TRD output required by the user: 1. Call 'write_trd' to generate TRD; 2. Call 'write_framework' to implement TRD into the software framework.
```json
[
{
@ -39,7 +39,6 @@ class TRDToolExpRetriever(ExpRetriever):
"instruction": "Execute `write_trd` to write the TRD based on user requirements",
"user_requirements": "This is user requirement balabala...",
"use_case_actors": "These are actors involved in the use case, balabala...",
"acknowledge": "## Interfaces\n balabala...",
}
},
{
@ -50,7 +49,6 @@ class TRDToolExpRetriever(ExpRetriever):
"instruction": "Execute `write_framework` to write the framework based on the TRD",
"use_case_actors": "These are actors involved in the use case, balabala...",
"trd": "<trd> returned by `write_trd`",
"acknowledge": "## Interfaces\n balabala...",
"additional_technical_requirements": "These are additional technical requirements, balabala..."
}
}

View file

@ -79,11 +79,189 @@ async def extract_external_interfaces(acknowledge: str) -> str:
return await compress_acknowledge.run(acknowledge=acknowledge)
async def mock_asearch_acknowledgement(use_case_actors: str):
return """
## Interfaces
- 用户登录
- Description: 用户从小程序/微应用发起请求需要验证用户的合法身份才能正常处理
- ID: 1
- Input Parameters:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
|authCode|用户临时免登授权码|String(64)|||
|loginTypeEnum|登录类型|String(20)|||
|authCorpId|用户所在企业/组织id|String(64)||微应用免登时传递|
|app|应用标识|String(3)|||
- Returns:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
|success|业务处理成功与否成功true否则false|boolean||只判断这个属性即可|
|message|错误信息可以用来提示|string|||
|code|返回状态码|string|||
|data|用户的sessionId|string|||
- 根据sessionId查询用户详细信息
- Description: 查询当前用户的详细信息 staffIdunionIdnameavatar等信息
- ID: 2
- Input Parameters:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
|NDA_SESSION|用户sessionId|String(64)|||
- Returns:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
|success|业务处理成功与否成功true否则false|boolean||只判断这个属性即可|
|message|错误信息可以用来提示|string|||
|code|返回状态码|string|||
|data|用户的详细信息|object|||
|-> corpId|当前用户企业 钉钉ID(小程序端会拿不到该信息)|string|||
|-> corpName|当前用户企业名称(小程序端会拿不到该信息)|string|||
|-> staffId|员工在当前企业内的唯一标识也称staffId(小程序端会拿不到该信息)|string|||
|-> unionId|员工在当前开发者企业账号范围内的唯一标识系统生成固定值不会改变|string|||
|-> name|当前用户的名称(小程序端会拿不到该信息)|string|||
|-> avatar|头像图片URL|string|||
- 查询国家情况描述
- Description: 根据国家code查询国家情况描述
- ID: 3
- Input Parameters:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
|countryCode|国家code|string|||
- Returns:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
|success|业务处理成功true否则false|boolean||只判断这个属性即可|
|message|错误信息可以用来提示|string|||
|code|返回状态码|string|||
|data|国家情况描述|object|||
|-> id|id|integer|||
|-> countryName|国家名称|string|||
|-> countryCode|国家code|string|||
|-> detail|产品法规分析|string|||
- 查询产品法规分析法律意见详情
- Description: 根据国家和业务线查询产品法规分析
- ID: 4
- Input Parameters:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
|countryCode|国家code|string|||
|businessCode|业务线code|string|||
- Returns:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
|success|业务处理成功true否则false|boolean||只判断这个属性即可|
|message|错误信息可以用来提示|string|||
|code|返回状态码|string|||
|data|法律意见详情|object|||
|-> id|id|integer|||
|-> countryName|国家名称|string|||
|-> countryCode|国家code|string|||
|-> businessLine|业务线|string|||
|-> businessCode|业务线code|string|||
|-> detail|产品法规分析|string|||
|-> signEntity|签约主体|string|||
- 查询法律意见总数
- Description: 法律意见总数查询
- ID: 5
- Input Parameters:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
- Returns:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
|success|业务处理成功true否则false|boolean||只判断这个属性即可|
|message|错误信息可以用来提示|string|||
|code|返回状态码|string|||
|data|总数|integer|||
- 查询所有国家和业务线信息列表
- Description: 查询所有国家和业务线信息列表
- ID: 6
- Input Parameters:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
- Returns:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
|success|业务处理成功true否则false|boolean||只判断这个属性即可|
|message|错误信息可以用来提示|string|||
|code|返回状态码|string|||
|data|所有数据列表|list of object|||
|-> country|国家code|string|||
|-> business|业务线code|string|||
|-> dataType|数据类型|string|||
|-> businessName|业务线名|string|||
|-> countryName|国家名|string|||
|-> businessNameEn|业务线名(英文)|string|||
- 调用法务中台antlaw接口
- ID: 7
- 国家/区域导游详情 & 法律意见详情 查询
- Description根据国家code查询国家/区域导游信息详情
- ID: 8
- Input Parameters:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
|countryCode|国家code|string|||
- Returns:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
|success|业务处理成功true否则false|boolean||只判断这个属性即可|
|message|错误信息可以用来提示|string|||
|code|返回状态码|string|||
|data|国家/区域导游详情|object|||
|-> country|||||
|-> -> id|id|integer|||
|-> -> country|国家code|string|||
|-> -> countryName|国家中文名称|string|||
|-> -> countryNameEn|国家英文名称|string|||
|-> -> content|国家导游中文详情json数组具体格式见下示例|list of object|||
|-> -> -> title|标题|object|||
|-> -> -> -> title|中文标题|string|||
|-> -> -> -> titleEn|英文标题|string|||
|-> -> -> contentList|标题下面的文字描述列表|list of object|||
|-> -> -> -> detail|内容中文详情|string|||
|-> -> -> -> detailEn|内容英文详情|string|||
|-> -> -> -> url|超链接|string|||
|-> legal|法务信息|object|||
|-> -> country|国家code|string|||
|-> -> businessList|业务线列表|list of object|||
|-> -> -> id|id|integer||新增时不传修改时传递|
|-> -> -> business|业务线code|string|||
|-> -> -> businessName|业务线中文名称|string|||
|-> -> -> businessNameEn|业务线英文名称|string|||
|-> -> -> content|业务线json具体如下|object|||
|-> -> -> -> detailEn|具体的详情英文内容|string|||
|-> -> -> -> detail|具体的详情内容|string|||
- 国家/区域导游列表分页查询
- Description: 分页查询国家/区域列表
- ID: 9
- Input Parameters:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
|pageSize|分页大小|integer||>=1|
|pageNum|分页大小|integer||>=1|
|country|国家code|string|||
|business|业务线code|string|||
- Returns:
|名称|描述|类型(长度)|必选|备注|
| :- | :- | :-: | :- | :- |
|success|业务处理成功true否则false|boolean||只判断这个属性即可|
|message|错误信息可以用来提示|string|||
|code|返回状态码|string|||
|data|国家/区域导游详情|list of object|||
|-> id|id|integer|||
|-> country|国家code|string|||
|-> countryName|国家中文名称|string|||
|-> countryNameEn|国家英文名称|string|||
|-> gmtCreate|创建时间|string|||
|-> gmtModified|更新时间|string|||
|total|数据总量|integer|||
"""
@register_tool(tags=["system design", "write trd", "Write a TRD"])
async def write_trd(
use_case_actors: str,
user_requirements: str,
acknowledge: str,
investment: float = 10,
context: Optional[Context] = None,
) -> (str, str):
@ -93,7 +271,6 @@ async def write_trd(
Args:
user_requirements (str): The new/incremental user requirements.
use_case_actors (str): Description of the actors involved in the use case.
acknowledge (str): A natural text of acknowledgement containing details about external system interfaces.
investment (float): Budget. Automatically stops optimizing TRD when the budget is overdrawn.
context (Context, optional): The context configuration. Default is None.
Returns:
@ -103,12 +280,10 @@ async def write_trd(
>>> # Given a new user requirements, write out a new TRD.
>>> user_requirements = "Write a 'snake game' TRD."
>>> use_case_actors = "- Actor: game player;\\n- System: snake game; \\n- External System: game center;"
>>> acknowledge = "## Interfaces\\n..."
>>> investment = 10.0
>>> trd = await write_trd(
>>> user_requirements=user_requirements,
>>> use_case_actors=use_case_actors,
>>> external_interfaces=external_interfaces,
>>> investment=investment,
>>> )
>>> print(trd)
@ -116,7 +291,8 @@ async def write_trd(
"""
context = context or Context(cost_manager=CostManager(max_budget=investment))
compress_acknowledge = CompressExternalInterfaces()
external_interfaces = await compress_acknowledge.run(acknowledge=acknowledge)
acknowledgement = await mock_asearch_acknowledgement(use_case_actors) # Replaced by acknowledgement_repo later.
external_interfaces = await compress_acknowledge.run(acknowledge=acknowledgement)
detect_interaction = DetectInteraction(context=context)
w_trd = WriteTRD(context=context)
evaluate_trd = EvaluateTRD(context=context)
@ -155,10 +331,9 @@ async def write_trd(
async def write_framework(
use_case_actors: str,
trd: str,
acknowledge: str,
additional_technical_requirements: str,
output_dir: Optional[str] = "",
investment: float = 10,
investment: float = 15.0,
context: Optional[Context] = None,
) -> str:
"""
@ -167,7 +342,6 @@ async def write_framework(
Args:
use_case_actors (str): Description of the use case actors involved.
trd (str): Technical Requirements Document detailing the requirements.
acknowledge (str): External acknowledgements or acknowledgements required.
additional_technical_requirements (str): Any additional technical requirements.
output_dir (str, optional): Path to save the software framework files. Default is en empty string.
investment (float): Budget. Automatically stops optimizing TRD when the budget is overdrawn.
@ -179,13 +353,11 @@ async def write_framework(
Example:
>>> use_case_actors = "- Actor: game player;\\n- System: snake game; \\n- External System: game center;"
>>> trd = "## TRD\\n..."
>>> acknowledge = "## Interfaces\\n..."
>>> additional_technical_requirements = "Using Java language, ..."
>>> investment = 10.0
>>> investment = 15.0
>>> framework = await write_framework(
>>> use_case_actors=use_case_actors,
>>> trd=trd,
>>> acknowledge=acknowledge,
>>> additional_technical_requirements=constraint,
>>> investment=investment,
>>> )
@ -198,12 +370,13 @@ async def write_framework(
is_pass = False
framework = ""
evaluation_conclusion = ""
acknowledgement = await mock_asearch_acknowledgement(use_case_actors) # Replaced by acknowledgement_repo later.
while not is_pass and (context.cost_manager.total_cost < context.cost_manager.max_budget):
try:
framework = await write_framework.run(
use_case_actors=use_case_actors,
trd=trd,
acknowledge=acknowledge,
acknowledge=acknowledgement,
legacy_output=framework,
evaluation_conclusion=evaluation_conclusion,
additional_technical_requirements=additional_technical_requirements,
@ -214,7 +387,7 @@ async def write_framework(
evaluation = await evaluate_framework.run(
use_case_actors=use_case_actors,
trd=trd,
acknowledge=acknowledge,
acknowledge=acknowledgement,
legacy_output=framework,
additional_technical_requirements=additional_technical_requirements,
)
@ -223,4 +396,4 @@ async def write_framework(
file_list = await save_framework(dir_data=framework, output_dir=output_dir)
logger.info(f"Output:\n{file_list}")
return "\n".join(file_list)
return "## Software Framework" + "".join([f"\n- {i}" for i in file_list])