Force reply to human when finishing the current task

This commit is contained in:
黄伟韬 2024-08-16 19:47:40 +08:00
parent 94ec668c3d
commit febc592af7
3 changed files with 83 additions and 25 deletions

View file

@ -218,3 +218,22 @@ QUICK_RESPONSE_SYSTEM_PROMPT = """
{role_info}
However, you MUST respond to the user message by yourself directly, DON'T ask your team members.
"""
REPORT_TO_HUMAN_PROMPT = """
# Current Plan
{plan_status}
Your have just finish a task, Use "RoleZero.reply_to_human" to report what you have done.
The output format is :
```json
[
{{
"command_name": "RoleZero.reply_to_human",
"args": {{
"content": ""
}}
}}
]
```
"""

View file

@ -26,6 +26,7 @@ from metagpt.prompts.di.role_zero import (
QUICK_THINK_PROMPT,
QUICK_THINK_SYSTEM_PROMPT,
REGENERATE_PROMPT,
REPORT_TO_HUMAN_PROMPT,
ROLE_INSTRUCTION,
SYSTEM_PROMPT,
THOUGHT_GUIDANCE,
@ -86,6 +87,8 @@ class RoleZero(Role):
use_fixed_sop: bool = False
requirements_constraints: str = "" # the constraints in user requirements
command_history: list[str] = []
@model_validator(mode="after")
def set_plan_and_tool(self) -> "RoleZero":
# We force using this parameter for DataAnalyst
@ -234,21 +237,57 @@ class RoleZero(Role):
if self.use_fixed_sop:
return await super()._act()
commands, ok = await self._parse_commands()
commands, ok = await self._parse_commands(self.command_rsp)
if not ok:
error_msg = commands
self.rc.memory.add(UserMessage(content=error_msg))
return error_msg
logger.info(f"Commands: \n{commands}")
outputs = await self._run_commands(commands)
logger.info(f"Commands outputs: \n{outputs}")
self.rc.memory.add(UserMessage(content=outputs))
# Report what is done when finishing the task.
current_command_list = [command["command_name"] for command in commands]
self.command_history.extend(current_command_list)
if self.check_whether_report_to_human():
memory = self.rc.memory.get(self.memory_k)
memory = await self.parse_browser_actions(memory)
memory = self.parse_images(memory)
plan_status, _ = self._get_plan_status()
prompt = REPORT_TO_HUMAN_PROMPT.format(plan_status=plan_status)
req = self.llm.format_msg(memory + [UserMessage(content=prompt)])
respond_command = await self.llm.aask(msg=req)
commands, ok = await self._parse_commands(respond_command)
if ok and len(commands) == 1 and commands[0]["command_name"] == "RoleZero.reply_to_human":
cmd = commands[0]
report_result = await self.reply_to_human(cmd["args"])
self.rc.memory.add(AIMessage(content=respond_command))
self.rc.memory.add(UserMessage(content=report_result))
self.command_history.append("RoleZero.reply_to_human")
logger.info(f"Commands outputs: \n{report_result}")
outputs += report_result
return AIMessage(
content=f"I have finished the task, please mark my task as finished. Outputs: {outputs}",
sent_from=self.name,
cause_by=RunCommand,
)
def check_whether_report_to_human(self):
""" "Check whether add reply to human command when finish current task"""
interaction_with_human = ["RoleZero.ask_human", "RoleZero.reply_to_human"]
plan_end_action = ["Plan.finish_current_task", "end"]
flag = 0
for command_name in self.command_history[::-1]:
if command_name in plan_end_action:
flag |= 1
elif command_name in interaction_with_human:
flag |= 2
else:
break
return flag == 1
async def _react(self) -> Message:
# NOTE: Diff 1: Each time landing here means news is observed, set todo to allow news processing in _think
self._set_state(0)
@ -332,7 +371,7 @@ class RoleZero(Role):
command_rsp = await self.llm.aask(regenerate_req)
return command_rsp
async def _parse_commands(self) -> Tuple[List[Dict], bool]:
async def _parse_commands(self, command_rsp) -> Tuple[List[Dict], bool]:
"""Retrieves commands from the Large Language Model (LLM).
This function attempts to retrieve a list of commands from the LLM by
@ -344,20 +383,20 @@ class RoleZero(Role):
- A boolean flag indicating success (True) or failure (False).
"""
try:
commands = CodeParser.parse_code(block=None, lang="json", text=self.command_rsp)
commands = CodeParser.parse_code(block=None, lang="json", text=command_rsp)
if commands.endswith("]") and not commands.startswith("["):
commands = "[" + commands
commands = json.loads(repair_llm_raw_output(output=commands, req_keys=[None], repair_type=RepairType.JSON))
except json.JSONDecodeError as e:
logger.warning(f"Failed to parse JSON for: {self.command_rsp}. Trying to repair...")
logger.warning(f"Failed to parse JSON for: {command_rsp}. Trying to repair...")
commands = await self.llm.aask(
msg=JSON_REPAIR_PROMPT.format(json_data=self.command_rsp, json_decode_error=str(e))
msg=JSON_REPAIR_PROMPT.format(json_data=command_rsp, json_decode_error=str(e))
)
try:
commands = json.loads(CodeParser.parse_code(block=None, lang="json", text=commands))
except json.JSONDecodeError:
# repair escape error of code and math
commands = CodeParser.parse_code(block=None, lang="json", text=self.command_rsp)
commands = CodeParser.parse_code(block=None, lang="json", text=command_rsp)
new_command = repair_escape_error(commands)
commands = json.loads(
repair_llm_raw_output(output=new_command, req_keys=[None], repair_type=RepairType.JSON)
@ -365,8 +404,7 @@ class RoleZero(Role):
except Exception as e:
tb = traceback.format_exc()
print(tb)
error_msg = UserMessage(content=str(e))
self.rc.memory.add(error_msg)
error_msg = str(e)
return error_msg, False
# 为了对LLM不按格式生成进行容错

View file

@ -9,6 +9,7 @@ from metagpt.prompts.di.swe_agent import (
NEXT_STEP_TEMPLATE,
)
from metagpt.roles.di.role_zero import RoleZero
from metagpt.schema import Message
from metagpt.tools.libs.git import git_create_pull
from metagpt.tools.libs.terminal import Bash
@ -32,8 +33,6 @@ class SWEAgent(RoleZero):
async def _think(self) -> bool:
await self._format_instruction()
res = await super()._think()
if self.run_eval:
await self._parse_commands_for_eval()
return res
def _update_tool_execution(self):
@ -55,6 +54,12 @@ class SWEAgent(RoleZero):
bash_state = json.loads(state_output)
self.cmd_prompt_current_state = CURRENT_BASH_STATE.format(**bash_state).strip()
async def _act(self) -> Message:
message = await super()._act()
if self.run_eval:
self._parse_commands_for_eval()
return message
async def _parse_commands_for_eval(self):
"""
Handles actions based on parsed commands.
@ -65,23 +70,19 @@ class SWEAgent(RoleZero):
This function is specifically added for SWE bench evaluation.
"""
# only import when evaluation is needed
from metagpt.tools.swe_agent_commands.swe_agent_utils import extract_patch
if not self.rc.todo:
from metagpt.tools.swe_agent_commands.swe_agent_utils import extract_patch
commands, ok = await self._parse_commands()
if not ok:
return
for cmd in commands:
if "end" != cmd.get("command_name", ""):
return
try:
diff_output = await self.terminal.run("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
# swe agent have been stop. it means 'end' or other command which can stop the swe agent have been executed
try:
diff_output = await self.terminal.run("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}")
except Exception as e:
logger.error(f"Error during submission: {e}")
def _retrieve_experience(self) -> str:
return MINIMAL_EXAMPLE