Merge branch 'add_deployer_tool_to_engineer' into fix/deployer_can_not_serialisze

This commit is contained in:
黄伟韬 2024-10-12 16:11:18 +08:00
commit 21ddacd433
6 changed files with 92 additions and 28 deletions

View file

@ -79,12 +79,12 @@ Note:
21. When planning, consider whether images are needed. If you are developing a showcase website, start by using ImageGetter.get_image to obtain the necessary images.
22. When planning, merge multiple tasks that operate on the same file into a single task. For example, create one task for writing unit tests for all functions in a class. Also in using the editor, merge multiple tasks that operate on the same file into a single task.
23. When create unit tests for a code file, use Editor.read() to read the code file before planing. And create one plan to writing the unit test for the whole file.
24. Follow the Sytem Design and Project Schedule if exists. Otherwise, use default template folder of Vite, React, MUI and Tailwind CSS. If the template does not exist, use native HTML.
25. When writing Vue/React project: The Vue template is in the {vue_template_path}, the React template is in the {react_template_path}.
24. Follow the Sytem Design and Project Schedule if exists. Otherwise, use default template folder of Vite, React, MUI and Tailwind CSS. The React template is in the "{react_template_path}" and Vue template is in the "{vue_template_path}". If the template does not exist, use native HTML.
25. When writing Vue/React project:
25.1. Create the project folder first. Use cmd " mkdir -p {{project_name}} "
25.2. Copy a Vue/React template to your project and view all files. This must be a single respond. Use cmd "cp -r {{template_folder}}/* {{workspace}}/{{project_name}}/ && cd {{workspace}}/{{project_name}} && pwd && tree -f".
25.3. Read the content of each file and use the write_new_code command to rewrite the code. Be sure you are in the {{project_name}}. Reorganize the file structure to match the project template if it differs from the system design.
25.4. After finish the project. use "npm install" and "npm run build" to build the project.
25.3. Read the content of each file and use the write_new_code command to rewrite the code. Be sure you are in the {{project_name}}.
25.4. After finish the project. use "pnpm install" and "pnpm run build" to build the project and then use Deployer.deploy_to_public to deploy the project to the public.
26. Engineer2.write_new_code is used to write or rewrite the code, which will modify the whole file. Editor.edit_file_by_replace is used to edit a small part of the file.
""".format(
vue_template_path=VUE_TEMPLATE_PATH.absolute(),

View file

@ -18,6 +18,7 @@ from metagpt.roles.di.role_zero import RoleZero
from metagpt.schema import UserMessage
from metagpt.strategy.experience_retriever import ENGINEER_EXAMPLE
from metagpt.tools.libs.cr import CodeReview
from metagpt.tools.libs.deployer import Deployer
from metagpt.tools.libs.git import git_create_pull
from metagpt.tools.libs.image_getter import ImageGetter
from metagpt.tools.libs.terminal import Terminal
@ -33,7 +34,7 @@ class Engineer2(RoleZero):
goal: str = "Take on game, app, and web development."
instruction: str = ENGINEER2_INSTRUCTION
terminal: Terminal = Field(default_factory=Terminal, exclude=True)
deployer: Deployer = Field(default_factory=Deployer, exclude=True)
tools: list[str] = [
"Plan",
"Editor",
@ -45,6 +46,7 @@ class Engineer2(RoleZero):
"Engineer2",
"CodeReview",
"ImageGetter",
"Deployer",
]
# SWE Agent parameter
run_eval: bool = False
@ -86,6 +88,7 @@ class Engineer2(RoleZero):
"Terminal.run_command": self._eval_terminal_run,
"RoleZero.ask_human": self._end,
"RoleZero.reply_to_human": self._end,
"Deployer.deploy_to_public": self.deployer.deploy_to_public,
}
)
else:
@ -98,6 +101,7 @@ class Engineer2(RoleZero):
"CodeReview.review": cr.review,
"CodeReview.fix": cr.fix,
"Terminal.run_command": self.terminal.run_command,
"Deployer.deploy_to_public": self.deployer.deploy_to_public,
}
)

View file

@ -76,7 +76,7 @@ class RoleZero(Role):
tools: list[str] = [] # Use special symbol ["<all>"] to indicate use of all registered tools
tool_recommender: Optional[ToolRecommender] = None
tool_execution_map: Annotated[dict[str, Callable], Field(exclude=True)] = {}
special_tool_commands: list[str] = ["Plan.finish_current_task", "end", "Bash.run", "RoleZero.ask_human"]
special_tool_commands: list[str] = ["Plan.finish_current_task", "end", "Terminal.run_command", "RoleZero.ask_human"]
# List of exclusive tool commands.
# If multiple instances of these commands appear, only the first occurrence will be retained.
exclusive_tool_commands: list[str] = [
@ -543,7 +543,7 @@ class RoleZero(Role):
return end_output
return human_response
# output from bash.run may be empty, add decorations to the output to ensure visibility.
elif cmd["command_name"] == "Bash.run":
elif cmd["command_name"] == "Terminal.run_command":
tool_obj = self.tool_execution_map[cmd["command_name"]]
tool_output = await tool_obj(**cmd["args"])
if len(tool_output) <= 10:

View file

@ -839,19 +839,27 @@ Explanation: I will first need to read the system design document and the projec
}
]
```
## example 2.1
## example 2
User Requirement: Implement the core game project in Vue/React framework.
Explanation: This is a project that needs to be implemented using Vue.js. Therefore, I need to copy the Vue/React template to the project folder first.
## example 2.2
## example 3
User Requirement: Writing code.
The template is :
public
src
App.jsx
index.css
main.jsx
vite.config.js
Here's the plan:
[Optional] Read the original file in the template if they exist.
[Optional] Obtain images before coding.
1. **Task 1**: Rewrite `App.vue` - This file will contain the Vue structure necessary for the game's UI, the game logic and UI interactions.
1. **Task 1**: Rewrite `App.jsx` - This file will contain the Vue structure necessary for the game's UI, the game logic and UI interactions.
2. **Task 2**: Rewrite `style.css` - This file will define the CSS styles to make the game visually appealing and responsive. Default use tailwindcss.
3. **Task 3**: Rewrite `main.js` - This file is the main entry of Vue project, including the main Vue instance, global styles, and the router.
[Optional] Install the dependencies after finishing project.
If the project is a Vue or React Project, install the dependencies after finishing project. And then deploy the project to the public.
```json
[
{
@ -894,15 +902,15 @@ Here's the plan:
"command_name": "Plan.append_task",
"args": {
"task_id": "5",
"dependent_task_ids": ["2","3","4"],
"instruction": "Install the necessary dependencies and configure the project structure.",
"dependent_task_ids": [],
"instruction": "Install the necessary dependencies, configure the project structure and deploy it to the public",
"assignee": "Alex"
}
}
]
```
## example 3
## example 4
Explanation: Take on one task, such as writing or rewriting a file. Upon completion, finish current task.
```json
@ -919,8 +927,31 @@ Explanation: Take on one task, such as writing or rewriting a file. Upon complet
}
]
```
## example 5
Explanation: The project have been completed. And this project is a Vue/React Project,so i will deploy the project to the public.
## example 4
```json
[
{
"command_name": "Terminal.run_command",
"args": {
"cmd": "pnpm install && pnpm run build"
}
}
]
## example 6
Explanation: After install the project. I will deploy the project to the public.
```json
[
{
"command_name": "Deployer.deploy_to_public,
"args": {
"dist_dir": "{{project_path}}/dist"
}
}
]
## example 7
I have received a GitHub issue URL.
I will use browser to review the detailed information of this issue in order to understand the problem.
```json
@ -934,7 +965,7 @@ I will use browser to review the detailed information of this issue in order to
]
```
## example 6
## example 8
I need to locating the `openai_api.py` file, so I will search for the `openai_api.py` file.
```json
[
@ -949,7 +980,7 @@ I need to locating the `openai_api.py` file, so I will search for the `openai_ap
## example 7
## example 9
I have located the openai_api.py file. I want to edit this file, so I will open it first.
```json
[
@ -962,7 +993,7 @@ I have located the openai_api.py file. I want to edit this file, so I will open
]
```
## example 8
## example 10
I have opened the openai_api.py file. However, the range of lines shown is from 001 to 100, and I want to see more. Therefore, I want to use the scroll_down command to view additional lines.
```json
[
@ -973,7 +1004,7 @@ I have opened the openai_api.py file. However, the range of lines shown is from
]
```
## example 9
## example 11
I want to change the key bindings from (w/s) to the arrow keys (up, down). And add the space bar to pause.
the previous file look like:
142| while not self.is_game_over():
@ -1000,7 +1031,7 @@ Editor tool is exclusive. If I use this tool, I cannot use any other commands in
]
```
## example 10
## example 12
I want to add a score variable in the initialization of the game.
the previous file look like:
028| if restart:
@ -1032,7 +1063,7 @@ After executing the command, the file will be:
033| self.location = (0,0)
In the next turn, I will try to add another code snippet
## example 11
## example 13
Create a pull request (Optional): Merge the changes from the new branch into the master branch.
Thought: Now that the changes have been pushed to the remote repository, due to the user's requirement, let's create a pull request to merge the changes into the master branch.
@ -1053,7 +1084,7 @@ Thought: Now that the changes have been pushed to the remote repository, due to
]
```
## example 12
## example 14
The requirement is to create a product website featuring goods such as caps, dresses, and T-shirts.
I believe pictures would improve the site, so I will get the images first.
```json

View file

@ -1,11 +1,26 @@
from metagpt.tools.tool_registry import register_tool
from metagpt.utils.report import ServerReporter
# An un-implemented tool reserved for deploying a local service to public
@register_tool()
@register_tool(
include_functions=[
"deploy_to_public",
]
)
class Deployer:
"""Deploy a local service to public. Used only for final deployment, you should NOT use it for development and testing."""
def deploy_to_public(self, local_url: str):
ServerReporter().report(local_url, "local_url")
async def static_server(self, src_path: str) -> str:
"""This function will be implemented in the remote service."""
return f"http://127.0.0.1:{8000}/index.html"
async def deploy_to_public(self, dist_dir: str):
"""
Deploy a web project to public.
Args:
dist_dir (str): The dist directory of the web project after run build.
>>>
deployer = Deployer("2048_game/dist")
"""
url = await self.static_server(dist_dir)
return "The Project is deployed to: " + url + "\n Deployment successed!"

View file

@ -26,6 +26,11 @@ class Terminal:
self.stdout_queue = Queue(maxsize=1000)
self.observer = TerminalReporter()
self.process: Optional[asyncio.subprocess.Process] = None
# The cmd in forbidden_terminal_commands will be replace by pass ana return the advise. example:{"cmd":"forbidden_reason/advice"}
self.forbidden_commands = {
"npm run dev": "Use Deployer.deploy_to_public instead.",
"pnpm run dev": "Use Deployer.deploy_to_public instead.",
}
async def _start_process(self):
# Start a persistent shell process
@ -60,6 +65,14 @@ class Terminal:
if self.process is None:
await self._start_process()
output = ""
# Remove forbidden commands
for cmd_name, reason in self.forbidden_commands.items():
# 'true' is a pass command in linux terminal.
if cmd_name in cmd:
cmd = cmd.replace(cmd_name, "true")
output += f"{cmd_name} is failed to executed. {reason}\n"
# Send the command
self.process.stdin.write((cmd + self.command_terminator).encode())
self.process.stdin.write(
@ -68,9 +81,10 @@ class Terminal:
await self.process.stdin.drain()
if daemon:
asyncio.create_task(self._read_and_process_output(cmd))
return ""
else:
return await self._read_and_process_output(cmd)
output += await self._read_and_process_output(cmd)
return output
async def execute_in_conda_env(self, cmd: str, env, daemon=False) -> str:
"""