From cf8efc9f67940e44e6248bed398cb696c9de36d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Mon, 17 Jun 2024 15:25:40 +0800 Subject: [PATCH] fixbug: write software framework, rfc243 --- examples/mgx_write_project_framework.py | 21 +- .../requirement_analysis/evaluate_action.py | 2 +- .../framework/__init__.py | 7 +- .../framework/evaluate_framework.py | 3 +- .../framework/write_framework.py | 6 +- metagpt/roles/architect.py | 39 +--- metagpt/strategy/experience_retriever.py | 6 +- metagpt/tools/libs/software_development.py | 201 ++++++++++++++++-- 8 files changed, 211 insertions(+), 74 deletions(-) diff --git a/examples/mgx_write_project_framework.py b/examples/mgx_write_project_framework.py index 2537e9b00..f82666e04 100644 --- a/examples/mgx_write_project_framework.py +++ b/examples/mgx_write_project_framework.py @@ -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__": diff --git a/metagpt/actions/requirement_analysis/evaluate_action.py b/metagpt/actions/requirement_analysis/evaluate_action.py index a94c9cffb..376c73f2c 100644 --- a/metagpt/actions/requirement_analysis/evaluate_action.py +++ b/metagpt/actions/requirement_analysis/evaluate_action.py @@ -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 diff --git a/metagpt/actions/requirement_analysis/framework/__init__.py b/metagpt/actions/requirement_analysis/framework/__init__.py index c59884746..96cf5f987 100644 --- a/metagpt/actions/requirement_analysis/framework/__init__.py +++ b/metagpt/actions/requirement_analysis/framework/__init__.py @@ -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("```") diff --git a/metagpt/actions/requirement_analysis/framework/evaluate_framework.py b/metagpt/actions/requirement_analysis/framework/evaluate_framework.py index 851b3af9f..8aa120712 100644 --- a/metagpt/actions/requirement_analysis/framework/evaluate_framework.py +++ b/metagpt/actions/requirement_analysis/framework/evaluate_framework.py @@ -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: diff --git a/metagpt/actions/requirement_analysis/framework/write_framework.py b/metagpt/actions/requirement_analysis/framework/write_framework.py index b2e3f2d67..c13fcdcaa 100644 --- a/metagpt/actions/requirement_analysis/framework/write_framework.py +++ b/metagpt/actions/requirement_analysis/framework/write_framework.py @@ -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; diff --git a/metagpt/roles/architect.py b/metagpt/roles/architect.py index cdedb8f73..404291e1e 100644 --- a/metagpt/roles/architect.py +++ b/metagpt/roles/architect.py @@ -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, + } + ) diff --git a/metagpt/strategy/experience_retriever.py b/metagpt/strategy/experience_retriever.py index 32fde7be6..75139fdc7 100644 --- a/metagpt/strategy/experience_retriever.py +++ b/metagpt/strategy/experience_retriever.py @@ -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": " returned by `write_trd`", - "acknowledge": "## Interfaces\n balabala...", "additional_technical_requirements": "These are additional technical requirements, balabala..." } } diff --git a/metagpt/tools/libs/software_development.py b/metagpt/tools/libs/software_development.py index 109400dcb..16ab74621 100644 --- a/metagpt/tools/libs/software_development.py +++ b/metagpt/tools/libs/software_development.py @@ -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: 查询当前用户的详细信息,如 staffId,unionId,name,avatar等信息 + - 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])