Update increment development for 0.5.x version: Delete files with 'refine' and write_code_guide.py. Add write_code_guide_an.py. Update write_code.py for guiding write code.

This commit is contained in:
mannaandpoem 2023-12-26 20:25:41 +08:00
parent 19b4b27f97
commit f7205645b2
12 changed files with 295 additions and 638 deletions

View file

@ -14,7 +14,6 @@ from metagpt.actions.debug_error import DebugError
from metagpt.actions.design_api import WriteDesign
from metagpt.actions.design_api_review import DesignReview
from metagpt.actions.project_management import AssignTasks, WriteTasks
from metagpt.actions.refine import Refine
from metagpt.actions.research import CollectLinks, WebBrowseAndSummarize, ConductResearch
from metagpt.actions.run_code import RunCode
from metagpt.actions.search_and_summarize import SearchAndSummarize

View file

@ -37,7 +37,7 @@ class PrepareDocuments(Action):
name = CONFIG.project_name or FileRepository.new_filename()
path = Path(CONFIG.workspace_path) / name
if path.exists() and not CONFIG.inc:
if Path(path).exists() and not CONFIG.inc:
shutil.rmtree(path)
CONFIG.git_repo = GitRepository(local_path=path, auto_init=True)

View file

@ -1,10 +0,0 @@
from metagpt.actions import Action
# 增量开发动作的基类
class Refine(Action):
def __init__(self, name="Refine", context=None, llm=None):
super().__init__(name, context, llm)
def run(self, *args, **kwargs):
raise NotImplementedError

View file

@ -1,222 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re
import shutil
from pathlib import Path
from typing import List, Union
from metagpt.actions import Action, ActionOutput
from metagpt.config import CONFIG
from metagpt.const import WORKSPACE_ROOT
from metagpt.logs import logger
from metagpt.utils.common import CodeParser
from metagpt.utils.get_template import get_template
from metagpt.utils.json_to_markdown import json_to_markdown
from metagpt.utils.mermaid import mermaid_to_file
templates = {
"json": {
"PROMPT_TEMPLATE": """
# Context
{context}
## Legacy
{legacy}
## Format example
{format_example}
-----
Role: You are an architect; the goal is to perform incremental development and design a state-of-the-art (SOTA) PEP8-compliant Python system based on the context and legacy design. Make the best use of good open source tools.
Requirement: Fill in the following missing information based on the context, each section name is a key in json. Output exactly as shown in the example, including single and double quotes.
Max Output: 8192 chars or 2048 tokens. Try to use them up.
## Incremental implementation approach: Provide as Python list[str]. Analyze the difficult points of the requirements, select the appropriate open-source framework. Up to 5.
## Python package name: Provide as Python str with python triple quoto, concise and clear, characters only use a combination of all lowercase and underscores
## Data structures and interface definitions: Use single quotes to wrap content. Use mermaid classDiagram code syntax, including classes (INCLUDING __init__ method) and functions (with type annotations), CLEARLY MARK the RELATIONSHIPS between classes, and comply with PEP8 standards. The data structures SHOULD BE VERY DETAILED and the API should be comprehensive with a complete design.
## Program call flow: Use single quotes to wrap content. Use sequenceDiagram code syntax, COMPLETE and VERY DETAILED, using CLASSES AND API DEFINED ABOVE accurately, covering the CRUD AND INIT of each object, SYNTAX MUST BE CORRECT.
output a properly formatted JSON, wrapped inside [CONTENT][/CONTENT] like format example.
Output exactly as shown in the example, including single and double quotes, and only output the json inside this tag, nothing else
""",
"FORMAT_EXAMPLE": """
[CONTENT]
{
"Incremental implementation approach": ["We will ...",],
"Python package name": "new_name",
"Data structures and interface definitions": '
classDiagram
class Game{
+int score
}
...
Game "1" -- "1" Food: has
',
"Program call flow": '
sequenceDiagram
participant M as Main
...
G->>M: end game
'
}
[/CONTENT]
""",
},
"markdown": {
"PROMPT_TEMPLATE": """
# Context
{context}
## Legacy
{legacy}
## Format example
{format_example}
-----
Role: You are an architect; the goal is to perform incremental development and design a state-of-the-art (SOTA) PEP8-compliant Python system based on the context and legacy design. Make the best use of good open source tools.
Requirement: Fill in the following missing information based on the context, note that all sections are response with code form separately. Output exactly as shown in the example, including single and double quotes.
Max Output: 8192 chars or 2048 tokens. Try to use them up.
Attention: Use '##' to split sections, not '#', and '## <SECTION_NAME>' SHOULD WRITE BEFORE the code and triple quote.
## Incremental implementation approach: Provide as Python list[str]. Analyze the difficult points of the requirements, select the appropriate open-source framework. Up to 5.
## Python package name: Provide as Python str with python triple quoto, concise and clear, characters only use a combination of all lowercase and underscores
## Data structures and interface definitions: Use single quotes to wrap content. Use mermaid classDiagram code syntax, including classes (INCLUDING __init__ method) and functions (with type annotations), CLEARLY MARK the RELATIONSHIPS between classes, and comply with PEP8 standards. The data structures SHOULD BE VERY DETAILED and the API should be comprehensive with a complete design.
## Program call flow: Use single quotes to wrap content. Use sequenceDiagram code syntax, COMPLETE and VERY DETAILED, using CLASSES AND API DEFINED ABOVE accurately, covering the CRUD AND INIT of each object, SYNTAX MUST BE CORRECT.
""",
"FORMAT_EXAMPLE": """
---
## Incremental implementation approach
```python
[
"We will ...",
]
```
## Python package name
```python
"new_name"
```
## Data structures and interface definitions
```mermaid
classDiagram
class Game{
+int score
}
...
Game "1" -- "1" Food: has
```
## Program call flow
```mermaid
sequenceDiagram
participant M as Main
...
G->>M: end game
```
---
""",
},
}
OUTPUT_MAPPING = {
# "Incremental Requirements": (str, ...),
# "Difference Description": (Union[List[str], str], ...),
"Incremental implementation approach": (Union[List[str], str], ...),
"Python package name": (str, ...),
# "File list": (List[str], ...),
"Data structures and interface definitions": (str, ...),
"Program call flow": (str, ...)
}
class RefineDesign(Action):
def __init__(self, name, context=None, llm=None):
super().__init__(name, context, llm)
self.desc = (
"Based on the PRD, think about the system design, and design the corresponding APIs, "
"data structures, library tables, processes, and paths. Please provide your design, feedback "
"clearly and in detail."
)
def recreate_workspace(self, workspace: Path):
try:
shutil.rmtree(workspace)
except FileNotFoundError:
pass # Folder does not exist, but we don't care
workspace.mkdir(parents=True, exist_ok=True)
def create_or_increment_workspace(self, workspace: Path):
# 如果工作空间已存在,添加数字以区分
original_workspace = workspace
index = 1
while workspace.exists():
ws_name_match = re.match(r'^(.*)_([\d]+)$', original_workspace.name)
if ws_name_match:
base_name, existing_index = ws_name_match.groups()
index = int(existing_index)
index += 1
workspace = original_workspace.parent / f"{base_name}_{index}"
else:
workspace = original_workspace.parent / f"{original_workspace.name}_{index}"
index += 1
# 创建工作空间,包括所有必要的父文件夹
workspace.mkdir(parents=True, exist_ok=True)
return workspace
async def _save_prd(self, docs_path, resources_path, context):
prd_file = docs_path / "prd.md"
if context[-1].instruct_content:
logger.info(f"Saving PRD to {prd_file}")
prd_file.write_text(json_to_markdown(context[-1].instruct_content.dict()))
async def _save_system_design(self, docs_path, resources_path, system_design):
data_api_design = system_design.instruct_content.dict()[
"Data structures and interface definitions"
] # CodeParser.parse_code(block="Data structures and interface definitions", text=content)
seq_flow = system_design.instruct_content.dict()[
"Program call flow"
] # CodeParser.parse_code(block="Program call flow", text=content)
await mermaid_to_file(data_api_design, resources_path / "data_api_design")
await mermaid_to_file(seq_flow, resources_path / "seq_flow")
system_design_file = docs_path / "system_design.md"
logger.info(f"Saving System Designs to {system_design_file}")
system_design_file.write_text((json_to_markdown(system_design.instruct_content.dict())))
async def _save(self, context, system_design):
if isinstance(system_design, ActionOutput):
ws_name = system_design.instruct_content.dict()["Python package name"]
else:
ws_name = CodeParser.parse_str(block="Python package name", text=system_design)
workspace = WORKSPACE_ROOT / ws_name
# workspace = self.create_or_increment_workspace(workspace)
self.recreate_workspace(workspace)
docs_path = workspace / "docs"
resources_path = workspace / "resources"
docs_path.mkdir(parents=True, exist_ok=True)
resources_path.mkdir(parents=True, exist_ok=True)
await self._save_prd(docs_path, resources_path, context)
await self._save_system_design(docs_path, resources_path, system_design)
async def run(self, context, legacy, format=CONFIG.prompt_format):
prompt_template, format_example = get_template(templates, format)
prompt = prompt_template.format(context=context, legacy=legacy, format_example=format_example)
# system_design = await self._aask(prompt)
system_design = await self._aask_v1(prompt, "system_design", OUTPUT_MAPPING, format=format)
# fix Python package name, we can't system_design.instruct_content.python_package_name = "xxx" since "Python package name" contain space, have to use setattr
setattr(
system_design.instruct_content,
"Python package name",
system_design.instruct_content.dict()["Python package name"].strip().strip("'").strip('"'),
)
await self._save(context, system_design)
return system_design

View file

@ -1,95 +0,0 @@
from typing import List, Union
from metagpt.actions import Refine, ActionOutput, SearchAndSummarize
from metagpt.config import CONFIG
from metagpt.logs import logger
from metagpt.utils.get_template import get_template
increment_template = {
"json": {
"PROMPT_TEMPLATE": """
# Context
{context}
## Legacy
{legacy}
## Search Information
{search_information}
## Format example
{format_example}
-----
Role: You are a professional Product Manager tasked with overseeing incremental development and crafting Product Requirements Documents (PRDs) for a concise, usable, and efficient product.
Requirements: According to the context, fill in the following missing information, each section name is a key in json ,If the requirements are unclear, ensure minimum viability and avoid excessive design. Only output one json, nothing else.
## Incremental Development Analysis: Provide as Python list[str], up to 5. incremental development analysis and plans based on the context and the legacy. If the requirement itself is simple, the Incremental Development Analysis should also be simple.
output a properly formatted JSON, wrapped inside [CONTENT][/CONTENT] like format example,
and only output the json inside this tag, nothing else
""",
"FORMAT_EXAMPLE": """
[CONTENT]
{
"Incremental Development Analysis": [],
}
[/CONTENT]
""",
},
"markdown": {
"PROMPT_TEMPLATE": """
# Context
{context}
## Legacy
{legacy}
## Search Information
{search_information}
## Format example
{format_example}
-----
Role: You are a professional Product Manager tasked with overseeing incremental development and crafting Product Requirements Documents (PRDs) for a concise, usable, and efficient product.
Requirements: According to the context, fill in the following missing information, note that each sections are returned in Python code triple quote form seperatedly. If the requirements are unclear, ensure minimum viability and avoid excessive design
ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. AND '## <SECTION_NAME>' SHOULD WRITE BEFORE the code and triple quote. Output carefully referenced "Format example" in format.Only output one json, nothing else.
## Incremental Development Analysis: Provide as Python list[str], up to 5. Incremental development analysis and plans based on the context and the legacy. If the requirement itself is simple, the Incremental Development Analysis should also be simple.
""",
"FORMAT_EXAMPLE": """
---
## Incremental Development Analysis
[
"We will ...",
]
""",
},
}
INCREMENT_OUTPUT_MAPPING = {
"Incremental Development Analysis": (List[str], ...),
}
class RefinePRD(Refine):
def __init__(self, name="RefinePRD", context=None, llm=None):
super().__init__(name, context, llm)
async def run(self, context, legacy, format=CONFIG.prompt_format, *args, **kwargs):
sas = SearchAndSummarize()
rsp = ""
info = f"### Search Results\n{sas.result}\n\n### Search Summary\n{rsp}"
if sas.result:
logger.info(sas.result)
logger.info(rsp)
prompt_template, format_example = get_template(increment_template, format)
prompt = prompt_template.format(
context=context, legacy=legacy, search_information=info,
format_example=format_example
)
logger.debug(prompt)
prd = await self._aask_v1(prompt, "prd", INCREMENT_OUTPUT_MAPPING, format=format)
return prd

View file

@ -1,146 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from typing import List, Union
from metagpt.actions.action import Action
from metagpt.config import CONFIG
from metagpt.const import WORKSPACE_ROOT
from metagpt.utils.common import CodeParser
from metagpt.utils.get_template import get_template
from metagpt.utils.json_to_markdown import json_to_markdown
templates = {
"json": {
"PROMPT_TEMPLATE": """
# Context
{context}
## Legacy
{legacy}
## Format example
{format_example}
-----
Role: You are a project manager; the goal is to perform incremental development based on the context and the legacy. Break down tasks according to PRD/technical design, provide a Task list, and analyze task dependencies to start with the prerequisite modules.
Requirements: Based on the context and the Legacy Project Management and Legacy Code, fill in the following missing information. Note that Please try your best to reuse legacy code, and all sections are returned in Python code triple quote form seperatedly. Here the granularity of the task is a file that need to modified.
Attention: Use '##' to split sections, not '#', and '## <SECTION_NAME>' SHOULD WRITE BEFORE the code and triple quote.
Output a properly formatted JSON, wrapped inside [CONTENT][/CONTENT]. The following is the attribute description of the JSON object.
## Required Python third-party packages: Provided as a python list, the requirements.txt format
## Full API spec: Use OpenAPI 3.0. Describe all APIs that may be used by both frontend and backend based on the previous.
## Task list: Provided as Python list[str]. Each str is a filename, the more at the beginning, the more it is a prerequisite dependency, should be done first.
Output a properly formatted JSON, wrapped inside [CONTENT][/CONTENT] like format example,
and only output the json inside this tag, nothing else
""",
"FORMAT_EXAMPLE": '''
{
"Required Python third-party packages": [
"flask==1.1.2",
"bcrypt==3.2.0"
],
"Full API spec": """
openapi: 3.0.0
...
description: A JSON object ...
""",
"Task list": [
"game.py"
]
}
''',
},
"markdown": {
"PROMPT_TEMPLATE": """
# Context
{context}
## Legacy
{legacy}
## Format example
{format_example}
-----
Role: You are a project manager; the goal is to perform incremental development based on the context and the legacy. Break down tasks according to PRD/technical design, provide a Task list need to modified files, and analyze task dependencies to start with the prerequisite modules.
Requirements: Based on the context and the Legacy Project Management and Legacy Code, fill in the following missing information. Note that Please try your best to reuse legacy code, and all sections are returned in Python code triple quote form seperatedly. Here the granularity of the task is a file that need to modified.
Attention: Use '##' to split sections, not '#', and '## <SECTION_NAME>' SHOULD WRITE BEFORE the code and triple quote.
## Required Python third-party packages: Provided as a python list, the requirements.txt format
## Full API spec: Use OpenAPI 3.0. Describe all APIs that may be used by both frontend and backend based on the previous.
## Task list: Provided as Python list[str]. Each str is a filename, the more at the beginning, the more it is a prerequisite dependency, should be done first.
""",
"FORMAT_EXAMPLE": '''
---
## Required Python third-party packages
```python
[
"flask==1.1.2",
"bcrypt==3.2.0"
]
```
## Full API spec
```python
"""
openapi: 3.0.0
...
description: A JSON object ...
"""
```
## Task list
```python
[
"game.py",
]
```
---
''',
},
}
OUTPUT_MAPPING = {
# "Incremental Requirements": (str, ...),
# ## Incremental Requirements: Provided as a str, the foremost incremental requirements for project management here based on the previous.
# "Difference Analysis": (Union[List[str], str], ...),
"Required Python third-party packages": (Union[List[str], str], ...),
"Full API spec": (str, ...),
# "Logic Analysis": (List[List[str]], ...),
"Task list": (List[str], ...),
}
class RefineTasks(Action):
def __init__(self, name="RefineTasks", context=None, llm=None):
super().__init__(name, context, llm)
def _save(self, context, rsp):
if context[-1].instruct_content:
ws_name = context[-1].instruct_content.dict()["Python package name"]
else:
ws_name = CodeParser.parse_str(block="Python package name", text=context[-1].content)
file_path = WORKSPACE_ROOT / ws_name / "docs/api_spec_and_tasks.md"
file_path.write_text(json_to_markdown(rsp.instruct_content.dict()))
# Write requirements.txt
requirements_path = WORKSPACE_ROOT / ws_name / "requirements.txt"
requirements_path.write_text("\n".join(rsp.instruct_content.dict().get("Required Python third-party packages")))
async def run(self, context, legacy, format=CONFIG.prompt_format):
prompt_template, format_example = get_template(templates, format)
prompt = prompt_template.format(context=context,
legacy=legacy,
format_example=format_example)
rsp = await self._aask_v1(prompt, "task", OUTPUT_MAPPING, format=format)
self._save(context, rsp)
return rsp
class AssignTasks(Action):
async def run(self, *args, **kwargs):
# Here you should implement the actual action
pass

View file

@ -21,6 +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_guide_an import WRITE_CODE_INCREMENT_TEMPLATE
from metagpt.config import CONFIG
from metagpt.const import (
BUGFIX_FILENAME,
@ -114,20 +115,35 @@ class WriteCode(Action):
test_detail = RunCodeResult.loads(test_doc.content)
logs = test_detail.stderr
guideline = kwargs.get("guideline", "")
if bug_feedback:
code_context = coding_context.code_doc.content
elif guideline:
code_context = await self.get_codes(coding_context.task_doc, exclude=self.context.filename, mode="guide")
else:
code_context = await self.get_codes(coding_context.task_doc, exclude=self.context.filename)
prompt = PROMPT_TEMPLATE.format(
design=coding_context.design_doc.content if coding_context.design_doc else "",
tasks=coding_context.task_doc.content if coding_context.task_doc else "",
code=code_context,
logs=logs,
feedback=bug_feedback.content if bug_feedback else "",
filename=self.context.filename,
summary_log=summary_doc.content if summary_doc else "",
)
if guideline: # guide write code 也有两种方式,进行尝试
prompt = WRITE_CODE_INCREMENT_TEMPLATE.format(
guideline=guideline,
design=coding_context.design_doc.content if coding_context.design_doc else "",
tasks=coding_context.task_doc.content if coding_context.task_doc else "",
code=code_context,
logs=logs,
feedback=bug_feedback.content if bug_feedback else "",
filename=self.context.filename,
summary_log=summary_doc.content if summary_doc else "",
)
else:
prompt = PROMPT_TEMPLATE.format(
design=coding_context.design_doc.content if coding_context.design_doc else "",
tasks=coding_context.task_doc.content if coding_context.task_doc else "",
code=code_context,
logs=logs,
feedback=bug_feedback.content if bug_feedback else "",
filename=self.context.filename,
summary_log=summary_doc.content if summary_doc else "",
)
logger.info(f"Writing {coding_context.filename}..")
code = await self.write_code(prompt)
if not coding_context.code_doc:
@ -138,7 +154,7 @@ class WriteCode(Action):
return coding_context
@staticmethod
async def get_codes(task_doc, exclude) -> str:
async def get_codes(task_doc, exclude, mode="normal") -> str:
if not task_doc:
return ""
if not task_doc.content:
@ -147,11 +163,37 @@ class WriteCode(Action):
code_filenames = m.get("Task list", [])
codes = []
src_file_repo = CONFIG.git_repo.new_file_repository(relative_path=CONFIG.src_workspace)
for filename in code_filenames:
if filename == exclude:
continue
doc = await src_file_repo.get(filename=filename)
if not doc:
continue
codes.append(f"----- {filename}\n" + doc.content)
old_file_repo = CONFIG.git_repo.new_file_repository(relative_path=CONFIG.old_workspace)
src_files = src_file_repo.all_files
old_files = old_file_repo.all_files
union_files_list = list(set(src_files) | set(old_files))
if mode == "guide":
# 从两个repo中取code并结合在一起
for filename in union_files_list:
if filename == exclude:
if filename in old_files:
doc = await old_file_repo.get(filename=filename) # 使用原始代码
else:
continue
else:
doc = await src_file_repo.get(filename=filename) # 使用先前生成的代码
if not doc:
if filename in old_files:
doc = await old_file_repo.get(filename=filename) # 使用原始代码
else:
continue
codes.append(f"----- {filename}\n```{doc.content}```")
else:
for filename in code_filenames:
if filename == exclude:
continue
doc = await src_file_repo.get(filename=filename)
if not doc:
continue
codes.append(f"----- {filename}\n```{doc.content}```")
return "\n".join(codes)

View file

@ -1,74 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from metagpt.actions.action import Action
from metagpt.logs import logger
from metagpt.schema import Message
from metagpt.utils.common import CodeParser
from tenacity import retry, stop_after_attempt, wait_fixed
PROMPT_TEMPLATE = """
NOTICE
Role: You are a professional software engineer, and your main task is to conduct incremental development, proposing incremental development plans and code guideance based on context and legacy code. Existing code and logic that need to be retained must also appear in the code after incremental development, do not omit it. Ensure that the code conforms to the PEP8 standards, is elegantly designed and modularized, easy to read and maintain, and is written in Python 3.9 (or in another programming language). Output format carefully referenced "Format example".
## Regulations Review: To make the software directly operable without further coding, follow the regulations below during incremental development:
0) Determine the scope of responsibilities of each file and what classes and methods need to be implemented.
1) Import all referenced classes.
2) Implement all methods.
3) Add necessary explanation to all methods.
4) Ensure there are no potential bugs.
5) Confirm that the entire project conforms to the tasks proposed by the user.
6) Review the code thoroughly, checking for errors and validating the logic to ensure seamless user interaction without compromising any specified requirements.
## Incremental Development Plan: Provided as a Python list containing `filename.py`. Proposed the detail and essential incremental development plan, based on the following context and legacy code by thinking and analyzing step by step. All incremental modules/functions need to be added to the corresponding code files.
## Code Guidance: Propose the foremost guidelines that how to implement code of modification part for incremental development based on the above context, legacy code and incremental development plan.
-----
# Context
{context}
## Legacy Code
You are tasked with conducting incremental development in the existing code based on the provided legacy code and above information.
```
{legacy}
```
-----
## Format example
-----
## Incremental Development Guide
[
"`game.py` Contains `Game` and ...",
]
## Code Guidance
### Implementation `xx` in `xxx.py` ..., else retain the original xxx.py code.
```python
## xxx.py
...
```
---
### Implementation of the `Game` in `game.py` ..., else retain the original game.py code.
```python
## game.py
class Game:
...
```
-----
"""
class WriteCodeGuide(Action):
def __init__(self, name="WriteCodeGuide", context: list[Message] = None, llm=None):
super().__init__(name, context, llm)
async def run(self, context, legacy):
prompt = PROMPT_TEMPLATE.format(context=context, legacy=legacy)
logger.info(f'Write Code Guide ..')
code_guide = await self._aask(prompt)
# code_rsp = await self._aask_v1(prompt, "code_rsp", OUTPUT_MAPPING)
return code_guide

View file

@ -0,0 +1,223 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2023/12/26
@Author : mannaandpoem
@File : write_code_guide_an.py
"""
import asyncio
from metagpt.actions.action_node import ActionNode
from pydantic import Field
from metagpt.actions.action import Action
from metagpt.llm import LLM
from metagpt.provider.base_gpt_api import BaseGPTAPI
from metagpt.schema import Document
GUIDELINE = ActionNode(
key="Code Guideline",
expected_type=list[str],
instruction="You are a professional software engineer, and your main task is to "
"proposing incremental development plans and code guidance",
example=[
"`calculator.py` should be extended to include methods for subtraction, multiplication, and division. Error handling should be implemented for division to prevent division by zero.",
"New endpoints for subtraction, multiplication, and division should be added to `main.py`.",
],
)
INCREMENTAL_CHANGE = ActionNode(
key="Incremental Change",
expected_type=str,
instruction="Write Incremental Change by making a code draft that how to implement incremental development based on the context and Code Guideline.",
example="""1. Extend `Calculator` class in `calculator.py` with new methods for subtraction, multiplication, and division.
```python
## calculator.py
class Calculator:
...
def subtract_numbers(self, num1: int, num2: int) -> int:
return num1 - num2
def multiply_numbers(self, num1: int, num2: int) -> int:
return num1 * num2
def divide_numbers(self, num1: int, num2: int) -> float:
if num2 == 0:
raise ValueError('Cannot divide by zero')
return num1 / num2
```
2. Implement new endpoints in `main.py` for the subtraction, multiplication, and division methods.
```python
## main.py
from flask import Flask, request, jsonify
from calculator import Calculator
app = Flask(__name__)
calculator = Calculator()
...
@app.route('/subtract_numbers', methods=['POST'])
def subtract_numbers():
data = request.get_json()
num1 = data.get('num1', 0)
num2 = data.get('num2', 0)
result = calculator.subtract_numbers(num1, num2)
return jsonify({'result': result}), 200
@app.route('/multiply_numbers', methods=['POST'])
def multiply_numbers():
data = request.get_json()
num1 = data.get('num1', 0)
num2 = data.get('num2', 0)
result = calculator.multiply_numbers(num1, num2)
return jsonify({'result': result}), 200
@app.route('/divide_numbers', methods=['POST'])
def divide_numbers():
data = request.get_json()
num1 = data.get('num1', 1)
num2 = data.get('num2', 1)
try:
result = calculator.divide_numbers(num1, num2)
except ValueError as e:
return jsonify({'error': str(e)}), 400
return jsonify({'result': result}), 200
if __name__ == '__main__':
app.run()
```"""
)
CODE_GUIDE_CONTEXT = """
NOTICE
Role: You are a professional software engineer, and your main task is to write code Guideline and code craft 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: Legacy Code may be more or less files than Tasks List in Tasks. However, only code guidance and Incremental Change are written for the files in Tasks List.
### Requirement
{requirement}
### Design
{design}
### Tasks
{tasks}
### Legacy Code
{code}
"""
WRITE_CODE_INCREMENT_TEMPLATE = """
NOTICE
Role: You are a professional engineer; The main goal is to complete incremental development by combining Legacy Code and Guideline to rewrite the complete code.
Language: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.
ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced "Format example".
# Context
## Guideline
{guideline}
## Design
{design}
## Tasks
{tasks}
## Legacy Code
```Code
{code}
```
## Debug logs
```text
{logs}
{summary_log}
```
## Bug Feedback logs
```text
{feedback}
```
# Format example
## Code: {filename}
```python
## {filename}
...
```
# Instruction: Based on the context, follow "Format example", write code.
## Rewrite Complete Code: Only Write one file {filename}, Write code using triple quotes, based on the following attentions and context.
1. Only One file: do your best to implement THIS ONLY ONE FILE.
2. COMPLETE CODE: Your code will be part of the entire project, so please implement complete, reliable, reusable code snippets.
3. Set default value: If there is any setting, ALWAYS SET A DEFAULT VALUE, ALWAYS USE STRONG TYPE AND EXPLICIT VARIABLE. AVOID circular import.
4. Follow design: YOU MUST FOLLOW "Data structures and interfaces". DONT CHANGE ANY DESIGN. Do not use public member functions that do not exist in your design.
5. CAREFULLY CHECK THAT YOU DONT MISS ANY NECESSARY CLASS/FUNCTION IN THIS FILE.
6. Before using a external variable/module, make sure you import it first.
7. Write out EVERY CODE DETAIL, DON'T LEAVE TODO.
8. Attention1: Implement the functions required by the current file scope of responsibility.
9. Attention2: Make modifications and additions to the legacy code in accordance with the provided guidelines and API. Ensure that the complete code is implemented without any omissions.
"""
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 = [
GUIDELINE,
INCREMENTAL_CHANGE
]
WRITE_CODE_GUIDE_NODE = ActionNode.from_children("WriteCodeGuide", GUIDE_NODES)
class WriteCodeGuide(Action):
name: str = "WriteCodeGuide"
context: Document = Field(default_factory=Document)
llm: BaseGPTAPI = Field(default_factory=LLM)
async def run(self):
rsp = await WRITE_CODE_GUIDE_NODE.fill(context=CODE_GUIDE_CONTEXT, llm=self.llm, schema="json")
return rsp
def main():
action = WriteCodeGuide()
return asyncio.run(action.run())
if __name__ == "__main__":
main()

View file

@ -1,67 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from metagpt.actions.action import Action
from metagpt.logs import logger
from metagpt.schema import Message
from metagpt.utils.common import CodeParser
from tenacity import retry, stop_after_attempt, wait_fixed
PROMPT_TEMPLATE = """
NOTICE
Role: You are a professional engineer; your primary goal is to write PEP8 compliant, elegant, modular, easy-to-read, and maintainable Python 3.9 code (or any other programming language of your choice).
Requirements: Rewrite the complete code based on the Legacy Code so that it can be executed and avoid any potential bugs. You should modify the corresponding code based on the guidance. Output the complete code, fixing all errors according to the context. Ensure that you adhere to the specified guidelines for incremental development and modification of legacy code.
ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format should be carefully referenced using the "Format example". Only output the current modified code, nothing else. In the modified code, if unchanged, you should output it, the complete code.
## Rewrite Complete Code: Only Write one file {filename}, Write code using triple quotes, based on the following list, context, guidelines and legacy code.
1. Important: Do your best to implement ONLY ONE FILE. ONLY USE EXISTING API. IF NO API, IMPLEMENT IT.
2. Implement one of the following code files based on the provided context. Return the code in the specified format. Your code will be part of the entire project, so ensure it is complete, reliable, and reusable.
3. Attention1: Implement the functions required by the current file scope of responsibility. For example, main only needs to focus on the basic functions of main.py in the legacy code and the incremental functions to be implemented. Reuse existing code as much as possible. You can import functions from other codes instead of reimplementing the function. If there is any setting, ALWAYS SET A DEFAULT VALUE, ALWAYS USE STRONG TYPE AND EXPLICIT VARIABLE.
4. Attention2: Make modifications and additions to the legacy code in accordance with the provided guidelines and API. Ensure that the complete code is implemented without any omissions, taking into account the guidelines, context, and existing legacy code. Retain the basic function methods from the legacy code, and make sure to preserve the existing code and logic that needs to be retained throughout the incremental development process. Avoid omitting any essential components.
5. Think before writing: What should be implemented and provided in this document?
6. CAREFULLY CHECK THAT YOU DONT MISS ANY NECESSARY CLASS/FUNCTION IN THIS FILE.
7. Do not use public member functions that do not exist in your design.
8. The Modified Code is implemented according to the requirements, and there are no issues with the code logic. All functions in the Modified Code are fully implemented; none are omitted or incomplete.
-----
# Context
{context}
-----
## Guidelines: The foremost guidelines of modification for incremental development.
{guide}
-----
## Legacy Code: The Legacy Code that needs to be modified. '===' is the separator of each code file in the legacy code. Basic function methods need to be retained in {filename}.
{legacy}
-----
## Format example
-----
## Rewrite Complete Code: {filename}
```python
# {filename}
...
```
-----
"""
class WriteCodeRefine(Action):
def __init__(self, name="WriteCodeRefine", context: list[Message] = None, llm=None):
super().__init__(name, context, llm)
@retry(stop=stop_after_attempt(2), wait=wait_fixed(1))
async def write_code(self, prompt):
code_rsp = await self._aask(prompt)
code = CodeParser.parse_code(block="", text=code_rsp)
return code
async def run(self, context, legacy, filename, guide):
prompt = PROMPT_TEMPLATE.format(context=context, legacy=legacy, filename=filename, guide=guide)
logger.info(f'Code refine {filename}..')
code = await self.write_code(prompt)
# code_rsp = await self._aask_v1(prompt, "code_rsp", OUTPUT_MAPPING)
# self._save(context, filename, code)
return code

View file

@ -237,7 +237,7 @@ class OpenAIGPTAPI(BaseGPTAPI, RateLimiter):
"n": 1,
"stop": None,
"temperature": 0.3,
"timeout": 3,
"timeout": 30,
"model": self.model,
}
if configs:

View file

@ -20,6 +20,7 @@
from __future__ import annotations
import json
import os
from collections import defaultdict
from pathlib import Path
from typing import Set
@ -27,6 +28,7 @@ from typing import Set
from metagpt.actions import Action, WriteCode, WriteCodeReview, WriteTasks
from metagpt.actions.fix_bug import FixBug
from metagpt.actions.summarize_code import SummarizeCode
from metagpt.actions.write_code_guide_an import CODE_GUIDE_CONTEXT, WRITE_CODE_GUIDE_NODE, WriteCodeGuide
from metagpt.config import CONFIG
from metagpt.const import (
CODE_SUMMARIES_FILE_REPO,
@ -77,6 +79,7 @@ class Engineer(Role):
)
n_borg: int = 1
use_code_review: bool = False
use_code_guide: bool = True
code_todos: list = []
summarize_todos = []
@ -84,14 +87,14 @@ class Engineer(Role):
super().__init__(**kwargs)
self._init_actions([WriteCode])
self._watch([WriteTasks, SummarizeCode, WriteCode, WriteCodeReview, FixBug])
self._watch([WriteTasks, SummarizeCode, WriteCode, WriteCodeReview, FixBug, WriteCodeGuide])
@staticmethod
def _parse_tasks(task_msg: Document) -> list[str]:
m = json.loads(task_msg.content)
return m.get("Task list")
async def _act_sp_with_cr(self, review=False) -> Set[str]:
async def _act_sp_with_cr(self, review=False, guideline="") -> Set[str]:
changed_files = set()
src_file_repo = CONFIG.git_repo.new_file_repository(CONFIG.src_workspace)
for todo in self.code_todos:
@ -102,7 +105,7 @@ 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()
coding_context = await todo.run(guideline=guideline)
# Code review
if review:
action = WriteCodeReview(context=coding_context, llm=self._llm)
@ -134,7 +137,11 @@ class Engineer(Role):
return None
async def _act_write_code(self):
changed_files = await self._act_sp_with_cr(review=self.use_code_review)
if self.use_code_guide:
code_guideline = await self._write_code_guideline()
changed_files = await self._act_sp_with_cr(review=self.use_code_review, guideline=code_guideline)
else:
changed_files = await self._act_sp_with_cr(review=self.use_code_review)
return Message(
content="\n".join(changed_files),
role=self.profile,