diff --git a/examples/write_project_framework.py b/examples/write_project_framework.py index bf713bbce..8d23695a7 100644 --- a/examples/write_project_framework.py +++ b/examples/write_project_framework.py @@ -163,7 +163,7 @@ async def develop( ) # Save - file_list = await save_framework(dir_data=framework, output_dir=output_dir) + file_list = await save_framework(dir_data=framework, trd=trd, output_dir=output_dir) logger.info(f"Output:\n{file_list}") diff --git a/metagpt/actions/requirement_analysis/framework/__init__.py b/metagpt/actions/requirement_analysis/framework/__init__.py index 96cf5f987..86f6a28e9 100644 --- a/metagpt/actions/requirement_analysis/framework/__init__.py +++ b/metagpt/actions/requirement_analysis/framework/__init__.py @@ -17,12 +17,40 @@ from pydantic import BaseModel from metagpt.actions.requirement_analysis.framework.evaluate_framework import EvaluateFramework from metagpt.actions.requirement_analysis.framework.write_framework import WriteFramework from metagpt.const import DEFAULT_WORKSPACE_ROOT -from metagpt.tools.tool_registry import register_tool 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]: +async def save_framework( + dir_data: str, trd: Optional[str] = None, output_dir: Optional[Union[str, Path]] = None +) -> List[str]: + """ + Saves framework data to files based on input JSON data and optionally saves a TRD (technical requirements document). + + Args: + dir_data (str): JSON data in string format enclosed in triple backticks ("```json" "...data..." "```"). + trd (str, optional): Technical requirements document content to be saved. Defaults to None. + output_dir (Union[str, Path], optional): Output directory path where files will be saved. If not provided, + a default directory is created based on the current timestamp and a random UUID suffix. + + Returns: + List[str]: List of file paths where data was saved. + + Raises: + Any exceptions raised during file writing operations. + + Notes: + - JSON data should be provided in the format "```json ...data... ```". + - The function ensures that paths and filenames are correctly formatted and creates necessary directories. + + Example: + ```python + dir_data = "```json\n[{\"path\": \"/folder\", \"filename\": \"file1.txt\", \"content\": \"Some content\"}]\n```" + trd = "Technical requirements document content." + output_dir = '/path/to/output/dir' + saved_files = await save_framework(dir_data, trd, output_dir) + print(saved_files) + ``` + """ output_dir = ( Path(output_dir) if output_dir @@ -38,6 +66,10 @@ async def save_framework(dir_data: str, output_dir: Optional[Union[str, Path]] = filename: str content: str + if trd: + pathname = output_dir / "TRD.md" + await awrite(filename=pathname, data=trd) + files = [] for i in items: v = Data.model_validate(i) diff --git a/metagpt/actions/requirement_analysis/framework/evaluate_framework.py b/metagpt/actions/requirement_analysis/framework/evaluate_framework.py index f81da6f5b..74f8a024a 100644 --- a/metagpt/actions/requirement_analysis/framework/evaluate_framework.py +++ b/metagpt/actions/requirement_analysis/framework/evaluate_framework.py @@ -93,9 +93,10 @@ You need to refer to the content of the "Legacy TRD" section to check for any er The content of "Actor, System, External System" provides an explanation of actors and systems that appear in UML Use Case diagram; 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"; +Do not implement the interface in "Acknowledge" section until it is used in "Legacy TRD", you can check whether they are the same interface by looking at its ID or url; 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; +Do the parameters of the interface of the external system used in the code comply with it's specifications in 'Acknowledge'? Return a markdown JSON object with: - a "is_pass" key containing a true boolean value if there is not any issue in the "Legacy Outputs"; - an "issues" key containing a string list of natural text about the issues found in the "Legacy Outputs" if any, each issue found must provide a detailed description and include reasons; diff --git a/metagpt/actions/requirement_analysis/framework/write_framework.py b/metagpt/actions/requirement_analysis/framework/write_framework.py index 8bb66e948..2aa03f447 100644 --- a/metagpt/actions/requirement_analysis/framework/write_framework.py +++ b/metagpt/actions/requirement_analysis/framework/write_framework.py @@ -66,6 +66,7 @@ class WriteFramework(Action): {"path":"balabala", "filename":"...", ... """ + acknowledge = await self._extract_external_interfaces(trd=trd, knowledge=acknowledge) prompt = PROMPT.format( use_case_actors=use_case_actors, trd=to_markdown_code_block(val=trd), @@ -93,6 +94,22 @@ class WriteFramework(Action): json.loads(json_data) # validate return json_data + @retry( + wait=wait_random_exponential(min=1, max=20), + stop=stop_after_attempt(6), + after=general_after_log(logger), + ) + async def _extract_external_interfaces(self, trd: str, knowledge: str) -> str: + prompt = f"## TRD\n{to_markdown_code_block(val=trd)}\n\n## Knowledge\n{to_markdown_code_block(val=knowledge)}\n" + rsp = await self.llm.aask( + prompt, + system_msgs=[ + "You are a tool that removes impurities from articles; you can remove irrelevant content from articles.", + 'Identify which interfaces are used in "TRD"? Remove the relevant content of the interfaces NOT used in "TRD" from "Knowledge" and return the simplified content of "Knowledge".', + ], + ) + return rsp + PROMPT = """ ## Actor, System, External System diff --git a/metagpt/actions/requirement_analysis/trd/evaluate_trd.py b/metagpt/actions/requirement_analysis/trd/evaluate_trd.py index a46a38295..7feb32112 100644 --- a/metagpt/actions/requirement_analysis/trd/evaluate_trd.py +++ b/metagpt/actions/requirement_analysis/trd/evaluate_trd.py @@ -102,7 +102,9 @@ In order to integrate the full upstream and downstream data flow, the "TRD Desig Which interactions from "Interaction Events" correspond to which steps in "TRD Design"? Please provide reasons. Which aspects of "TRD Design" and "Interaction Events" do not align with the descriptions in "User Requirements"? Please provide detailed descriptions and reasons. If the descriptions in "User Requirements" are divided into multiple steps in "TRD Design" and "Interaction Events," it can be considered compliant with the descriptions in "User Requirements" as long as it does not conflict with them; -There is a possibility of missing details in the descriptions of "User Requirements". Any additional steps in "TRD Design" and "Interaction Events" are considered compliant with "User Requirements" as long as they do not conflict with the descriptions provided in "User Requirements". +There is a possibility of missing details in the descriptions of "User Requirements". Any additional steps in "TRD Design" and "Interaction Events" are considered compliant with "User Requirements" as long as they do not conflict with the descriptions provided in "User Requirements"; +If there are interaction events with external systems in "TRD Design", you must explicitly specify the ID of the external interface to use for the interaction events; +Does the sequence of steps in "Interaction Events" cause performance or cost issues? Please provide detailed descriptions and reasons; Return a markdown JSON object with: - a "is_pass" key containing a true boolean value if there is not any issue in the "TRD Design"; - an "issues" key containing a string list of natural text about the issues found in the "TRD Design" if any, each issue found must provide a detailed description and include reasons; diff --git a/metagpt/actions/requirement_analysis/trd/write_trd.py b/metagpt/actions/requirement_analysis/trd/write_trd.py index 94fedb3b2..bad93ea76 100644 --- a/metagpt/actions/requirement_analysis/trd/write_trd.py +++ b/metagpt/actions/requirement_analysis/trd/write_trd.py @@ -206,7 +206,8 @@ The content of "Available External Interfaces" provides the candidate steps, alo 3.1. In the description, use the actor and system names defined in the "Actor, System, External System" section to describe the interactors; 3.2. The content should include the original text of the requirements from "User Requirements"; 3.3. In the TRD, each step can involve a maximum of two participants. If there are more than two participants, the step needs to be further split; - 3.4. In the TRD, each step must include detailed descriptions, inputs, outputs, participants, initiator, and the rationale for the step's existence. The rationale should reference the original text to justify it, such as specifying which interface requires the output of this step as parameters or where in the requirements this step is mandated, etc. + 3.4. In the TRD, each step must include detailed descriptions, inputs, outputs, participants, initiator, and the rationale for the step's existence. The rationale should reference the original text to justify it, such as specifying which interface requires the output of this step as parameters or where in the requirements this step is mandated, etc.; + 3.5. In the TRD, if you need to call interfaces of external systems, you must explicitly specify the interface IDs of the external systems you want to call; """ INCREMENTAL_PROMPT = """ diff --git a/metagpt/strategy/experience_retriever.py b/metagpt/strategy/experience_retriever.py index a43b94ce0..4ba4652fd 100644 --- a/metagpt/strategy/experience_retriever.py +++ b/metagpt/strategy/experience_retriever.py @@ -19,6 +19,224 @@ class DummyExpRetriever(ExpRetriever): EXAMPLE: str = "" +class TRDAllExpRetriever(ExpRetriever): + def retrieve(self, context: str = "") -> str: + return self.EXAMPLE + + EXAMPLE: str = """ +## example 1 +User Requirement: Given some user requirements, write a software framework. +Explanation: Given a complete user requirement, to write a TRD and 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 +[ + { + "command_name": "write_trd_and_framework", + "task_id": "1", + "dependent_task_ids": [], + "instruction": "Execute `write_trd_and_framework` to write a TRD and software framework based on user requirements", + "args": { + "user_requirements": "This is user requirement balabala...", + "use_case_actors": "These are actors involved in the use case, balabala...", + "additional_technical_requirements": "These are additional technical requirements, balabala..." + } + } +] +``` +## example 2 +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 +[ + { + "command_name": "write_trd", + "task_id": "1", + "dependent_task_ids": [], + "instruction": "Execute `write_trd` to write the TRD based on user requirements", + "args": { + "user_requirements": "This is user requirement balabala...", + "use_case_actors": "These are actors involved in the use case, balabala...", + } + }, + { + "command_name": "write_framework", + "task_id": "2", + "dependent_task_ids": ["1"], + "instruction": "Execute `write_framework` to write the framework based on the TRD", + "args": { + "use_case_actors": "These are actors involved in the use case, balabala...", + "trd": " returned by `write_trd`", + "additional_technical_requirements": "These are additional technical requirements, balabala..." + } + } +] +``` +## example 3 +User Requirement: Given some user requirements, write a TRD, and implement the TRD within a software framework. +Explanation: + Given a complete requirement, 要写TRD需要follow如下步骤: + 1. 调用`CompressExternalInterfaces.run`,从acknowledgement中抽取external interfaces的信息; + 2. 按顺序执行如下步骤: + 2.1. 执行`DetectInteraction.run`; + 2.2. 执行`WriteTRD.run`; + 2.3. 执行`EvaluateTRD.run`; + 2.4. 检查`EvaluateTRD.run`的结果: + 2.4.1. 如果`EvaluateTRD.run`的结果被判定为pass,则执行步骤3; + 2.4.2. 如果`EvaluateTRD.run`的结果被判定为deny,则继续执行步骤2; + 3. 按顺序执行如下步骤: + 3.1. 执行`WriteFramework.run`; + 3.2. 执行`EvaluateFramework.run`; + 3.3. 检查`EvaluateFramework.run`的结果: + 3.3.1. 如果`EvaluateFramework.run`的结果被判定为pass,则执行步骤4; + 3.3.2. 如果`EvaluateFramework.run`的结果被判定为deny,则继续执行步骤3; + 3.3.3. 如果已经重复执行步骤3超过9次,则执行步骤4; + 4. 执行`save_framework`,将`WriteFramework.run`的结果保存下来; +```json +[ + { + "command_name": "CompressExternalInterfaces.run", + "args": { + "task_id": "1", + "dependent_task_ids": [], + "instruction": "Execute `DetectInteraction.run` to extract external interfaces information from acknowledgement.", + "acknowledge": "## Interfaces\n balabala..." + } + }, + { + "command_name": "DetectInteraction.run", + "args": { + "task_id": "2", + "dependent_task_ids": ["1"], + "instruction": "Execute `DetectInteraction.run` to extract external interfaces information from acknowledgement.", + "user_requirements": "This is user requirement balabala...", + "use_case_actors": "These are actors involved in the use case, balabala...", + } + }, + { + "command_name": "WriteTRD.run", + "args": { + "task_id": "3", + "dependent_task_ids": ["2"], + "instruction": "Execute `WriteTRD.run` to write TRD", + "user_requirements": "This is user requirement balabala...", + "use_case_actors": "These are actors involved in the use case, balabala...", + "available_external_interfaces": " returned by `CompressExternalInterfaces.run`", + "interaction_events": " returned by `DetectInteraction.run`" + } + }, + { + "command_name": "EvaluateTRD.run", + "args": { + "task_id": "4", + "dependent_task_ids": ["3"], + "instruction": "Execute `EvaluateTRD.run` to evaluate the TRD", + "user_requirements": "This is user requirement balabala...", + "use_case_actors": "These are actors involved in the use case, balabala...", + "available_external_interfaces": " returned by `CompressExternalInterfaces.run`", + "interaction_events": "", + "trd": " returned by `EvaluateTRD.run`" + } + }, + { + "command_name": "DetectInteraction.run", + "args": { + "task_id": "5", + "dependent_task_ids": ["4"], + "instruction": "Execute `DetectInteraction.run` to extract external interfaces information from acknowledgement.", + "user_requirements": "This is user requirement balabala...", + "use_case_actors": "These are actors involved in the use case, balabala...", + "evaluation_conclusion": " returned by `EvaluateTRD.run`" + } + }, + { + "command_name": "WriteTRD.run", + "args": { + "task_id": "6", + "dependent_task_ids": ["5"], + "instruction": "Execute `WriteTRD.run` to write TRD", + "user_requirements": "This is user requirement balabala...", + "use_case_actors": "These are actors involved in the use case, balabala...", + "available_external_interfaces": " returned by `CompressExternalInterfaces.run`", + "interaction_events": " returned by `DetectInteraction.run`", + "previous_version_trd": " returned by `WriteTRD.run`" + } + }, + { + "command_name": "EvaluateTRD.run", + "args": { + "task_id": "7", + "dependent_task_ids": ["6"], + "instruction": "Execute `EvaluateTRD.run` to evaluate the TRD", + "user_requirements": "This is user requirement balabala...", + "use_case_actors": "These are actors involved in the use case, balabala...", + "available_external_interfaces": " returned by `CompressExternalInterfaces.run`", + "interaction_events": " returned by `DetectInteraction.run`", + "trd": " returned by `WriteTRD.run`", + } + }, + { + "command_name": "WriteFramework.run", + "args": { + "task_id": "8", + "dependent_task_ids": ["7"], + "instruction": "Execute `WriteFramework.run` to write a software framework according to the TRD", + "use_case_actors": "These are actors involved in the use case, balabala...", + "trd": " returned by `WriteTRD.run`", + "acknowledge": "## Interfaces\n balabala...", + "additional_technical_requirements": "These are additional technical requirements, balabala...", + } + }, + { + "command_name": "EvaluateFramework.run", + "args": { + "task_id": "9", + "dependent_task_ids": ["8"], + "instruction": "Execute `EvaluateFramework.run` to evaluate the software framework returned by `WriteFramework.run`", + "use_case_actors": "These are actors involved in the use case, balabala...", + "trd": " returned by `WriteTRD.run`", + "acknowledge": "## Interfaces\n balabala...", + "legacy_output": " returned by `WriteFramework.run`", + "additional_technical_requirements": "These are additional technical requirements, balabala...", + } + }, + { + "command_name": "WriteFramework.run", + "args": { + "task_id": "10", + "dependent_task_ids": ["9"], + "instruction": "Execute `WriteFramework.run` to write a software framework according to the TRD", + "use_case_actors": "These are actors involved in the use case, balabala...", + "trd": " returned by `WriteTRD.run`", + "acknowledge": "## Interfaces\n balabala...", + "additional_technical_requirements": "These are additional technical requirements, balabala...", + } + }, + { + "command_name": "EvaluateFramework.run", + "args": { + "task_id": "11", + "dependent_task_ids": ["10"], + "instruction": "Execute `EvaluateFramework.run` to evaluate the software framework returned by `WriteFramework.run`", + "use_case_actors": "These are actors involved in the use case, balabala...", + "trd": " returned by `WriteTRD.run`", + "acknowledge": "## Interfaces\n balabala...", + "legacy_output": " returned by `WriteFramework.run`", + "additional_technical_requirements": "These are additional technical requirements, balabala...", + } + }, + { + "command_name": "save_framework", + "args": { + "task_id": "12", + "dependent_task_ids": ["11"], + "instruction": "Execute `save_framework` to save the software framework returned by `WriteFramework.run`", + "dir_data": " returned by `WriteFramework.run`", + } + } +] +``` + """ + + class TRDToolExpRetriever(ExpRetriever): """A TRD-related experience retriever that returns empty string.""" diff --git a/metagpt/tools/libs/software_development.py b/metagpt/tools/libs/software_development.py index a527c5806..431766343 100644 --- a/metagpt/tools/libs/software_development.py +++ b/metagpt/tools/libs/software_development.py @@ -159,7 +159,7 @@ async def write_framework( trd: str, additional_technical_requirements: str, output_dir: Optional[str] = "", - investment: float = 15.0, + investment: float = 20.0, context: Optional[Context] = None, ) -> str: """ @@ -220,7 +220,7 @@ async def write_framework( is_pass = evaluation.is_pass evaluation_conclusion = evaluation.conclusion - file_list = await save_framework(dir_data=framework, output_dir=output_dir) + file_list = await save_framework(dir_data=framework, trd=trd, output_dir=output_dir) logger.info(f"Output:\n{file_list}") return "## Software Framework" + "".join([f"\n- {i}" for i in file_list]) @@ -230,7 +230,7 @@ async def write_trd_and_framework( use_case_actors: str, user_requirements: str, additional_technical_requirements: str, - investment: float = 15.0, + investment: float = 17.0, output_dir: Optional[str] = "", context: Optional[Context] = None, ) -> str: diff --git a/tests/data/requirements/1.constraint.md b/tests/data/requirements/1.constraint.md index 02aa1ffb3..b8c42be63 100644 --- a/tests/data/requirements/1.constraint.md +++ b/tests/data/requirements/1.constraint.md @@ -1,4 +1,3 @@ -- 基于dingtalk框架编码; -- 用java编程语言; -- 接口类的功能要放到implement子类中实现; -- 法务中台网址:`https://mock.apipark.cn/m1/4717294-4369585-default` \ No newline at end of file +- 用javascript语言, 法务查询者与国际小超人钉钉小程序之间UI用web; +- 法务中台网址:`https://mock.apipark.cn/m1/4717294-4369585-default`; +- 写代码时,不要单元测试代码; \ No newline at end of file