Added some test cases

This commit is contained in:
mannaandpoem 2024-01-03 14:19:14 +08:00
parent a1e75d1173
commit b19995f083
10 changed files with 757 additions and 49 deletions

View file

@ -120,7 +120,7 @@ INC_DESIGN_CONTEXT = """
{prd_increment}
"""
REFINE_DESIGN_CONTEXT = """
MERGE_DESIGN_CONTEXT = """
Role: You are a professional Architect tasked with overseeing incremental development.
Based on new requirements, review and refine the system design. Integrate existing architecture with incremental design changes, ensuring the refined design encompasses all architectural elements, enhancements, and adjustments. Retain content unrelated to incremental development needs for coherence and clarity.
@ -148,7 +148,6 @@ INC_NODES = [INCREMENTAL_IMPLEMENTATION_APPROACH, INCREMENTAL_DATA_STRUCTURES_AN
REFINE_NODES = [
REFINED_IMPLEMENTATION_APPROACH,
# PROJECT_NAME,
FILE_LIST,
REFINED_DATA_STRUCTURES_AND_INTERFACES,
REFINED_PROGRAM_CALL_FLOW,
@ -161,7 +160,9 @@ REFINED_DESIGN_NODES = ActionNode.from_children("Refined_Design_API", REFINE_NOD
def main():
prompt = REFINED_DESIGN_NODES.compile(context="...")
prompt = DESIGN_API_NODE.compile(context="")
logger.info(prompt)
prompt = REFINED_DESIGN_NODES.compile(context="")
logger.info(prompt)

View file

@ -138,7 +138,7 @@ INC_PM_CONTEXT = """
{design_increment}
"""
REFINE_PM_CONTEXT = """
MERGE_PM_CONTEXT = """
Role: You are a professional Project Manager tasked with overseeing incremental development.
Based on New Requirements, refine the project context to account for incremental development. Ensure the context offers a comprehensive overview of the project's evolving scope, covering both legacy content and incremental content. Retain any content unrelated to incremental development.
@ -183,6 +183,8 @@ REFINED_PM_NODES = ActionNode.from_children("Refined_PM_NODES", REFINE_NODES)
def main():
prompt = PM_NODE.compile(context="")
logger.info(prompt)
prompt = REFINED_PM_NODES.compile(context="")
logger.info(prompt)
if __name__ == "__main__":

View file

@ -21,7 +21,7 @@ from pydantic import Field
from tenacity import retry, stop_after_attempt, wait_random_exponential
from metagpt.actions.action import Action
from metagpt.actions.write_code_guideline_an import REFINED_TEMPLATE
from metagpt.actions.write_code_guideline_an import REFINED_CODE_TEMPLATE
from metagpt.config import CONFIG
from metagpt.const import (
BUGFIX_FILENAME,
@ -127,7 +127,7 @@ class WriteCode(Action):
code_context = await self.get_codes(coding_context.task_doc, exclude=self.context.filename)
if guideline:
prompt = REFINED_TEMPLATE.format(
prompt = REFINED_CODE_TEMPLATE.format(
requirement=requirement_doc.content if requirement_doc else "",
guideline=guideline,
design=coding_context.design_doc.content if coding_context.design_doc else "",

View file

@ -103,7 +103,291 @@ Role: You are a professional software engineer, and your main task is to craft c
{code}
"""
REFINED_TEMPLATE = """
CODE_GUIDELINE_CONTEXT_EXAMPLE = """
NOTICE
Role: You are a professional software engineer, and your main task is to craft comprehensive incremental development plans and provide detailed code guidance with triple quote, based on the following attentions and context. Output format carefully referenced "Format example".
1. Determine the scope of responsibilities of each file and what classes and methods need to be implemented.
2. Import all referenced classes.
3. Implement all methods.
4. Add necessary explanation to all methods.
5. Ensure there are no potential bugs.
6. Confirm that the entire project conforms to the tasks proposed by the user.
7. Examine the code closely to find and fix errors, and confirm that the logic is sound to ensure smooth user interaction while meeting all specified requirements.
8. Attention: Code files in the task list may have a different number of files compared to legacy code files. This requires integrating legacy code files that do not appear in the task list into the code files of the task list. Therefore, when writing code guidance and incremental changes for the code files in the task list, also include how to seamlessly merge and adjust legacy code files.
# Context
## Requirement
Add subtraction, multiplication and division operations to the calculator.
The current calculator can only perform basic addition operations, and it is necessary to introduce subtraction, multiplication, division operation into the calculator
## Design
{
"Refined Implementation Approach": "To accommodate the new requirements, we will extend the existing Python-based calculator application. We will enhance the Tkinter-based UI to include buttons for subtraction, multiplication, and division, alongside the existing addition functionality. We will also implement input validation to handle edge cases such as division by zero. The architecture will be modular, with separate components for the UI, calculation logic, and error handling to maintain simplicity and facilitate future enhancements such as a history feature.",
"File list": [
"main.py",
"calculator.py",
"interface.py",
"operations.py"
],
"Refined Data Structures and Interfaces": "classDiagram\n class CalculatorApp {\n +main() None\n }\n class Calculator {\n -result float\n +add(number1: float, number2: float) float\n +subtract(number1: float, number2: float) float\n +multiply(number1: float, number2: float) float\n +divide(number1: float, number2: float) float\n +clear() None\n }\n class Interface {\n -calculator Calculator\n +start() None\n +display_result(result: float) None\n +get_input() float\n +show_error(message: str) None\n +update_operation(operation: str) None\n }\n class Operations {\n +perform_operation(operation: str, number1: float, number2: float) float\n }\n CalculatorApp --> Interface\n Interface --> Calculator\n Calculator --> Operations",
"Refined Program call flow": "sequenceDiagram\n participant CA as CalculatorApp\n participant I as Interface\n participant C as Calculator\n participant O as Operations\n CA->>I: start()\n I->>I: get_input()\n I->>I: update_operation(operation)\n loop For Each Operation\n I->>C: perform_operation(operation, number1, number2)\n C->>O: perform_operation(operation, number1, number2)\n O-->>C: return result\n C-->>I: return result\n I->>I: display_result(result)\n end\n I->>I: show_error(message)",
"Anything UNCLEAR": "The requirement for a history feature is mentioned but not prioritized. It is unclear whether this should be implemented now or in the future. Additionally, there is no specification on the limit to the size of the numbers or the number of operations that can be performed in sequence. These aspects will need clarification for complete implementation."
}
## Tasks
{
"Required Python packages": [
"tkinter"
],
"Required Other language third-party packages": [
"No third-party dependencies required"
],
"Refined Logic Analysis": [
[
"main.py",
"Entry point of the application, creates an instance of the Interface class and starts the application."
],
[
"calculator.py",
"Contains the Calculator class with add, subtract, multiply, divide and clear methods for performing arithmetic operations."
],
[
"interface.py",
"Contains the Interface class responsible for the GUI, interacts with Calculator for the logic and displays results or errors."
],
[
"operations.py",
"Contains the Operations class with perform_operation method that delegates the arithmetic operation based on the operation argument."
]
],
"Refined Task list": [
"operations.py",
"calculator.py",
"interface.py",
"main.py"
],
"Full API spec": "",
"Refined Shared Knowledge": "`interface.py` will use the Calculator class from `calculator.py` to perform operations and display results. `main.py` will be the starting point that initializes the Interface. `calculator.py` will now also interact with `operations.py` to perform the arithmetic operations.",
"Anything UNCLEAR": "The requirement for a history feature is mentioned but not prioritized. It is unclear whether this should be implemented now or in the future. Additionally, there is no specification on the limit to the size of the numbers or the number of operations that can be performed in sequence. These aspects will need clarification for complete implementation."
}
## Legacy Code
{code}
"""
CODE_GUIDELINE_SCRIPT_EXAMPLE = """
----- calculator.py
```## calculator.py
class Calculator:
def __init__(self):
self.result = 0.0 # Default value for the result
def add(self, number1: float, number2: float) -> float:
'''
Adds two numbers and returns the result.
Args:
number1 (float): The first number to add.
number2 (float): The second number to add.
Returns:
float: The sum of number1 and number2.
'''
self.result = number1 + number2
return self.result
def clear(self) -> None:
'''
Clears the result to its default value.
'''
self.result = 0.0
```
---- interface.py
```## interface.py
import tkinter as tk
from calculator import Calculator
class Interface:
def __init__(self):
self.calculator = Calculator()
self.root = tk.Tk()
self.root.title("Calculator")
self.create_widgets()
def create_widgets(self):
self.result_var = tk.StringVar()
self.result_display = tk.Entry(self.root, textvariable=self.result_var, state='readonly', justify='right', font=('Arial', 24))
self.result_display.grid(row=0, column=0, columnspan=4, sticky='nsew')
self.entry_number1 = tk.Entry(self.root, justify='right', font=('Arial', 18))
self.entry_number1.grid(row=1, column=0, columnspan=2, sticky='nsew')
self.entry_number2 = tk.Entry(self.root, justify='right', font=('Arial', 18))
self.entry_number2.grid(row=1, column=2, columnspan=2, sticky='nsew')
self.add_button = tk.Button(self.root, text='+', command=self.add, font=('Arial', 18))
self.add_button.grid(row=2, column=0, sticky='nsew')
self.clear_button = tk.Button(self.root, text='C', command=self.clear, font=('Arial', 18))
self.clear_button.grid(row=2, column=1, sticky='nsew')
self.quit_button = tk.Button(self.root, text='Quit', command=self.root.quit, font=('Arial', 18))
self.quit_button.grid(row=2, column=2, columnspan=2, sticky='nsew')
self.root.grid_rowconfigure(1, weight=1)
self.root.grid_columnconfigure(0, weight=1)
def start(self):
self.root.mainloop()
def display_result(self, result: float):
self.result_var.set(str(result))
def get_input(self):
try:
number1 = float(self.entry_number1.get())
number2 = float(self.entry_number2.get())
return number1, number2
except ValueError:
self.show_error("Invalid input! Please enter valid numbers.")
return None, None
def add(self):
number1, number2 = self.get_input()
if number1 is not None and number2 is not None:
result = self.calculator.add(number1, number2)
self.display_result(result)
def clear(self):
self.entry_number1.delete(0, tk.END)
self.entry_number2.delete(0, tk.END)
self.result_var.set("")
def show_error(self, message: str):
tk.messagebox.showerror("Error", message)
# This code is meant to be used as a module and not as a standalone script.
# The Interface class will be instantiated and started by the main.py file.
```
---- main.py
```## main.py
from interface import Interface
class CalculatorApp:
@staticmethod
def main():
interface = Interface()
interface.start()
if __name__ == "__main__":
CalculatorApp.main()
```
"""
REFINE_CODE_SCRIPT_EXAMPLE = """
----- calculator.py
```## calculator.py
class Calculator:
def __init__(self):
self.result = 0.0 # Default value for the result
def add(self, number1: float, number2: float) -> float:
'''
Adds two numbers and returns the result.
Args:
number1 (float): The first number to add.
number2 (float): The second number to add.
Returns:
float: The sum of number1 and number2.
'''
self.result = number1 + number2
return self.result
def clear(self) -> None:
'''
Clears the result to its default value.
'''
self.result = 0.0
```
---- Now, interface.py to be rewritten
```## interface.py
import tkinter as tk
from calculator import Calculator
class Interface:
def __init__(self):
self.calculator = Calculator()
self.root = tk.Tk()
self.root.title("Calculator")
self.create_widgets()
def create_widgets(self):
self.result_var = tk.StringVar()
self.result_display = tk.Entry(self.root, textvariable=self.result_var, state='readonly', justify='right', font=('Arial', 24))
self.result_display.grid(row=0, column=0, columnspan=4, sticky='nsew')
self.entry_number1 = tk.Entry(self.root, justify='right', font=('Arial', 18))
self.entry_number1.grid(row=1, column=0, columnspan=2, sticky='nsew')
self.entry_number2 = tk.Entry(self.root, justify='right', font=('Arial', 18))
self.entry_number2.grid(row=1, column=2, columnspan=2, sticky='nsew')
self.add_button = tk.Button(self.root, text='+', command=self.add, font=('Arial', 18))
self.add_button.grid(row=2, column=0, sticky='nsew')
self.clear_button = tk.Button(self.root, text='C', command=self.clear, font=('Arial', 18))
self.clear_button.grid(row=2, column=1, sticky='nsew')
self.quit_button = tk.Button(self.root, text='Quit', command=self.root.quit, font=('Arial', 18))
self.quit_button.grid(row=2, column=2, columnspan=2, sticky='nsew')
self.root.grid_rowconfigure(1, weight=1)
self.root.grid_columnconfigure(0, weight=1)
def start(self):
self.root.mainloop()
def display_result(self, result: float):
self.result_var.set(str(result))
def get_input(self):
try:
number1 = float(self.entry_number1.get())
number2 = float(self.entry_number2.get())
return number1, number2
except ValueError:
self.show_error("Invalid input! Please enter valid numbers.")
return None, None
def add(self):
number1, number2 = self.get_input()
if number1 is not None and number2 is not None:
result = self.calculator.add(number1, number2)
self.display_result(result)
def clear(self):
self.entry_number1.delete(0, tk.END)
self.entry_number2.delete(0, tk.END)
self.result_var.set("")
def show_error(self, message: str):
tk.messagebox.showerror("Error", message)
# This code is meant to be used as a module and not as a standalone script.
# The Interface class will be instantiated and started by the main.py file.
```
"""
REFINED_CODE_TEMPLATE = """
NOTICE
Role: You are a professional engineer; The main goal is to complete incremental development by combining legacy code and Incremental Change, ensuring the integration of new features.
@ -157,38 +441,6 @@ Role: You are a professional engineer; The main goal is to complete incremental
9. Attention: If Legacy Code files contain "{filename} to be rewritten", you are required to merge the Incremental Change into the {filename} file when rewriting "{filename} to be rewritten".
"""
CODE_GUIDE_CONTEXT_EXAMPLE = """
# Legacy Code
## main.py
from flask import Flask, request, jsonify
from calculator import Calculator
app = Flask(__name__)
calculator = Calculator()
@app.route('/add_numbers', methods=['POST'])
def add_numbers():
data = request.get_json()
num1 = data.get('num1', 0)
num2 = data.get('num2', 0)
result = calculator.add_numbers(num1, num2)
return jsonify({'result': result}), 200
if __name__ == '__main__':
app.run()
## calculator.py
class Calculator:
def __init__(self, num1: int = 0, num2: int = 0):
self.num1 = num1
self.num2 = num2
def add_numbers(self, num1: int, num2: int) -> int:
return num1 + num2
"""
GUIDE_NODES = [INCREMENTAL_CHANGE]
WRITE_CODE_GUIDELINE_NODE = ActionNode.from_children("WriteCodeGuideline", GUIDE_NODES)
@ -199,10 +451,12 @@ class WriteCodeGuideline(Action):
return await WRITE_CODE_GUIDELINE_NODE.fill(context=context, llm=self.llm, schema="json")
def main():
action = WriteCodeGuideline()
return asyncio.run(action.run(CODE_GUIDELINE_CONTEXT))
async def main():
write_code_guideline = WriteCodeGuideline()
node = await write_code_guideline.run(CODE_GUIDELINE_CONTEXT_EXAMPLE.format(code=CODE_GUIDELINE_SCRIPT_EXAMPLE))
guideline = node.instruct_content.json(ensure_ascii=False)
print(guideline)
if __name__ == "__main__":
main()
asyncio.run(main())

View file

@ -141,6 +141,15 @@ INCREMENTAL_REQUIREMENT_ANALYSIS = ActionNode(
example=["Require add/update/modify ..."],
)
REFINED_REQUIREMENT_ANALYSIS = ActionNode(
key="Refined Requirement Analysis",
expected_type=List[str],
instruction="Review and refine the existing requirement analysis to align with the evolving needs of the project "
"due to incremental development. Ensure the analysis comprehensively covers the new features and enhancements "
"required for the refined project scope.",
example=["Require add/update/modify ..."],
)
REQUIREMENT_POOL = ActionNode(
key="Requirement Pool",
expected_type=List[List[str]],
@ -151,7 +160,7 @@ REQUIREMENT_POOL = ActionNode(
REFINED_REQUIREMENT_POOL = ActionNode(
key="Refined Requirement Pool",
expected_type=List[List[str]],
instruction="List no less than 7 requirements with their priority (P0, P1, P2). "
instruction="List no less than 5 requirements with their priority (P0, P1, P2). "
"Cover both legacy content and incremental content. Retain any content unrelated to incremental development",
example=[["P0", "The main code ..."], ["P0", "The game algorithm ..."]],
)
@ -189,7 +198,7 @@ REASON = ActionNode(
)
REFINE_PRD_CONTEXT = """
INCREMENTAL_PRD_CONTEXT = """
Role: You are a professional Product Manager tasked with overseeing incremental development.
Based on New Requirements, output a New PRD that seamlessly integrates both the Legacy Content and the Incremental Content. Ensure the resulting document captures the complete scope of features, enhancements, and retain content unrelated to incremental development needs for coherence and clarity.
@ -243,7 +252,7 @@ REFINE_NODES = [
REFINED_USER_STORIES,
COMPETITIVE_ANALYSIS,
COMPETITIVE_QUADRANT_CHART,
INCREMENTAL_REQUIREMENT_ANALYSIS,
REFINED_REQUIREMENT_ANALYSIS,
REFINED_REQUIREMENT_POOL,
UI_DESIGN_DRAFT,
ANYTHING_UNCLEAR,
@ -259,8 +268,9 @@ WP_IS_RELATIVE_NODE = ActionNode.from_children("WP_IS_RELATIVE", [IS_RELATIVE, R
def main():
# prompt = WRITE_PRD_NODE.compile(context="")
prompt = INCREMENTAL_PRD_NODE.compile(context=REFINE_PRD_CONTEXT)
prompt = WRITE_PRD_NODE.compile(context="")
logger.info(prompt)
prompt = REFINE_PRD_NODE.compile(context="")
logger.info(prompt)

View file

@ -86,7 +86,8 @@ def loguru_caplog(caplog):
# init & dispose git repo
@pytest.fixture(scope="session", autouse=True)
# @pytest.fixture(scope="session", autouse=True)
@pytest.fixture(scope="session")
def setup_and_teardown_git_repo(request):
CONFIG.git_repo = GitRepository(local_path=DEFAULT_WORKSPACE_ROOT / "unittest")

View file

@ -0,0 +1,102 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2024/01/03
@Author : mannaandpoem
@File : test_design_api_an.py.py
"""
import pytest
from metagpt.actions.design_api_an import REFINED_DESIGN_NODES
from metagpt.provider import OpenAIGPTAPI
CONTEXT = """
### Legacy Content
{
"Implementation approach": "We will use Python with the Pygame library to develop the core mechanics of the 2048 game. The game will feature a simple and intuitive user interface, score tracking with high score memory, and an undo move feature. We'll ensure the game has visually appealing graphics and animations while maintaining a minimalist design. The undo feature will allow a single move to be undone without affecting the score, to keep the implementation straightforward.",
"File list": [
"main.py",
"game.py",
"ui.py",
"constants.py"
],
"Data structures and interfaces": "classDiagram\n class Main {\n +pygame: PygameInstance\n +game: Game\n +run() void\n }\n class Game {\n -grid: list\n -current_score: int\n -high_score: int\n -last_move: list\n +move(direction: str) bool\n +undo() bool\n +check_game_over() bool\n +reset_game() void\n }\n class UI {\n -screen: PygameSurface\n -font: PygameFont\n +draw_grid(grid: list) void\n +display_score(current_score: int, high_score: int) void\n +show_game_over() void\n +show_undo_button() void\n }\n class Constants {\n +GRID_SIZE: int\n +WINDOW_WIDTH: int\n +WINDOW_HEIGHT: int\n +BACKGROUND_COLOR: tuple\n +TILE_COLORS: dict\n }\n Main --> Game\n Main --> UI\n Game --> Constants\n UI --> Constants",
"Program call flow": "sequenceDiagram\n participant M as Main\n participant G as Game\n participant U as UI\n M->>G: create instance\n M->>U: create instance\n loop game loop\n M->>U: draw_grid(G.grid)\n M->>U: display_score(G.current_score, G.high_score)\n M->>U: show_undo_button()\n M->>G: move(direction)\n alt if move is valid\n G-->>M: return true\n else if move is invalid\n G-->>M: return false\n end\n alt if undo is triggered\n M->>G: undo()\n G-->>M: return true\n else no undo\n G-->>M: return false\n end\n alt if game over\n M->>U: show_game_over()\n M->>G: reset_game()\n end\n end",
"Anything UNCLEAR": "The specifics of the scoring system and how the high score is stored and retrieved need to be clarified. Additionally, the exact graphical assets and animations for the game are not specified."
}
### New Requirements
{
"Language": "en_us",
"Programming Language": "Python",
"Refined Requirements": "Update the py2048_game to have a larger 8x8 grid and a new winning score target of 4096, while maintaining the core mechanics and user-friendly interface of the original 2048 game.",
"Project Name": "py2048_game",
"Refined Product Goals": [
"Develop an enhanced version of the 2048 game with a larger grid and higher score target to provide a new challenge to players",
"Ensure the game remains visually appealing and maintains a consistent theme with the added complexity",
"Implement smooth and responsive game controls suitable for an 8x8 grid interface"
],
"Refined User Stories": [
"As a player, I want to experience a clear and simple interface on an 8x8 grid so that I can focus on the gameplay",
"As a player, I want to aim for a higher score target of 4096 to challenge my skills further",
"As a player, I want to see my current and high scores to track my progress on the new larger grid",
"As a player, I want the option to undo my last move to improve my strategy on the 8x8 grid",
"As a player, I want the game to perform smoothly despite the increased complexity of the larger grid"
],
"Competitive Analysis": [
"2048 Original: Classic gameplay with minimalistic design, but lacks modern features",
"2048 by Gabriele Cirulli: Open-source version with clean UI, but no additional features",
"2048 Hex: Unique hexagon board, providing a different challenge",
"2048 Multiplayer: Allows playing against others, but the interface is cluttered",
"2048 with AI: Includes AI challenge mode, but the AI is often too difficult for casual players",
"2048.io: Combines 2048 gameplay with .io style, though it can be overwhelming for new players",
"2048 Animated: Features animations, but has performance issues on some devices"
],
"Competitive Quadrant Chart": "quadrantChart\n title \"2048 Game Market Positioning\"\n x-axis \"Basic Features\" --> \"Advanced Features\"\n y-axis \"Low User Engagement\" --> \"High User Engagement\"\n quadrant-1 \"Niche Innovators\"\n quadrant-2 \"Market Leaders\"\n quadrant-3 \"Emerging Contenders\"\n quadrant-4 \"Falling Behind\"\n \"2048 Original\": [0.2, 0.7]\n \"2048 by Gabriele Cirulli\": [0.3, 0.8]\n \"2048 Hex\": [0.5, 0.4]\n \"2048 Multiplayer\": [0.6, 0.6]\n \"2048 with AI\": [0.7, 0.5]\n \"2048.io\": [0.4, 0.3]\n \"2048 Animated\": [0.3, 0.2]\n \"Our Target Product\": [0.9, 0.9]",
"Incremental Requirement Analysis": [
"Adjust the game logic to accommodate an 8x8 grid while ensuring performance remains optimal",
"Update the UI to fit the larger grid and include visual cues for the new score target",
"Enhance the scoring system to support the new target of 4096",
"Ensure the undo feature is adapted to work with the larger grid and increased game complexity",
"Test the game thoroughly to maintain a smooth and responsive experience on the new 8x8 grid"
],
"Refined Requirement Pool": [
[
"P0",
"Expand the game grid to 8x8 and adjust the core mechanics accordingly"
],
[
"P0",
"Increase the score target to 4096 and update the scoring system"
],
[
"P1",
"Redesign the user interface to accommodate the larger grid size"
],
[
"P1",
"Ensure the undo move feature is compatible with the new grid and score target"
],
[
"P2",
"Optimize game performance for the increased complexity of an 8x8 grid"
],
[
"P2",
"Maintain a visually appealing and consistent theme with the updated game features"
]
],
"UI Design draft": "The UI will be updated to feature an 8x8 grid while maintaining a minimalist design. The main game screen will display the larger game grid, current score, high score, and an undo button. The color scheme and transitions will be adapted to ensure clarity and pleasant aesthetics despite the increased grid size.",
"Anything UNCLEAR": "The specifics of how the undo feature should work with the larger grid size and whether there should be any limitations on its use need to be clarified."
}
"""
llm = OpenAIGPTAPI()
@pytest.mark.asyncio
async def test_write_design_an():
node = await REFINED_DESIGN_NODES.fill(CONTEXT, llm)
assert node.instruct_content
assert "Refined Data Structures and Interfaces" in node.instruct_content.json(ensure_ascii=False)

View file

@ -0,0 +1,141 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2024/01/03
@Author : mannaandpoem
@File : test_project_management_an.py.py
"""
import pytest
from metagpt.actions.project_management_an import REFINED_PM_NODES
from metagpt.provider import OpenAIGPTAPI
CONTEXT = """
### Legacy Content
{
"Required Python packages": [
"pygame==2.0.1"
],
"Required Other language third-party packages": [
"No third-party dependencies required"
],
"Logic Analysis": [
[
"constants.py",
"Contains all the constants like GRID_SIZE, WINDOW_WIDTH, WINDOW_HEIGHT, BACKGROUND_COLOR, TILE_COLORS"
],
[
"game.py",
"Contains Game class with methods for game logic such as move, undo, check_game_over, and reset_game"
],
[
"ui.py",
"Contains UI class responsible for drawing the grid, displaying scores, showing game over, and undo button"
],
[
"main.py",
"Contains Main class which initializes the game loop and orchestrates the interactions between Game and UI classes"
]
],
"Task list": [
"constants.py",
"game.py",
"ui.py",
"main.py"
],
"Full API spec": "",
"Shared Knowledge": "`constants.py` contains constants shared across `game.py` and `ui.py`. The Main class in `main.py` acts as the controller orchestrating the game flow and UI updates.",
"Anything UNCLEAR": "The specifics of the scoring system and how the high score is stored and retrieved need to be clarified. Additionally, the exact graphical assets and animations for the game are not specified."
}
### New Requirements
{
"Refined Implementation Approach": "We will refine our implementation approach to accommodate the new 8x8 grid and the increased winning score target of 4096. This will involve optimizing the game's core logic to handle the larger grid size efficiently and updating the scoring system. We will also enhance the UI to ensure it remains user-friendly and visually appealing with the new grid. The undo feature will be adapted to work seamlessly with the increased complexity of the game.",
"File list": [
"main.py",
"game.py",
"ui.py",
"constants.py"
],
"Refined Data Structures and Interfaces": "classDiagram
class Main {
+pygame: PygameInstance
+game: Game
+ui: UI
+run() void
}
class Game {
-grid: list
-current_score: int
-high_score: int
-last_move: list
-target_score: int
+__init__(grid_size: int, target_score: int)
+move(direction: str) bool
+undo() bool
+check_game_over() bool
+reset_game() void
+update_score(value: int) void
}
class UI {
-screen: PygameSurface
-font: PygameFont
+__init__(screen_size: tuple, grid_size: int)
+draw_grid(grid: list) void
+display_score(current_score: int, high_score: int) void
+show_game_over() void
+show_undo_button() void
+update_ui_for_larger_grid() void
}
class Constants {
+GRID_SIZE: int = 8
+TARGET_SCORE: int = 4096
+WINDOW_WIDTH: int
+WINDOW_HEIGHT: int
+BACKGROUND_COLOR: tuple
+TILE_COLORS: dict
}
Main --> Game
Main --> UI
Game --> Constants
UI --> Constants",
"Refined Program call flow": "sequenceDiagram
participant M as Main
participant G as Game
participant U as UI
M->>G: create instance(grid_size: Constants.GRID_SIZE, target_score: Constants.TARGET_SCORE)
M->>U: create instance(screen_size: (Constants.WINDOW_WIDTH, Constants.WINDOW_HEIGHT), grid_size: Constants.GRID_SIZE)
loop game loop
M->>U: draw_grid(G.grid)
M->>U: display_score(G.current_score, G.high_score)
M->>U: show_undo_button()
M->>G: move(direction)
alt if move is valid
G-->>M: return true
M->>G: update_score(value)
else if move is invalid
G-->>M: return false
end
alt if undo is triggered
M->>G: undo()
G-->>M: return true
else no undo
G-->>M: return false
end
alt if game over
M->>U: show_game_over()
M->>G: reset_game()
end
end",
"Anything UNCLEAR": "It remains unclear if the undo feature should have limitations on its use, such as a maximum number of undos per game or if it should be available without restriction. Further clarification on this aspect would be beneficial."
}
"""
llm = OpenAIGPTAPI()
@pytest.mark.asyncio
async def test_project_management_an():
node = await REFINED_PM_NODES.fill(CONTEXT, llm)
assert node.instruct_content
assert "Refined Logic Analysis" in node.instruct_content.json(ensure_ascii=False)

View file

@ -0,0 +1,112 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2024/01/03
@Author : mannaandpoem
@File : test_write_code_guideline_an.py.py
"""
import pytest
from metagpt.actions import WriteCode
from metagpt.actions.write_code_guideline_an import (
CODE_GUIDELINE_CONTEXT,
CODE_GUIDELINE_SCRIPT_EXAMPLE,
REFINE_CODE_SCRIPT_EXAMPLE,
REFINED_CODE_TEMPLATE,
WriteCodeGuideline,
)
from metagpt.provider import OpenAIGPTAPI
REQUIREMENT_EXAMPLE = """Add subtraction, multiplication and division operations to the calculator.
The current calculator can only perform basic addition operations, and it is necessary to introduce subtraction, multiplication, division operation into the calculator
"""
DESIGN_EXAMPLE = """
{
"Refined Implementation Approach": "To accommodate the new requirements, we will extend the existing Python-based calculator application. We will enhance the Tkinter-based UI to include buttons for subtraction, multiplication, and division, alongside the existing addition functionality. We will also implement input validation to handle edge cases such as division by zero. The architecture will be modular, with separate components for the UI, calculation logic, and error handling to maintain simplicity and facilitate future enhancements such as a history feature.",
"File list": [
"main.py",
"calculator.py",
"interface.py",
"operations.py"
],
"Refined Data Structures and Interfaces": "classDiagram\n class CalculatorApp {\n +main() None\n }\n class Calculator {\n -result float\n +add(number1: float, number2: float) float\n +subtract(number1: float, number2: float) float\n +multiply(number1: float, number2: float) float\n +divide(number1: float, number2: float) float\n +clear() None\n }\n class Interface {\n -calculator Calculator\n +start() None\n +display_result(result: float) None\n +get_input() float\n +show_error(message: str) None\n +update_operation(operation: str) None\n }\n class Operations {\n +perform_operation(operation: str, number1: float, number2: float) float\n }\n CalculatorApp --> Interface\n Interface --> Calculator\n Calculator --> Operations",
"Refined Program call flow": "sequenceDiagram\n participant CA as CalculatorApp\n participant I as Interface\n participant C as Calculator\n participant O as Operations\n CA->>I: start()\n I->>I: get_input()\n I->>I: update_operation(operation)\n loop For Each Operation\n I->>C: perform_operation(operation, number1, number2)\n C->>O: perform_operation(operation, number1, number2)\n O-->>C: return result\n C-->>I: return result\n I->>I: display_result(result)\n end\n I->>I: show_error(message)",
"Anything UNCLEAR": "The requirement for a history feature is mentioned but not prioritized. It is unclear whether this should be implemented now or in the future. Additionally, there is no specification on the limit to the size of the numbers or the number of operations that can be performed in sequence. These aspects will need clarification for complete implementation."
}
"""
TASKS_EXAMPLE = """
{
"Required Python packages": [
"tkinter"
],
"Required Other language third-party packages": [
"No third-party dependencies required"
],
"Refined Logic Analysis": [
[
"main.py",
"Entry point of the application, creates an instance of the Interface class and starts the application."
],
[
"calculator.py",
"Contains the Calculator class with add, subtract, multiply, divide and clear methods for performing arithmetic operations."
],
[
"interface.py",
"Contains the Interface class responsible for the GUI, interacts with Calculator for the logic and displays results or errors."
],
[
"operations.py",
"Contains the Operations class with perform_operation method that delegates the arithmetic operation based on the operation argument."
]
],
"Refined Task list": [
"operations.py",
"calculator.py",
"interface.py",
"main.py"
],
"Full API spec": "",
"Refined Shared Knowledge": "`interface.py` will use the Calculator class from `calculator.py` to perform operations and display results. `main.py` will be the starting point that initializes the Interface. `calculator.py` will now also interact with `operations.py` to perform the arithmetic operations.",
"Anything UNCLEAR": "The requirement for a history feature is mentioned but not prioritized. It is unclear whether this should be implemented now or in the future. Additionally, there is no specification on the limit to the size of the numbers or the number of operations that can be performed in sequence. These aspects will need clarification for complete implementation."
}
"""
INCREMENTAL_CHANGE_EXAMPLE = """
{
"Incremental Change": "- operations.py: Implement the Operations class with a method to perform the requested arithmetic operation. This class will be used by the Calculator class to execute the operations.\n```python\n## operations.py\nclass Operations:\n @staticmethod\n def perform_operation(operation: str, number1: float, number2: float) -> float:\n if operation == 'add':\n return number1 + number2\n elif operation == 'subtract':\n return number1 - number2\n elif operation == 'multiply':\n return number1 * number2\n elif operation == 'divide':\n if number2 == 0:\n raise ValueError('Cannot divide by zero')\n return number1 / number2\n else:\n raise ValueError('Invalid operation')\n```\n\n- calculator.py: Extend the Calculator class to include methods for subtraction, multiplication, and division. These methods will utilize the Operations class to perform the actual calculations.\n```python\n## calculator.py\nfrom operations import Operations\nclass Calculator:\n ...\n def subtract(self, number1: float, number2: float) -> float:\n return Operations.perform_operation('subtract', number1, number2)\n\n def multiply(self, number1: float, number2: float) -> float:\n return Operations.perform_operation('multiply', number1, number2)\n\n def divide(self, number1: float, number2: float) -> float:\n return Operations.perform_operation('divide', number1, number2)\n```\n\n- interface.py: Update the Interface class to include buttons for subtraction, multiplication, and division, and link them to the corresponding methods in the Calculator class. Also, handle the display of errors such as division by zero.\n```python\n## interface.py\nimport tkinter as tk\nfrom tkinter import messagebox\nfrom calculator import Calculator\n...\nclass Interface:\n ...\n def create_widgets(self):\n ...\n self.subtract_button = tk.Button(self.root, text='-', command=self.subtract, font=('Arial', 18))\n self.subtract_button.grid(row=3, column=0, sticky='nsew')\n\n self.multiply_button = tk.Button(self.root, text='*', command=self.multiply, font=('Arial', 18))\n self.multiply_button.grid(row=3, column=1, sticky='nsew')\n\n self.divide_button = tk.Button(self.root, text='/', command=self.divide, font=('Arial', 18))\n self.divide_button.grid(row=3, column=2, sticky='nsew')\n ...\n\n def subtract(self):\n number1, number2 = self.get_input()\n if number1 is not None and number2 is not None:\n result = self.calculator.subtract(number1, number2)\n self.display_result(result)\n\n def multiply(self):\n number1, number2 = self.get_input()\n if number1 is not None and number2 is not None:\n result = self.calculator.multiply(number1, number2)\n self.display_result(result)\n\n def divide(self):\n number1, number2 = self.get_input()\n if number1 is not None and number2 is not None:\n try:\n result = self.calculator.divide(number1, number2)\n except ValueError as e:\n self.show_error(str(e))\n return\n self.display_result(result)\n```\n\n- main.py: No changes needed in main.py as it serves as the entry point and will run the updated Interface class.\n```python\n## main.py\nfrom interface import Interface\n...\n```\n\nNote: Ensure that the new operations buttons in the Interface class are properly arranged and that the grid layout is adjusted accordingly. Also, make sure to import the messagebox module from tkinter for error handling."
}
"""
llm = OpenAIGPTAPI()
@pytest.mark.asyncio
async def test_write_code_guideline_an():
write_code_guideline = WriteCodeGuideline()
context = CODE_GUIDELINE_CONTEXT.format(
requirement=REQUIREMENT_EXAMPLE, design=DESIGN_EXAMPLE, tasks=TASKS_EXAMPLE, code=CODE_GUIDELINE_SCRIPT_EXAMPLE
)
node = await write_code_guideline.run(context=context)
assert node.instruct_content
assert "Incremental Change" in node.instruct_content.json(ensure_ascii=False)
@pytest.mark.asyncio
async def test_refine_code():
prompt = REFINED_CODE_TEMPLATE.format(
requirement=REQUIREMENT_EXAMPLE,
guideline=INCREMENTAL_CHANGE_EXAMPLE,
design=DESIGN_EXAMPLE,
tasks=TASKS_EXAMPLE,
code=REFINE_CODE_SCRIPT_EXAMPLE,
logs="",
feedback="",
filename="interface.py",
summary_log="",
)
code = await WriteCode().write_code(prompt=prompt)
assert code
assert "def create_widgets" in code

View file

@ -0,0 +1,85 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2024/01/03
@Author : mannaandpoem
@File : test_write_prd_an.py.py
"""
import pytest
from metagpt.actions.write_prd_an import REFINE_PRD_NODE
from metagpt.provider import OpenAIGPTAPI
CONTEXT = """
### New Project Name
py2048_game
### New Requirements
Changed score target for 2048 game from 2048 to 4096.
Please change the game's score target from 2048 to 4096, and change the interface size from 4*4 to 8*8.
### Legacy Content
{
"Language": "en_us",
"Programming Language": "Python",
"Original Requirements": "make a simple 2048 game based on pygame",
"Project Name": "pygame_2048",
"Product Goals": [
"Develop a user-friendly and intuitive 2048 game",
"Ensure the game is visually appealing and maintains a consistent theme",
"Implement smooth and responsive game controls"
],
"User Stories": [
"As a player, I want to experience a clear and simple interface so that I can focus on the gameplay",
"As a player, I want to see my current and high scores to track my progress",
"As a player, I want the option to undo my last move to improve my strategy"
],
"Competitive Analysis": [
"2048 Original: Classic gameplay with minimalistic design, but lacks modern features",
"2048 by Gabriele Cirulli: Open-source version with clean UI, but no additional features",
"2048 Hex: Unique hexagon board, providing a different challenge",
"2048 Multiplayer: Allows playing against others, but the interface is cluttered",
"2048 with AI: Includes AI challenge mode, but the AI is often too difficult for casual players",
"2048.io: Combines 2048 gameplay with .io style, though it can be overwhelming for new players",
"2048 Animated: Features animations, but has performance issues on some devices"
],
"Competitive Quadrant Chart": "quadrantChart\n title \"2048 Game Market Positioning\"\n x-axis \"Basic Features\" --> \"Advanced Features\"\n y-axis \"Low User Engagement\" --> \"High User Engagement\"\n quadrant-1 \"Niche Innovators\"\n quadrant-2 \"Market Leaders\"\n quadrant-3 \"Emerging Contenders\"\n quadrant-4 \"Falling Behind\"\n \"2048 Original\": [0.2, 0.7]\n \"2048 by Gabriele Cirulli\": [0.3, 0.8]\n \"2048 Hex\": [0.5, 0.4]\n \"2048 Multiplayer\": [0.6, 0.6]\n \"2048 with AI\": [0.7, 0.5]\n \"2048.io\": [0.4, 0.3]\n \"2048 Animated\": [0.3, 0.2]\n \"Our Target Product\": [0.8, 0.9]",
"Requirement Analysis": "The game should be simple yet engaging, with a focus on smooth performance and an intuitive user interface. High scores and undo functionality are important to users for a competitive and strategic gameplay experience. Aesthetic appeal and a consistent theme will also contribute to the game's success.",
"Requirement Pool": [
[
"P0",
"Develop core 2048 game mechanics using pygame"
],
[
"P0",
"Design a clean and intuitive user interface"
],
[
"P1",
"Implement score tracking with high score memory"
],
[
"P1",
"Add undo move feature for enhanced gameplay strategy"
],
[
"P2",
"Create visually appealing graphics and animations"
]
],
"UI Design draft": "The UI will feature a minimalist design with a focus on ease of use. The main game screen will display the game grid, current score, high score, and an undo button. The color scheme will be consistent and pleasant to the eye, with smooth transitions for tile movements.",
"Anything UNCLEAR": "The specifics of the undo feature need to be clarified, such as how many moves can be undone and whether it affects the scoring."
}
### Search Information
-
"""
llm = OpenAIGPTAPI()
@pytest.mark.asyncio
async def test_write_prd_an():
node = await REFINE_PRD_NODE.fill(CONTEXT, llm)
assert node.instruct_content
assert "Refined Requirement Pool" in node.instruct_content.json(ensure_ascii=False)