diff --git a/metagpt/actions/anaylze_requirements.py b/metagpt/actions/anaylze_requirements.py new file mode 100644 index 000000000..486dc02fa --- /dev/null +++ b/metagpt/actions/anaylze_requirements.py @@ -0,0 +1,68 @@ +import json + +from metagpt.actions import Action +from metagpt.utils.common import CodeParser + +ANALYZE_REQUIREMENTS = """ +# Example 1 +Requirements : Create 2048 game. Do not write PRD. +Outputs:{{ + "response_language": "English" + "requirements_constraints": "Do not write PRD." +}} +# Example 2 +Requirements : 创建一个贪吃蛇,只需要给出设计文档和代码 +Outputs:{{ + "response_language": "Chinese" + "requirements_constraints": "只需要给出设计文档和代码" +}} + +# Example 3 +Requirements :You must ignore create PRD and TRD. Snake Game RequirementsGame InterfaceThe game interface is a rectangular grid.The size of the grid can be adjusted as needed (e.g., 20x20).The snake and the food are displayed on the grid.Game ObjectsSnake: Composed of multiple cells, with an initial length of 3 cells.Food: Appears randomly in one cell on the grid.GameplayThe player controls the snake's movement direction using the arrow keys (up, down, left, right).Each key press changes the snake's movement direction.With each move, the snake's head enters the next cell, and the tail leaves the last cell.When the snake's head meets the food, the snake lengthens by one cell, and new food appears at a random position on the grid.The game ends if the snake's head hits the wall or itself.User InterfaceDisplay the current score (number of food eaten).Display the game status (ongoing or ended).Provide an option to restart the game.Technical RequirementsImplement using HTML, CSS, and JavaScript.Use the Canvas element to draw the game interface.Code should be well-structured and easy to maintain and extend. +Outputs:{{ + "response_language": "English" + "requirements_constraints": "You must ignore create PRD and TRD." +}} + +# Requirements +{requirements} + +# Instructions +You must output in the same language as the Requirements. +First, This language should be consistent with the language used in the requirement description. determine the natural language you must respond in. The default language is English +Second, extract the restrictions in the requirements, specifically the steps. Do not include detailed demand descriptions; focus only on the restrictions. + +Note: +1. if there is not restrictions, requirements_restrictions must be "" + +# Output Format +{{ + "response_language": "the language to respond in" + "requirements_restrictions": "the restrictions in the requirements" +}} +""" + + +class AnalyzeRequirementsRestrictions(Action): + """Write a review for the given context.""" + + name: str = "AnalyzeRequirementsRestrictions" + + async def run(self, requirements): + """Analyze the constraints and the language used in the requirements.""" + node = await self.llm.aask(ANALYZE_REQUIREMENTS.format(requirements=requirements)) + node = CodeParser.parse_code(block=None, lang="json", text=node) + try: + node = json.loads(node) + except: + node = {} + # constraints = node.instruct_content.model_dump().get("CONSTRAINTS",None) + # output_language = node.instruct_content.model_dump().get("OUTPUT_LANGUAGE","English") + output_language = node.get("response_language", "English") + constraints = node.get("requirements_restrictions", "") + format_instruction = "" + format_instruction += f"1.[User Restrictions] : {constraints}\n" + format_instruction += ( + f"2.[Language Restrictions] : The response must be in the language specified by {output_language}." + ) + return format_instruction diff --git a/metagpt/prompts/di/role_zero.py b/metagpt/prompts/di/role_zero.py index 375e7f256..210652f8e 100644 --- a/metagpt/prompts/di/role_zero.py +++ b/metagpt/prompts/di/role_zero.py @@ -10,7 +10,7 @@ Note: 4. Don't forget to append task first when all existing tasks are finished and new tasks are required. 5. Avoid repeating tasks you have already completed. And end loop when all requirements are met. """ -# To ensure compatibility with hard-coded experience, do not add any other content between "# Example" and "# User Requirements". +# To ensure compatibility with hard-coded experience, do not add any other content between "# Example" and "# Instruction". CMD_PROMPT = """ # Latest Observation {latest_observation} @@ -39,8 +39,6 @@ Special Command: Use {{"command_name": "end"}} to do nothing or indicate complet # Example {example} -# User Requirements -{user_requirements} # Instruction {instruction} @@ -51,12 +49,15 @@ If you finish current task, you will automatically take the next task in the exi Review the latest plan's outcome, focusing on achievements. If your completed task matches the current, consider it finished. In your response, include at least one command. +# Constraints +{requirements_constraints} + # Your commands in a json array, in the following output format with correct command_name and args. If there is nothing to do, use the pass or end command: Some text indicating your thoughts before JSON is required, such as what tasks have been completed, what tasks are next, how you should update the plan status, respond to inquiry, or seek for help. Then a json array of commands. You must output ONE and ONLY ONE json array. DON'T output multiple json arrays with thoughts between them. -Firstly, pay attention to User Requirements and it's constraints. Provide a complete description of the User Requirements and the current task. +Firstly, pay attention to User Requirements. Provide a complete description of the User Requirements ,pay attention to[User Restrictions] Provide a complete description of constraints, and the current task. Secondly, pay attention to the Latest Observation, describing what the latest observation is and any relevant messages. Thirdly, if you find that the current task is identical to a previously completed one, it indicates that the current task has already been accomplished. -Then, articulate your thoughts and list the commands, adhering closely to the instructions provided. +Then, articulate your thoughts and list the commands, adhering closely to the instructions provided. you thoughts and cammand must obey [User Restrictions] ```json [ {{ @@ -68,7 +69,6 @@ Then, articulate your thoughts and list the commands, adhering closely to the in ``` Notice: your output JSON data section must start with **```json [** Notice: Your answer must start with an ordinal number. -Notice: The response and arguments must be in the language specified in the User Requirements. """ JSON_REPAIR_PROMPT = """ diff --git a/metagpt/roles/architect.py b/metagpt/roles/architect.py index c14a424da..2886cbad3 100644 --- a/metagpt/roles/architect.py +++ b/metagpt/roles/architect.py @@ -33,7 +33,7 @@ class Architect(RoleZero): name: str = "Bob" profile: str = "Architect" - goal: str = "design a concise, usable, complete software system. Create a System Design Document." + goal: str = "design a concise, usable, complete software system." constraints: str = ( "make sure the architecture is simple enough and use appropriate open source " "libraries. Use same language as user requirement" diff --git a/metagpt/roles/di/role_zero.py b/metagpt/roles/di/role_zero.py index 27bbb76b9..4c4098f31 100644 --- a/metagpt/roles/di/role_zero.py +++ b/metagpt/roles/di/role_zero.py @@ -9,6 +9,7 @@ from typing import Callable, Dict, List, Literal, Tuple from pydantic import model_validator from metagpt.actions import Action, UserRequirement +from metagpt.actions.anaylze_requirements import AnalyzeRequirementsRestrictions from metagpt.actions.di.run_command import RunCommand from metagpt.exp_pool import exp_cache from metagpt.exp_pool.context_builders import RoleZeroContextBuilder @@ -134,6 +135,7 @@ class RoleZero(Role): if not self.planner.plan.goal: self.planner.plan.goal = self.get_memories()[-1].content + self.requirements_constraints = await AnalyzeRequirementsRestrictions().run(self.planner.plan.goal) ### 1. Experience ### example = self._retrieve_experience() @@ -156,7 +158,7 @@ class RoleZero(Role): current_task=current_task, instruction=instruction, latest_observation=memory[-1].content, - user_requirements=self.planner.plan, + requirements_constraints=self.requirements_constraints, ) memory = await self.parse_browser_actions(memory) req = self.llm.format_msg(memory + [UserMessage(content=prompt)]) diff --git a/tests/metagpt/exp_pool/test_context_builders/test_rolezero_context_builder.py b/tests/metagpt/exp_pool/test_context_builders/test_rolezero_context_builder.py index 9e7e628d5..b7182602d 100644 --- a/tests/metagpt/exp_pool/test_context_builders/test_rolezero_context_builder.py +++ b/tests/metagpt/exp_pool/test_context_builders/test_rolezero_context_builder.py @@ -34,7 +34,7 @@ class TestRoleZeroContextBuilder: result = context_builder.replace_example_content("Original text", "New example content") assert result == "Replaced content" context_builder.replace_content_between_markers.assert_called_once_with( - "Original text", "# Example", "# User Requirements", "New example content" + "Original text", "# Example", "# Instruction", "New example content" ) def test_replace_content_between_markers(self):