From f45b7176b511a13c22bc60027de329a0438c7b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E8=8C=82=E5=AE=87?= Date: Wed, 12 Jul 2023 11:49:31 +0800 Subject: [PATCH] =?UTF-8?q?roadmap=202.2=20+=20add=20=E5=8D=95=E6=B5=8B?= =?UTF-8?q?=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- metagpt/actions/action_output.py | 41 ++++ tests/metagpt/actions/test_action_output.py | 49 +++++ tests/metagpt/utils/test_code_parser.py | 139 ++++++++++++ tests/metagpt/utils/test_output_parser.py | 223 ++++++++++++++++++++ 4 files changed, 452 insertions(+) create mode 100644 metagpt/actions/action_output.py create mode 100644 tests/metagpt/actions/test_action_output.py create mode 100644 tests/metagpt/utils/test_code_parser.py create mode 100644 tests/metagpt/utils/test_output_parser.py diff --git a/metagpt/actions/action_output.py b/metagpt/actions/action_output.py new file mode 100644 index 000000000..04f357f7f --- /dev/null +++ b/metagpt/actions/action_output.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# coding: utf-8 +""" +@Time : 2023/7/11 10:03 +@Author : chengmaoyu +@File : action_output +""" + +from pydantic import create_model, validator, root_validator, BaseModel +from typing import Dict, Type + + +class ActionOutput: + content: str + instruct_content: BaseModel + + def __init__(self, content: str, instruct_content: BaseModel): + self.content = content + self.instruct_content = instruct_content + + @classmethod + def create_model_class(cls, class_name: str, mapping: Dict[str, Type]): + new_class = create_model(class_name, **mapping) + + @validator('*', allow_reuse=True) + def check_name(v, field): + if field.name not in mapping.keys(): + raise ValueError(f'Unrecognized block: {field.name}') + return v + + @root_validator(pre=True, allow_reuse=True) + def check_missing_fields(values): + required_fields = set(mapping.keys()) + missing_fields = required_fields - set(values.keys()) + if missing_fields: + raise ValueError(f'Missing fields: {missing_fields}') + return values + + new_class.__validator_check_name = classmethod(check_name) + new_class.__root_validator_check_missing_fields = classmethod(check_missing_fields) + return new_class diff --git a/tests/metagpt/actions/test_action_output.py b/tests/metagpt/actions/test_action_output.py new file mode 100644 index 000000000..c6df0b0f8 --- /dev/null +++ b/tests/metagpt/actions/test_action_output.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# coding: utf-8 +""" +@Time : 2023/7/11 10:49 +@Author : chengmaoyu +@File : test_action_output +""" +from metagpt.actions import ActionOutput +from typing import List, Tuple + +t_dict = {"Required Python third-party packages": "\"\"\"\nflask==1.1.2\npygame==2.0.1\n\"\"\"\n", + "Required Other language third-party packages": "\"\"\"\nNo third-party packages required for other languages.\n\"\"\"\n", + "Full API spec": "\"\"\"\nopenapi: 3.0.0\ninfo:\n title: Web Snake Game API\n version: 1.0.0\npaths:\n /game:\n get:\n summary: Get the current game state\n responses:\n '200':\n description: A JSON object of the game state\n post:\n summary: Send a command to the game\n requestBody:\n required: true\n content:\n application/json:\n schema:\n type: object\n properties:\n command:\n type: string\n responses:\n '200':\n description: A JSON object of the updated game state\n\"\"\"\n", + "Logic Analysis": [ + ["app.py", "Main entry point for the Flask application. Handles HTTP requests and responses."], + ["game.py", "Contains the Game and Snake classes. Handles the game logic."], + ["static/js/script.js", "Handles user interactions and updates the game UI."], + ["static/css/styles.css", "Defines the styles for the game UI."], + ["templates/index.html", "The main page of the web application. Displays the game UI."]], + "Task list": ["game.py", "app.py", "static/css/styles.css", "static/js/script.js", "templates/index.html"], + "Shared Knowledge": "\"\"\"\n'game.py' contains the Game and Snake classes which are responsible for the game logic. The Game class uses an instance of the Snake class.\n\n'app.py' is the main entry point for the Flask application. It creates an instance of the Game class and handles HTTP requests and responses.\n\n'static/js/script.js' is responsible for handling user interactions and updating the game UI based on the game state returned by 'app.py'.\n\n'static/css/styles.css' defines the styles for the game UI.\n\n'templates/index.html' is the main page of the web application. It displays the game UI and loads 'static/js/script.js' and 'static/css/styles.css'.\n\"\"\"\n", + "Anything UNCLEAR": "We need clarification on how the high score should be stored. Should it persist across sessions (stored in a database or a file) or should it reset every time the game is restarted? Also, should the game speed increase as the snake grows, or should it remain constant throughout the game?"} + +WRITE_TASKS_OUTPUT_MAPPING = { + "Required Python third-party packages": (str, ...), + "Required Other language third-party packages": (str, ...), + "Full API spec": (str, ...), + "Logic Analysis": (List[Tuple[str, str]], ...), + "Task list": (List[str], ...), + "Shared Knowledge": (str, ...), + "Anything UNCLEAR": (str, ...), +} + + +def test_create_model_class(): + test_class = ActionOutput.create_model_class("test_class", WRITE_TASKS_OUTPUT_MAPPING) + assert test_class.__name__ == "test_class" + + +def test_create_model_class_with_mapping(): + t = ActionOutput.create_model_class("test_class_1", WRITE_TASKS_OUTPUT_MAPPING) + t1 = t(**t_dict) + value = t1.dict()["Task list"] + assert value == ["game.py", "app.py", "static/css/styles.css", "static/js/script.js", "templates/index.html"] + + +if __name__ == '__main__': + test_create_model_class() + test_create_model_class_with_mapping() diff --git a/tests/metagpt/utils/test_code_parser.py b/tests/metagpt/utils/test_code_parser.py new file mode 100644 index 000000000..cf06cce1d --- /dev/null +++ b/tests/metagpt/utils/test_code_parser.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python +# coding: utf-8 +""" +@Time : 2023/7/10 17:14 +@Author : chengmaoyu +@File : test_code_parser.py +""" + +import pytest +from metagpt.utils.common import CodeParser + +t_text = ''' +## Required Python third-party packages +```python +""" +flask==1.1.2 +pygame==2.0.1 +""" +``` + +## Required Other language third-party packages +```python +""" +No third-party packages required for other languages. +""" +``` + +## Full API spec +```python +""" +openapi: 3.0.0 +info: + title: Web Snake Game API + version: 1.0.0 +paths: + /game: + get: + summary: Get the current game state + responses: + '200': + description: A JSON object of the game state + post: + summary: Send a command to the game + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + command: + type: string + responses: + '200': + description: A JSON object of the updated game state +""" +``` + +## Logic Analysis +```python +[ + ("app.py", "Main entry point for the Flask application. Handles HTTP requests and responses."), + ("game.py", "Contains the Game and Snake classes. Handles the game logic."), + ("static/js/script.js", "Handles user interactions and updates the game UI."), + ("static/css/styles.css", "Defines the styles for the game UI."), + ("templates/index.html", "The main page of the web application. Displays the game UI.") +] +``` + +## Task list +```python +[ + "game.py", + "app.py", + "static/css/styles.css", + "static/js/script.js", + "templates/index.html" +] +``` + +## Shared Knowledge +```python +""" +'game.py' contains the Game and Snake classes which are responsible for the game logic. The Game class uses an instance of the Snake class. + +'app.py' is the main entry point for the Flask application. It creates an instance of the Game class and handles HTTP requests and responses. + +'static/js/script.js' is responsible for handling user interactions and updating the game UI based on the game state returned by 'app.py'. + +'static/css/styles.css' defines the styles for the game UI. + +'templates/index.html' is the main page of the web application. It displays the game UI and loads 'static/js/script.js' and 'static/css/styles.css'. +""" +``` + +## Anything UNCLEAR +We need clarification on how the high score should be stored. Should it persist across sessions (stored in a database or a file) or should it reset every time the game is restarted? Also, should the game speed increase as the snake grows, or should it remain constant throughout the game? + ''' + + +class TestCodeParser: + @pytest.fixture + def parser(self): + return CodeParser() + + @pytest.fixture + def text(self): + return t_text + + def test_parse_blocks(self, parser, text): + result = parser.parse_blocks(text) + print(result) + assert result == {"title": "content", "title2": "content2"} + + def test_parse_block(self, parser, text): + result = parser.parse_block("title", text) + print(result) + assert result == "content" + + def test_parse_code(self, parser, text): + result = parser.parse_code("title", text, "python") + print(result) + assert result == "print('hello world')" + + def test_parse_str(self, parser, text): + result = parser.parse_str("title", text, "python") + print(result) + assert result == "hello world" + + def test_parse_file_list(self, parser, text): + result = parser.parse_file_list("Task list", text) + print(result) + assert result == ['task1', 'task2'] + + +if __name__ == '__main__': + t = TestCodeParser() + t.test_parse_file_list(CodeParser(), t_text) + # TestCodeParser.test_parse_file_list() diff --git a/tests/metagpt/utils/test_output_parser.py b/tests/metagpt/utils/test_output_parser.py new file mode 100644 index 000000000..4a365648d --- /dev/null +++ b/tests/metagpt/utils/test_output_parser.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python +# coding: utf-8 +""" +@Time : 2023/7/11 10:25 +@Author : chengmaoyu +@File : test_output_parser.py +""" + +import pytest +from typing import List, Tuple +import re +import ast +from metagpt.utils.common import OutputParser + + +def test_parse_blocks(): + test_text = "##block1\nThis is block 1.\n##block2\nThis is block 2." + expected_result = {'block1': 'This is block 1.', 'block2': 'This is block 2.'} + assert OutputParser.parse_blocks(test_text) == expected_result + + +def test_parse_code(): + test_text = "```python\nprint('Hello, world!')\n```" + expected_result = "print('Hello, world!')" + assert OutputParser.parse_code(test_text, 'python') == expected_result + + with pytest.raises(Exception): + OutputParser.parse_code(test_text, 'java') + + +def test_parse_str(): + test_text = "name = 'Alice'" + expected_result = 'Alice' + assert OutputParser.parse_str(test_text) == expected_result + + +def test_parse_file_list(): + test_text = "files=['file1', 'file2', 'file3']" + expected_result = ['file1', 'file2', 'file3'] + assert OutputParser.parse_file_list(test_text) == expected_result + + with pytest.raises(Exception): + OutputParser.parse_file_list("wrong_input") + + +def test_parse_data(): + test_data = "##block1\n```python\nprint('Hello, world!')\n```\n##block2\nfiles=['file1', 'file2', 'file3']" + expected_result = {'block1': "print('Hello, world!')", 'block2': ['file1', 'file2', 'file3']} + assert OutputParser.parse_data(test_data) == expected_result + + +if __name__ == '__main__': + t_text = ''' +## Required Python third-party packages +```python +""" +flask==1.1.2 +pygame==2.0.1 +""" +``` + +## Required Other language third-party packages +```python +""" +No third-party packages required for other languages. +""" +``` + +## Full API spec +```python +""" +openapi: 3.0.0 +info: + title: Web Snake Game API + version: 1.0.0 +paths: + /game: + get: + summary: Get the current game state + responses: + '200': + description: A JSON object of the game state + post: + summary: Send a command to the game + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + command: + type: string + responses: + '200': + description: A JSON object of the updated game state +""" +``` + +## Logic Analysis +```python +[ + ("app.py", "Main entry point for the Flask application. Handles HTTP requests and responses."), + ("game.py", "Contains the Game and Snake classes. Handles the game logic."), + ("static/js/script.js", "Handles user interactions and updates the game UI."), + ("static/css/styles.css", "Defines the styles for the game UI."), + ("templates/index.html", "The main page of the web application. Displays the game UI.") +] +``` + +## Task list +```python +[ + "game.py", + "app.py", + "static/css/styles.css", + "static/js/script.js", + "templates/index.html" +] +``` + +## Shared Knowledge +```python +""" +'game.py' contains the Game and Snake classes which are responsible for the game logic. The Game class uses an instance of the Snake class. + +'app.py' is the main entry point for the Flask application. It creates an instance of the Game class and handles HTTP requests and responses. + +'static/js/script.js' is responsible for handling user interactions and updating the game UI based on the game state returned by 'app.py'. + +'static/css/styles.css' defines the styles for the game UI. + +'templates/index.html' is the main page of the web application. It displays the game UI and loads 'static/js/script.js' and 'static/css/styles.css'. +""" +``` + +## Anything UNCLEAR +We need clarification on how the high score should be stored. Should it persist across sessions (stored in a database or a file) or should it reset every time the game is restarted? Also, should the game speed increase as the snake grows, or should it remain constant throughout the game? + ''' + + OUTPUT_MAPPING = { + "Original Requirements": (str, ...), + "Product Goals": (List[str], ...), + "User Stories": (List[str], ...), + "Competitive Analysis": (List[str], ...), + "Competitive Quadrant Chart": (str, ...), + "Requirement Analysis": (str, ...), + "Requirement Pool": (List[Tuple[str, str]], ...), + "Anything UNCLEAR": (str, ...), + } + t_text1 = '''## Original Requirements: + +The boss wants to create a web-based version of the game "Fly Bird". + +## Product Goals: + +- Create a web-based version of the game "Fly Bird" that is engaging and addictive. +- Provide a seamless and intuitive user experience. +- Optimize the game for different devices and screen sizes. + +## User Stories: + +- As a user, I want to be able to control the bird's flight by clicking or tapping on the screen. +- As a user, I want to see my score and the highest score achieved in the game. +- As a user, I want the game to be challenging but not frustratingly difficult. +- As a user, I want to be able to pause and resume the game at any time. +- As a user, I want to be able to share my score on social media. + +## Competitive Analysis: + +- Flappy Bird: A popular mobile game where the player controls a bird's flight through a series of obstacles. +- Angry Birds: A physics-based puzzle game where the player launches birds to destroy structures and defeat pigs. +- Snake Game: A classic game where the player controls a snake to eat food and grow longer without hitting the walls or its own body. +- Temple Run: An endless running game where the player controls a character to avoid obstacles and collect coins. +- Subway Surfers: An endless running game where the player controls a character to avoid obstacles and collect coins while being chased by a guard. +- Doodle Jump: A vertical platform game where the player controls a character to jump on platforms and avoid falling. +- Fruit Ninja: A fruit-slicing game where the player uses their finger to slice flying fruits. + +## Competitive Quadrant Chart: + +```mermaid +quadrantChart + title Reach and engagement of games + x-axis Low Reach --> High Reach + y-axis Low Engagement --> High Engagement + quadrant-1 We should expand + quadrant-2 Need to promote + quadrant-3 Re-evaluate + quadrant-4 May be improved + "Flappy Bird": [0.8, 0.9] + "Angry Birds": [0.9, 0.8] + "Snake Game": [0.6, 0.6] + "Temple Run": [0.9, 0.7] + "Subway Surfers": [0.9, 0.7] + "Doodle Jump": [0.7, 0.5] + "Fruit Ninja": [0.8, 0.6] + "Our Target Product": [0.7, 0.8] +``` + +## Requirement Analysis: + +The product should be a web-based version of the game "Fly Bird" that is engaging, addictive, and optimized for different devices and screen sizes. It should provide a seamless and intuitive user experience, with controls that allow the user to control the bird's flight by clicking or tapping on the screen. The game should display the user's score and the highest score achieved. It should be challenging but not frustratingly difficult, allowing the user to pause and resume the game at any time. The user should also have the option to share their score on social media. + +## Requirement Pool: + +```python +[ + ("Implement bird's flight control using click or tap", "P0"), + ("Display user's score and highest score achieved", "P0"), + ("Implement challenging but not frustrating difficulty level", "P1"), + ("Allow user to pause and resume the game", "P1"), + ("Implement social media sharing feature", "P2") +] +``` + +## Anything UNCLEAR: + +There are no unclear points. + ''' + d = OutputParser.parse_data_with_mapping(t_text1, OUTPUT_MAPPING) + import json + + print(json.dumps(d))