1. add mock.py in tests\data\incremental_dev_project

2. add mock for test case of ActionNode
3. add path of Guideline file in const.py
4. update engineer.py
This commit is contained in:
mannaandpoem 2024-01-17 18:47:59 +08:00
parent 8831177a22
commit a6d56bd748
9 changed files with 599 additions and 853 deletions

View file

@ -39,7 +39,7 @@ FILE_LIST = ActionNode(
)
REFINED_FILE_LIST = ActionNode(
key="Refined File List",
key="Refined File list",
expected_type=List[str],
instruction="Update and expand the original file list including only relative paths. Up to 2 files can be added."
"Ensure that the refined file list reflects the evolving structure of the project.",
@ -56,7 +56,7 @@ DATA_STRUCTURES_AND_INTERFACES = ActionNode(
)
REFINED_DATA_STRUCTURES_AND_INTERFACES = ActionNode(
key="Refined Data Structures and Interfaces",
key="Refined Data structures and interfaces",
expected_type=str,
instruction="Update and extend the existing mermaid classDiagram code syntax to incorporate new classes, "
"methods (including __init__), and functions with precise type annotations. Delineate additional "
@ -108,7 +108,7 @@ REFINE_NODES = [
]
DESIGN_API_NODE = ActionNode.from_children("DesignAPI", NODES)
REFINED_DESIGN_NODES = ActionNode.from_children("Refined_Design_API", REFINE_NODES)
REFINED_DESIGN_NODES = ActionNode.from_children("RefinedDesignAPI", REFINE_NODES)
def main():

View file

@ -5,11 +5,11 @@
@Author : mannaandpoem
@File : write_code_guideline_an.py
"""
import asyncio
from metagpt.actions.action import Action
from metagpt.actions.action_node import ActionNode
from metagpt.logs import logger
from metagpt.actions.action_node import ActionNode, dict_to_markdown
from metagpt.config import CONFIG
from metagpt.const import CODE_GUIDELINE_FILE_REPO, CODE_GUIDELINE_PDF_FILE_REPO
GUIDELINES_AND_INCREMENTAL_CHANGE = ActionNode(
key="Guidelines and Incremental Change",
@ -123,271 +123,6 @@ CODE_GUIDELINE_CONTEXT = """
{code}
"""
CODE_GUIDELINE_CONTEXT_EXAMPLE = """
## New Requirements
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
----- 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)
```
"""
REFINED_CODE_TEMPLATE = """
NOTICE
Role: You are a professional engineer; The main goal is to complete incremental development by combining legacy code and Guidelines and Incremental Change, ensuring the integration of new features.
@ -451,13 +186,21 @@ class WriteCodeGuideline(Action):
"meticulously craft comprehensive incremental development guidelines and deliver detailed Incremental Change"
return await WRITE_CODE_GUIDELINE_NODE.fill(context=context, llm=self.llm, schema="json")
@staticmethod
async def save(guideline):
await WriteCodeGuideline.save_json(guideline)
await WriteCodeGuideline.save_md(guideline)
async def main():
write_code_guideline = WriteCodeGuideline()
node = await write_code_guideline.run(CODE_GUIDELINE_CONTEXT_EXAMPLE)
guideline = node.instruct_content.model_dump_json()
logger.info(guideline)
@staticmethod
async def save_json(guideline):
filename = "code_guideline.json"
await CONFIG.git_repo.new_file_repository(CODE_GUIDELINE_FILE_REPO).save(
filename=filename, content=str(guideline)
)
if __name__ == "__main__":
asyncio.run(main())
@staticmethod
async def save_md(guideline):
filename = "code_guideline.md"
await CONFIG.git_repo.new_file_repository(CODE_GUIDELINE_PDF_FILE_REPO).save(
filename=filename, content=dict_to_markdown(guideline)
)

View file

@ -92,12 +92,14 @@ DOCS_FILE_REPO = "docs"
PRDS_FILE_REPO = "docs/prds"
SYSTEM_DESIGN_FILE_REPO = "docs/system_design"
TASK_FILE_REPO = "docs/tasks"
CODE_GUIDELINE_FILE_REPO = "docs/code_guideline"
COMPETITIVE_ANALYSIS_FILE_REPO = "resources/competitive_analysis"
DATA_API_DESIGN_FILE_REPO = "resources/data_api_design"
SEQ_FLOW_FILE_REPO = "resources/seq_flow"
SYSTEM_DESIGN_PDF_FILE_REPO = "resources/system_design"
PRD_PDF_FILE_REPO = "resources/prd"
TASK_PDF_FILE_REPO = "resources/api_spec_and_tasks"
CODE_GUIDELINE_PDF_FILE_REPO = "resources/code_guideline"
TEST_CODES_FILE_REPO = "tests"
TEST_OUTPUTS_FILE_REPO = "test_outputs"
CODE_SUMMARIES_FILE_REPO = "docs/code_summaries"

View file

@ -26,6 +26,7 @@ from pathlib import Path
from typing import Set
from metagpt.actions import Action, WriteCode, WriteCodeReview, WriteTasks
from metagpt.actions.action_node import dict_to_markdown
from metagpt.actions.fix_bug import FixBug
from metagpt.actions.summarize_code import SummarizeCode
from metagpt.actions.write_code_guideline_an import (
@ -34,6 +35,7 @@ from metagpt.actions.write_code_guideline_an import (
)
from metagpt.config import CONFIG
from metagpt.const import (
CODE_GUIDELINE_PDF_FILE_REPO,
CODE_SUMMARIES_FILE_REPO,
CODE_SUMMARIES_PDF_FILE_REPO,
PRDS_FILE_REPO,
@ -101,7 +103,7 @@ class Engineer(Role):
m = json.loads(task_msg.content)
return m.get("Task list") or m.get("Refined Task list")
async def _act_sp_with_cr(self, review=False, guideline="") -> Set[str]:
async def _act_sp_with_cr(self, review=False, guideline=Document()) -> Set[str]:
changed_files = set()
src_file_repo = CONFIG.git_repo.new_file_repository(CONFIG.src_workspace)
for todo in self.code_todos:
@ -112,16 +114,16 @@ class Engineer(Role):
3. Do we need other codes (currently needed)?
TODO: The goal is not to need it. After clear task decomposition, based on the design idea, you should be able to write a single file without needing other codes. If you can't, it means you need a clearer definition. This is the key to writing longer code.
"""
coding_context = await todo.run(guideline=guideline)
coding_context = await todo.run(guideline=guideline.content)
# Code review
if review:
action = WriteCodeReview(context=coding_context, llm=self.llm)
self._init_action_system_message(action)
coding_context = await action.run(guideline=guideline)
coding_context = await action.run(guideline=guideline.content)
dependencies = {coding_context.design_doc.root_relative_path, coding_context.task_doc.root_relative_path}
if guideline:
dependencies.add("code_guideline.json")
if guideline.content:
dependencies.add(guideline.root_relative_path)
await src_file_repo.save(
coding_context.filename,
dependencies=dependencies,
@ -364,12 +366,11 @@ class Engineer(Role):
code=old_codes,
)
node = await WriteCodeGuideline().run(context=context)
guideline = node.instruct_content.model_dump_json()
guideline = node.instruct_content.model_dump()
await WriteCodeGuideline.save(guideline)
guideline = dict_to_markdown(guideline)
await CONFIG.git_repo.new_file_repository(CONFIG.git_repo.workdir).save(
filename="code_guideline.json", content=guideline
)
return guideline
return Document(root_path=CODE_GUIDELINE_PDF_FILE_REPO, filename="code_guideline.md", content=guideline)
@staticmethod
async def get_old_codes() -> str: