mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-07-17 16:41:05 +02:00
update_engineer_and_editorprompt
This commit is contained in:
parent
5dcde29d73
commit
c2f9f010b7
10 changed files with 251 additions and 219 deletions
|
|
@ -1,18 +1,14 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from metagpt.config2 import Config
|
||||
from metagpt.logs import logger
|
||||
|
||||
# from metagpt.actions.write_code_review import ValidateAndRewriteCode
|
||||
from metagpt.prompts.di.engineer2 import (
|
||||
CURRENT_EDITOR_STATE,
|
||||
CURRENT_TERMINAL_STATE,
|
||||
ENGINEER2_CMD_PROMPT,
|
||||
CURRENT_STATE,
|
||||
ENGINEER2_INSTRUCTION,
|
||||
WRITE_CODE_PROMPT,
|
||||
WRITE_CODE_SYSTEM_PROMPT,
|
||||
|
|
@ -33,7 +29,6 @@ class Engineer2(RoleZero):
|
|||
profile: str = "Engineer"
|
||||
goal: str = "Take on game, app, and web development."
|
||||
instruction: str = ENGINEER2_INSTRUCTION
|
||||
cmd_prompt: str = ENGINEER2_CMD_PROMPT
|
||||
terminal: Terminal = Field(default_factory=Terminal, exclude=True)
|
||||
|
||||
tools: list[str] = [
|
||||
|
|
@ -58,52 +53,38 @@ class Engineer2(RoleZero):
|
|||
|
||||
async def _format_instruction(self):
|
||||
"""
|
||||
Formats the instruction message for the Engineer2.
|
||||
Uses Editor's state to format the `_instruction` template.
|
||||
Display the current terminal and editor state.
|
||||
This information will be dynamically added to the command prompt.
|
||||
"""
|
||||
bash_working_dir = await self.terminal.run_command("pwd")
|
||||
bash_state = {"working_dir": bash_working_dir}
|
||||
editor_state = {"open_file": self.editor.current_file, "working_dir": self.editor.working_dir}
|
||||
self.cmd_prompt_current_state = CURRENT_EDITOR_STATE.format(
|
||||
**editor_state
|
||||
).strip() + CURRENT_TERMINAL_STATE.format(**bash_state)
|
||||
state = {
|
||||
"editor_open_file": self.editor.current_file,
|
||||
"editor_current_directory": self.editor.working_dir,
|
||||
"terminal_current_directory": await self.terminal.run_command("pwd"),
|
||||
}
|
||||
self.cmd_prompt_current_state = CURRENT_STATE.format(**state).strip()
|
||||
|
||||
def _update_tool_execution(self):
|
||||
self.tool_execution_map.update(
|
||||
{
|
||||
"Terminal.run_command": self.eval_terminal_run if self.run_eval else self.terminal.run_command,
|
||||
"Terminal.run_command": self.terminal.run_command,
|
||||
"git_create_pull": git_create_pull,
|
||||
"Engineer2.write_new_code": self.write_new_code,
|
||||
# "ValidateAndRewriteCode.run": validate.run,
|
||||
# "ValidateAndRewriteCode": validate.run,
|
||||
}
|
||||
)
|
||||
self.exclusive_tool_commands.append("Engineer2.write_new_code")
|
||||
if self.run_eval:
|
||||
self.tool_execution_map.update(
|
||||
{
|
||||
"Terminal.run_command": self._eval_terminal_run,
|
||||
"RoleZero.ask_human": self._end,
|
||||
"RoleZero.reply_to_human": self._end,
|
||||
}
|
||||
)
|
||||
|
||||
async def eval_terminal_run(self, cmd):
|
||||
"""change command pull/push/commit to end."""
|
||||
if any([cmd_key_word in cmd for cmd_key_word in ["pull", "push", "commit"]]):
|
||||
# The Engineer2 attempts to submit the repository after fixing the bug, thereby reaching the end of the fixing process.
|
||||
# Set self.rc.todo to None to stop the engineer and then will trigger _save_git_diff funcion to save difference.
|
||||
logger.info("Engineer2 use cmd:{cmd}")
|
||||
logger.info("Current test case is finished.")
|
||||
# stop the Engineer2
|
||||
self._set_state(-1)
|
||||
command_output = "Current test case is finished."
|
||||
else:
|
||||
command_output = await self.terminal.run_command(cmd)
|
||||
return command_output
|
||||
|
||||
async def _act(self) -> Message:
|
||||
message = await super()._act()
|
||||
if self.run_eval:
|
||||
await self._save_git_diff()
|
||||
return message
|
||||
|
||||
def _retrieve_experience(self) -> str:
|
||||
|
|
@ -119,44 +100,6 @@ class Engineer2(RoleZero):
|
|||
command_output += await super()._run_special_command(cmd)
|
||||
return command_output
|
||||
|
||||
async def _save_git_diff(self):
|
||||
"""
|
||||
Handles actions based on parsed commands.
|
||||
|
||||
When detecting engineer2 at the final action round, the process will stop immediately.
|
||||
generates a patch using `git diff`.
|
||||
Stores the cleaned patch in `output_diff`. Logs any exceptions.
|
||||
|
||||
This function is specifically added for SWE bench evaluation.
|
||||
"""
|
||||
# If todo switches to None, it indicates that this is the final round of reactions, and the Engineer2 will stop. Use git diff to store any changes made.
|
||||
if not self.rc.todo:
|
||||
from metagpt.tools.swe_agent_commands.swe_agent_utils import extract_patch
|
||||
|
||||
try:
|
||||
logger.info(await self.submit())
|
||||
diff_output = await self.terminal.run_command("git diff --cached")
|
||||
clear_diff = extract_patch(diff_output)
|
||||
logger.info(f"Diff output: \n{clear_diff}")
|
||||
if clear_diff:
|
||||
self.output_diff = clear_diff
|
||||
except Exception as e:
|
||||
logger.error(f"Error during submission: {e}")
|
||||
|
||||
async def submit(self):
|
||||
if "SWE_CMD_WORK_DIR" not in os.environ:
|
||||
os.environ["SWE_CMD_WORK_DIR"] = str(Config.default().workspace.path)
|
||||
if os.path.exists(os.environ["SWE_CMD_WORK_DIR"] + "/test.patch"):
|
||||
await self.terminal.run_command('git apply -R < "$SWE_CMD_WORK_DIR/test.patch"')
|
||||
cmd = """
|
||||
git add -A
|
||||
echo "<<SUBMISSION START||"
|
||||
git diff --cached
|
||||
echo "||SUBMISSION DONE>>"
|
||||
"""
|
||||
diff_output = await self.terminal.run_command(cmd)
|
||||
return diff_output
|
||||
|
||||
async def write_new_code(self, path: str, instruction: str = "") -> str:
|
||||
"""Write a new code file.
|
||||
Args:
|
||||
|
|
@ -180,3 +123,14 @@ class Engineer2(RoleZero):
|
|||
|
||||
# TODO: Consider adding line no to be ready for editing.
|
||||
return f"The file {path} has been successfully created, with content:\n{code}"
|
||||
|
||||
async def _eval_terminal_run(self, cmd):
|
||||
"""change command pull/push/commit to end."""
|
||||
if any([cmd_key_word in cmd for cmd_key_word in ["pull", "push", "commit"]]):
|
||||
# The Engineer2 attempts to submit the repository after fixing the bug, thereby reaching the end of the fixing process.
|
||||
logger.info("Engineer2 use cmd:{cmd}\nCurrent test case is finished.")
|
||||
# Set self.rc.todo to None to stop the engineer.
|
||||
self._set_state(-1)
|
||||
else:
|
||||
command_output = await self.terminal.run_command(cmd)
|
||||
return command_output
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ class RoleZero(Role):
|
|||
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"]
|
||||
# List of exclusive tool commands
|
||||
exclusive_tool_commands: list[str] = [
|
||||
"Editor.edit_file_by_replace",
|
||||
"Editor.insert_content_at_line",
|
||||
|
|
@ -420,11 +421,13 @@ class RoleZero(Role):
|
|||
# Set the exclusive command flag to False.
|
||||
command_flag = [command["command_name"] not in self.exclusive_tool_commands for command in commands]
|
||||
if command_flag.count(False) > 1:
|
||||
# Set the flag of the first exclusive command to True.
|
||||
# Keep only the first exclusive command
|
||||
index_of_first_exclusive = command_flag.index(False)
|
||||
command_flag[index_of_first_exclusive] = True
|
||||
# Select command which flag is True.
|
||||
commands = [commands[index] for index, flag in enumerate(command_flag) if flag is True]
|
||||
commands = [
|
||||
cmd
|
||||
for index, cmd in enumerate(commands)
|
||||
if index == index_of_first_exclusive or cmd["command_name"] not in self.exclusive_tool_commands
|
||||
]
|
||||
command_rsp = "```json\n" + json.dumps(commands, indent=4, ensure_ascii=False) + "\n```json"
|
||||
logger.info(
|
||||
"exclusive command more than one in current command list. change the command list.\n" + command_rsp
|
||||
|
|
@ -436,14 +439,14 @@ class RoleZero(Role):
|
|||
for cmd in commands:
|
||||
output = f"Command {cmd['command_name']} executed"
|
||||
# handle special command first
|
||||
try:
|
||||
if self._is_special_command(cmd):
|
||||
special_command_output = await self._run_special_command(cmd)
|
||||
outputs.append(output + ":" + special_command_output)
|
||||
continue
|
||||
# run command as specified by tool_execute_map
|
||||
if cmd["command_name"] in self.tool_execution_map:
|
||||
tool_obj = self.tool_execution_map[cmd["command_name"]]
|
||||
if self._is_special_command(cmd):
|
||||
special_command_output = await self._run_special_command(cmd)
|
||||
outputs.append(output + ":" + special_command_output)
|
||||
continue
|
||||
# run command as specified by tool_execute_map
|
||||
if cmd["command_name"] in self.tool_execution_map:
|
||||
tool_obj = self.tool_execution_map[cmd["command_name"]]
|
||||
try:
|
||||
if inspect.iscoroutinefunction(tool_obj):
|
||||
tool_output = await tool_obj(**cmd["args"])
|
||||
else:
|
||||
|
|
@ -451,14 +454,14 @@ class RoleZero(Role):
|
|||
if tool_output:
|
||||
output += f": {str(tool_output)}"
|
||||
outputs.append(output)
|
||||
else:
|
||||
outputs.append(f"Command {cmd['command_name']} not found.")
|
||||
break
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
logger.exception(str(e) + tb)
|
||||
outputs.append(output + f": {tb}")
|
||||
break # Stop executing if any command fails
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
logger.exception(str(e) + tb)
|
||||
outputs.append(output + f": {tb}")
|
||||
break # Stop executing if any command fails
|
||||
else:
|
||||
outputs.append(f"Command {cmd['command_name']} not found.")
|
||||
break
|
||||
outputs = "\n\n".join(outputs)
|
||||
|
||||
return outputs
|
||||
|
|
@ -536,11 +539,8 @@ class RoleZero(Role):
|
|||
from metagpt.environment.mgx.mgx_env import MGXEnv # avoid circular import
|
||||
|
||||
if not isinstance(self.rc.env, MGXEnv):
|
||||
rsp = "Not in MGXEnv, command will not be executed."
|
||||
else:
|
||||
rsp = await self.rc.env.reply_to_human(content, sent_from=self)
|
||||
rsp += " If you no longer need to take action, use the command ‘end’ to stop."
|
||||
return rsp
|
||||
return "Not in MGXEnv, command will not be executed."
|
||||
return await self.rc.env.reply_to_human(content, sent_from=self)
|
||||
|
||||
async def _end(self, **kwarg):
|
||||
self._set_state(-1)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue