diff --git a/examples/mgx_write_project_framework.py b/examples/mgx_write_project_framework.py index dca684478..359b90080 100644 --- a/examples/mgx_write_project_framework.py +++ b/examples/mgx_write_project_framework.py @@ -19,11 +19,12 @@ from metagpt.config2 import Config from metagpt.const import DEFAULT_WORKSPACE_ROOT from metagpt.context import Context from metagpt.environment import Environment +from metagpt.environment.mgx.mgx_env import MGXEnv from metagpt.logs import logger from metagpt.roles import Architect from metagpt.roles.di.team_leader import TeamLeader from metagpt.schema import AIMessage, UserMessage -from metagpt.utils.common import any_to_str, aread, to_markdown_code_block +from metagpt.utils.common import aread, to_markdown_code_block app = typer.Typer(add_completion=False) @@ -37,19 +38,9 @@ class EnvBuilder(BaseModel): output_dir: Path def build(self) -> Environment: - env = Environment(context=self.context) + env = MGXEnv(context=self.context) team_leader = TeamLeader() architect = Architect() - architect.tools.extend( - [ - "CompressExternalInterfaces", - "DetectInteraction", - "EvaluateTRD", - "WriteTRD", - "WriteFramework", - "EvaluateFramework", - ] - ) # Prepare context use_case_actors = "".join([f"- {v}: {k}\n" for k, v in self.actors.items()]) @@ -111,7 +102,8 @@ async def develop( {user_requirements} """ env.publish_message( - UserMessage(content=msg.format(user_requirements="\n".join(user_requirements)), send_to=any_to_str(TeamLeader)) + UserMessage(content=msg.format(user_requirements="\n".join(user_requirements)), send_to="Bob"), + user_defined_recipient="Bob", ) while not env.is_idle: diff --git a/examples/write_project_framework.py b/examples/write_project_framework.py index 4e8a03789..bf713bbce 100644 --- a/examples/write_project_framework.py +++ b/examples/write_project_framework.py @@ -49,7 +49,7 @@ async def _write_trd( evaluation_conclusion = "" interaction_events = "" trd = "" - while not is_pass: + while not is_pass and (context.cost_manager.total_cost < context.cost_manager.max_budget): interaction_events = await detect_interaction.run( user_requirements=r, use_case_actors=use_case_actors, @@ -99,7 +99,7 @@ async def _write_framework(context: Context, use_case_actors: str, trd: str, ack is_pass = False framework = "" evaluation_conclusion = "" - while not is_pass: + 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, @@ -175,6 +175,7 @@ def startup( 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."), + investment: float = typer.Option(default=15.0, help="Dollar amount to invest in the AI company."), ): if llm_config and Path(llm_config).exists(): config = Config.from_yaml_file(Path(llm_config)) @@ -182,6 +183,7 @@ def startup( logger.info("GPT 4 turbo is recommended") config = Config.default() ctx = Context(config=config) + ctx.cost_manager.max_budget = investment asyncio.run( develop(ctx, user_requirement_filename, actors_filename, acknowledge_filename, constraint_filename, output_dir) diff --git a/metagpt/actions/requirement_analysis/framework/__init__.py b/metagpt/actions/requirement_analysis/framework/__init__.py index 0f6022444..c59884746 100644 --- a/metagpt/actions/requirement_analysis/framework/__init__.py +++ b/metagpt/actions/requirement_analysis/framework/__init__.py @@ -17,7 +17,7 @@ from metagpt.actions.requirement_analysis.framework.evaluate_framework import Ev 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 CodeParser, awrite +from metagpt.utils.common import awrite @register_tool(tags=["software framework"]) @@ -25,7 +25,7 @@ async def save_framework(dir_data: str, output_dir: Optional[Union[str, Path]] = output_dir = Path(output_dir) if output_dir else DEFAULT_WORKSPACE_ROOT / uuid.uuid4().hex output_dir.mkdir(parents=True, exist_ok=True) - json_data = CodeParser.parse_code(text=dir_data, lang="json") + json_data = dir_data.removeprefix("```json").removesuffix("```") items = json.loads(json_data) class Data(BaseModel): @@ -36,6 +36,8 @@ async def save_framework(dir_data: str, output_dir: Optional[Union[str, Path]] = files = [] for i in items: v = Data.model_validate(i) + if v.path and v.path[0] == "/": + v.path = "." + v.path pathname = output_dir / v.path pathname.mkdir(parents=True, exist_ok=True) pathname = pathname / v.filename diff --git a/metagpt/actions/requirement_analysis/framework/evaluate_framework.py b/metagpt/actions/requirement_analysis/framework/evaluate_framework.py index 10b2c65b9..851b3af9f 100644 --- a/metagpt/actions/requirement_analysis/framework/evaluate_framework.py +++ b/metagpt/actions/requirement_analysis/framework/evaluate_framework.py @@ -8,10 +8,16 @@ """ from metagpt.actions.requirement_analysis import EvaluateAction, EvaluationData +from metagpt.tools.tool_registry import register_tool from metagpt.utils.common import to_markdown_code_block +@register_tool(include_functions=["run"]) class EvaluateFramework(EvaluateAction): + """WriteFramework deal with the following situations: + 1. Given a TRD and the software framework based on the TRD, evaluate the quality of the software framework. + """ + async def run( self, *, @@ -21,6 +27,40 @@ class EvaluateFramework(EvaluateAction): legacy_output: str, additional_technical_requirements: str, ) -> EvaluationData: + """ + Run the evaluation of the software framework based on the provided TRD and related parameters. + + Args: + use_case_actors (str): A description of the actors involved in the use case. + trd (str): The Technical Requirements Document (TRD) that outlines the requirements for the software framework. + acknowledge (str): External acknowledgments or acknowledgments information related to the framework. + legacy_output (str): The previous versions of software framework returned by `WriteFramework`. + additional_technical_requirements (str): Additional technical requirements that need to be considered during evaluation. + + Returns: + EvaluationData: An object containing the results of the evaluation. + + Example: + >>> evaluate_framework = EvaluateFramework() + >>> use_case_actors = "- Actor: game player;\\n- System: snake game; \\n- External System: game center;" + >>> trd = "## TRD\\n..." + >>> acknowledge = "## Interfaces\\n..." + >>> framework = '{"path":"balabala", "filename":"...", ...' + >>> constraint = "Using Java language, ..." + >>> evaluation = await evaluate_framework.run( + >>> use_case_actors=use_case_actors, + >>> trd=trd, + >>> acknowledge=acknowledge, + >>> legacy_output=framework, + >>> additional_technical_requirements=constraint, + >>> ) + >>> is_pass = evaluation.is_pass + >>> print(is_pass) + True + >>> evaluation_conclusion = evaluation.conclusion + >>> print(evaluation_conclusion) + Balabala... + """ prompt = PROMPT.format( use_case_actors=use_case_actors, trd=to_markdown_code_block(val=trd), diff --git a/metagpt/actions/requirement_analysis/framework/write_framework.py b/metagpt/actions/requirement_analysis/framework/write_framework.py index 5101a4d5e..b2e3f2d67 100644 --- a/metagpt/actions/requirement_analysis/framework/write_framework.py +++ b/metagpt/actions/requirement_analysis/framework/write_framework.py @@ -12,10 +12,16 @@ from tenacity import retry, stop_after_attempt, wait_random_exponential from metagpt.actions import Action from metagpt.logs import logger -from metagpt.utils.common import CodeParser, general_after_log, to_markdown_code_block +from metagpt.tools.tool_registry import register_tool +from metagpt.utils.common import general_after_log, to_markdown_code_block +@register_tool(include_functions=["run"]) class WriteFramework(Action): + """WriteFramework deal with the following situations: + 1. Given a TRD, write out the software framework. + """ + async def run( self, *, @@ -26,6 +32,40 @@ class WriteFramework(Action): evaluation_conclusion: str, additional_technical_requirements: str, ) -> str: + """ + Run the action to generate a software framework based on the provided TRD and related information. + + 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. + legacy_output (str): Previous version of the software framework returned by `WriteFramework.run`. + evaluation_conclusion (str): Conclusion from the evaluation of the requirements. + additional_technical_requirements (str): Any additional technical requirements. + + Returns: + str: The generated software framework as a string. + + Example: + >>> write_framework = WriteFramework() + >>> use_case_actors = "- Actor: game player;\\n- System: snake game; \\n- External System: game center;" + >>> trd = "## TRD\\n..." + >>> acknowledge = "## Interfaces\\n..." + >>> legacy_output = '{"path":"balabala", "filename":"...", ...' + >>> evaluation_conclusion = "Balabala..." + >>> constraint = "Using Java language, ..." + >>> framework = await write_framework.run( + >>> use_case_actors=use_case_actors, + >>> trd=trd, + >>> acknowledge=acknowledge, + >>> legacy_output=framework, + >>> evaluation_conclusion=evaluation_conclusion, + >>> additional_technical_requirements=constraint, + >>> ) + >>> print(framework) + {"path":"balabala", "filename":"...", ... + + """ prompt = PROMPT.format( use_case_actors=use_case_actors, trd=to_markdown_code_block(val=trd), @@ -43,7 +83,13 @@ class WriteFramework(Action): ) async def _write(self, prompt: str) -> str: rsp = await self.llm.aask(prompt) - json_data = CodeParser.parse_code(text=rsp, lang="json") + # Do not use `CodeParser` here. + tags = ["```json", "```"] + bix = rsp.find(tags[0]) + eix = rsp.rfind(tags[1]) + if bix >= 0: + rsp = rsp[bix : eix + len(tags[1])] + json_data = rsp.removeprefix("```json").removesuffix("```") json.loads(json_data) # validate return json_data @@ -76,13 +122,12 @@ The descriptions of the interfaces used in the "TRD" can be found in the "Acknow 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 class diagram in PlantUML format; -- The `SEQUENCE.md` file should include sequence diagram in PlantUML format; Return a markdown JSON object list, each object containing: - a "path" key with a value specifying its path; diff --git a/metagpt/prompts/di/team_leader.py b/metagpt/prompts/di/team_leader.py index 5f2f6b27d..8abd13165 100644 --- a/metagpt/prompts/di/team_leader.py +++ b/metagpt/prompts/di/team_leader.py @@ -18,8 +18,10 @@ Note: 1. If the requirement is a pure DATA-RELATED requirement, such as bug fixes, issue reporting, environment setup, terminal operations, pip install, web browsing, web scraping, web searching, web imitation, data science, data analysis, machine learning, deep learning, text-to-image etc. DON'T decompose it, assign a single task with the original user requirement as instruction directly to Data Analyst. 2. If the requirement is developing a software, game, app, or website, excluding the above data-related tasks, you should decompose the requirement into multiple tasks and assign them to different team members based on their expertise, usually the sequence of Product Manager -> Architect -> Project Manager -> Engineer -> (optional: QaEngine if present) -> (optional: DataAnalyst if user requests deployment), each assigned ONE task. When publishing message to Product Manager, you should directly copy the full original user requirement. 3. If the requirement contains both DATA-RELATED part mentioned in 1 and software development part mentioned in 2, you should decompose the software development part and assign them to different team members based on their expertise, and assign the DATA-RELATED part to Data Analyst David directly. -4. It is helpful for Engineer to have both the system design and the project schedule for writing the code, so include paths of both files (if available) when publishing message to Engineer. -5. If the requirement is writing a TRD, you should assign it to Architect. When publishing message to Architect, you should directly copy the full original user requirement. +4. If the requirement is a common-sense, logical, or math problem, you should respond directly without assigning any task to team members. +5. If you think the requirement is not clear or ambiguous, you should ask the user for clarification immediately. Assign tasks only after all info is clear. +6. It is helpful for Engineer to have both the system design and the project schedule for writing the code, so include paths of both files (if available) when publishing message to Engineer. +7. If the requirement is writing a TRD, you should assign it to Architect. When publishing message to Architect, you should directly copy the full original user requirement. """ FINISH_CURRENT_TASK_CMD = """ diff --git a/metagpt/roles/architect.py b/metagpt/roles/architect.py index afa234a3c..90781abef 100644 --- a/metagpt/roles/architect.py +++ b/metagpt/roles/architect.py @@ -7,7 +7,18 @@ """ 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.utils.common import tool2name class Architect(RoleZero): @@ -29,9 +40,19 @@ class Architect(RoleZero): "libraries. Use same language as user requirement" ) - instruction: str = """Use WriteDesign tool to write a system design document""" + 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"] + tools: list[str] = [ + "Editor:write,read,write_content", + "RoleZero", + "WriteDesign", + "CompressExternalInterfaces", + "DetectInteraction", + "EvaluateTRD", + "WriteTRD", + "WriteFramework", + "EvaluateFramework", + ] def __init__(self, **kwargs) -> None: super().__init__(**kwargs) @@ -45,10 +66,17 @@ class Architect(RoleZero): self._watch({WritePRD}) def _update_tool_execution(self): - wd = WriteDesign() - self.tool_execution_map.update( - { - "WriteDesign.run": wd.run, - "WriteDesign": wd.run, # alias - } - ) + 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)) diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index dc28e53a6..44e658ed4 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -3,7 +3,7 @@ from __future__ import annotations import inspect import json import traceback -from typing import Literal, Tuple +from typing import Callable, Literal, Tuple from pydantic import model_validator @@ -42,7 +42,7 @@ class RoleZero(Role): # Tools tools: list[str] = [] # Use special symbol [""] to indicate use of all registered tools tool_recommender: ToolRecommender = None - tool_execution_map: dict[str, callable] = {} + tool_execution_map: dict[str, Callable] = {} special_tool_commands: list[str] = ["Plan.finish_current_task", "end"] # Equipped with three basic tools by default for optional use editor: Editor = Editor() @@ -175,7 +175,7 @@ class RoleZero(Role): actions_taken += 1 return rsp # return output from the last action - async def _run_commands(self, commands) -> list: + async def _run_commands(self, commands) -> str: outputs = [] for cmd in commands: # handle special command first @@ -247,7 +247,7 @@ class RoleZero(Role): if not isinstance(self.rc.env, MGXEnv): return "Not in MGXEnv, command will not be executed." - return await self.rc.env.get_human_input(question, sent_from=self) + return await self.rc.env.ask_human(question, sent_from=self) async def reply_to_human(self, content: str) -> str: """Reply to human user with the content provided. Use this when you have a clear answer or solution to the user's question.""" diff --git a/metagpt/roles/di/team_leader.py b/metagpt/roles/di/team_leader.py index 92ddcab04..2932dd7f0 100644 --- a/metagpt/roles/di/team_leader.py +++ b/metagpt/roles/di/team_leader.py @@ -18,7 +18,8 @@ class TeamLeader(RoleZero): profile: str = "Team Leader" system_msg: list[str] = [SYSTEM_PROMPT] - max_react_loop: int = 1 # TeamLeader only reacts once each time + # TeamLeader only reacts once each time, but may encounter errors or need to ask human, thus allowing 2 more turns + max_react_loop: int = 3 tools: list[str] = ["Plan", "RoleZero", "TeamLeader"] diff --git a/metagpt/strategy/experience_retriever.py b/metagpt/strategy/experience_retriever.py index 97fb9a9b0..d04f0b5a1 100644 --- a/metagpt/strategy/experience_retriever.py +++ b/metagpt/strategy/experience_retriever.py @@ -14,7 +14,174 @@ class DummyExpRetriever(ExpRetriever): """A dummy experience retriever that returns empty string.""" def retrieve(self, context: str = "") -> str: - return "" + return self.EXAMPLE + + 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 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 SimpleExpRetriever(ExpRetriever): @@ -83,6 +250,9 @@ class SimpleExpRetriever(ExpRetriever): "args": { "content": "I have assigned the tasks to the team members. Alice will create the PRD, Bob will design the software architecture, Eve will break down the architecture into tasks, Alex will implement the core game logic, and Edward will write comprehensive tests. The team will work on the project accordingly", } + }, + { + "command_name": "end" } ] ``` @@ -113,6 +283,9 @@ class SimpleExpRetriever(ExpRetriever): "args": { "content": "I have assigned the task to David. He will break down the task further by himself and starts solving it.", } + }, + { + "command_name": "end" } ] ``` @@ -142,6 +315,9 @@ class SimpleExpRetriever(ExpRetriever): "args": { "content": "Alice has completed the PRD. I have marked her task as finished and sent the PRD to Bob. Bob will work on the software architecture.", } + }, + { + "command_name": "end" } ] ``` @@ -156,13 +332,16 @@ class SimpleExpRetriever(ExpRetriever): "args": { "content": "The team is currently working on ... We have completed ...", } + }, + { + "command_name": "end" } ] ``` """ def retrieve(self, context: str = "") -> str: - return "" # byRFC243 self.EXAMPLE + return self.EXAMPLE class KeywordExpRetriever(ExpRetriever): @@ -213,6 +392,7 @@ Explanation: Launching a service requires Terminal tool with daemon mode, write "assignee": "David" } }, +] """ diff --git a/metagpt/tools/libs/browser.py b/metagpt/tools/libs/browser.py index 955058ea0..1b1b3d82d 100644 --- a/metagpt/tools/libs/browser.py +++ b/metagpt/tools/libs/browser.py @@ -189,8 +189,8 @@ class Browser: async def _view(self, keep_len: int = 5000) -> str: """simulate human viewing the current page, return the visible text with links""" - # visible_text_with_links = await self.current_page.evaluate(VIEW_CONTENT_JS) - # print("The visible text and their links (if any): ", visible_text_with_links[:keep_len]) + visible_text_with_links = await self.current_page.evaluate(VIEW_CONTENT_JS) + print("The visible text and their links (if any): ", visible_text_with_links[:keep_len]) # html_content = await self._view_page_html(keep_len=keep_len) # print("The html content: ", html_content) diff --git a/metagpt/tools/libs/editor.py b/metagpt/tools/libs/editor.py index 23df02edd..eba1a1eac 100644 --- a/metagpt/tools/libs/editor.py +++ b/metagpt/tools/libs/editor.py @@ -1,4 +1,5 @@ import os +import re import shutil import subprocess @@ -26,6 +27,9 @@ class Editor: def write(self, path: str, content: str): """Write the whole content to a file. When used, make sure content arg contains the full content of the file.""" + if len(re.findall(r"\\n", content)) >= 5: + # A very raw rule to correct the content: Many \\n suggests all new line characters are mistaken as \\n whereas the correct one should be \n + content = content.replace("\\n", "\n") directory = os.path.dirname(path) if directory and not os.path.exists(directory): os.makedirs(directory) diff --git a/metagpt/tools/libs/git.py b/metagpt/tools/libs/git.py index b4d759bf4..740cb81f9 100644 --- a/metagpt/tools/libs/git.py +++ b/metagpt/tools/libs/git.py @@ -141,6 +141,8 @@ async def git_create_pull( Returns: PullRequest: The created pull request. """ + from metagpt.utils.git_repository import GitRepository + return await GitRepository.create_pull( base=base, head=head, @@ -187,4 +189,6 @@ async def git_create_issue( Returns: Issue: The created issue. """ + from metagpt.utils.git_repository import GitRepository + return await GitRepository.create_issue(repo_name=repo_name, title=title, body=body, access_token=access_token) diff --git a/metagpt/tools/libs/terminal.py b/metagpt/tools/libs/terminal.py index faf2893a7..e6d059636 100644 --- a/metagpt/tools/libs/terminal.py +++ b/metagpt/tools/libs/terminal.py @@ -26,7 +26,7 @@ class Terminal: stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - executable="/bin/bash" + executable="/bin/bash", ) self.stdout_queue = Queue() self.observer = TerminalReporter() diff --git a/metagpt/utils/common.py b/metagpt/utils/common.py index b58ac99da..dfd3a8919 100644 --- a/metagpt/utils/common.py +++ b/metagpt/utils/common.py @@ -26,7 +26,7 @@ import sys import traceback from io import BytesIO from pathlib import Path -from typing import Any, Callable, List, Literal, Optional, Tuple, Union +from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union from urllib.parse import quote, unquote import aiofiles @@ -1013,3 +1013,34 @@ async def save_json_to_markdown(content: str, output_filename: str | Path): logger.warning(f"An unexpected error occurred: {e}") return await awrite(filename=output_filename, data=json_to_markdown(m)) + + +def tool2name(cls, methods: List[str], entry) -> Dict[str, Any]: + """ + Generates a mapping of class methods to a given entry with class name as a prefix. + + Args: + cls: The class from which the methods are derived. + methods (List[str]): A list of method names as strings. + entry (Any): The entry to be mapped to each method. + + Returns: + Dict[str, Any]: A dictionary where keys are method names prefixed with the class name and + values are the given entry. If the number of methods is less than 2, + the dictionary will contain a single entry with the class name as the key. + + Example: + >>> class MyClass: + >>> pass + >>> + >>> tool2name(MyClass, ['method1', 'method2'], 'some_entry') + {'MyClass.method1': 'some_entry', 'MyClass.method2': 'some_entry'} + + >>> tool2name(MyClass, ['method1'], 'some_entry') + {'MyClass': 'some_entry', 'MyClass.method1': 'some_entry'} + """ + class_name = cls.__name__ + mappings = {f"{class_name}.{i}": entry for i in methods} + if len(mappings) < 2: + mappings[class_name] = entry + return mappings diff --git a/tests/data/requirements/1.constraint.md b/tests/data/requirements/1.constraint.md index e09fef4b7..af7be1c84 100644 --- a/tests/data/requirements/1.constraint.md +++ b/tests/data/requirements/1.constraint.md @@ -1,2 +1,3 @@ - 基于dingtalk框架编码; -- 用java编程语言; \ No newline at end of file +- 用java编程语言; +- 接口类的功能要放到implement子类中实现; \ No newline at end of file diff --git a/tests/data/requirements/1.json b/tests/data/requirements/1.json index c995ea28f..8e608bbca 100644 --- a/tests/data/requirements/1.json +++ b/tests/data/requirements/1.json @@ -1,5 +1,5 @@ [ - "【按国家名维度搜索】\n法务查询者在国际小超人钉钉小程序搜索框中进行检索时采用 typeahead,只能下拉选择数据库中有的国家名。", - "法务查询者从国际小超人钉钉小程序UI侧的国家名称列表中选中国家名,进入国家详情界面。\n法务查询者从国家详情中的业务线名列表中选出业务线名。", + "【按国家名维度搜索】\n法务查询者在国际小超人钉钉小程序搜索框中进行检索时采用 typeahead,只能下拉选择法务中台中有的国家名。", + "法务查询者从国际小超人钉钉小程序UI侧的国家名称列表中选中国家名,进入国家详情界面。\n在国家详情界面里,法务查询者从国家详情中的业务线名列表中选出业务线名。", "国际小超人钉钉小程序用国家代码和业务代码做参数,查询法律意见详情,然后将结果展示给法务查询者。" ] \ No newline at end of file