Merge remote-tracking branch 'origin/dev' into dev_tool_selection

# Conflicts:
#	metagpt/actions/write_analysis_code.py
#	metagpt/roles/ml_engineer.py
This commit is contained in:
lidanyang 2023-12-14 10:06:39 +08:00
commit 3a3b31badb
3 changed files with 48 additions and 52 deletions

View file

@ -26,7 +26,7 @@ from metagpt.utils.common import create_func_config, remove_comments
class BaseWriteAnalysisCode(Action):
DEFAULT_SYSTEM_MSG = """You are Code Interpreter, a world-class programmer that can complete any goal by executing code. Strictly follow the plan and generate code step by step. Each step of the code will be executed on the user's machine, and the user will provide the code execution results to you.""" # prompt reference: https://github.com/KillianLucas/open-interpreter/blob/v0.1.4/interpreter/system_message.txt
REUSE_CODE_INSTRUCTION = """ATTENTION: DONT include codes from previous tasks in your current code block, include new codes only, DONT repeat codes!"""
def process_msg(self, prompt: Union[str, List[Dict], Message, List[Message]], system_msg: str = None):
default_system_msg = system_msg or self.DEFAULT_SYSTEM_MSG
# 全部转成list
@ -45,23 +45,23 @@ class BaseWriteAnalysisCode(Action):
messages.append(p.to_dict())
elif isinstance(p.content, dict) and "code" in p.content:
messages.append(p.content["code"])
# 添加默认的提示词
if (
default_system_msg not in messages[0]["content"]
and messages[0]["role"] != "system"
default_system_msg not in messages[0]["content"]
and messages[0]["role"] != "system"
):
messages.insert(0, {"role": "system", "content": default_system_msg})
elif (
default_system_msg not in messages[0]["content"]
and messages[0]["role"] == "system"
default_system_msg not in messages[0]["content"]
and messages[0]["role"] == "system"
):
messages[0] = {
"role": "system",
"content": messages[0]["content"] + default_system_msg,
}
return messages
async def run(
self, context: List[Message], plan: Plan = None, code_steps: str = ""
) -> str:
@ -79,19 +79,18 @@ class BaseWriteAnalysisCode(Action):
class WriteCodeByGenerate(BaseWriteAnalysisCode):
"""Write code fully by generation"""
def __init__(self, name: str = "", context=None, llm=None) -> str:
super().__init__(name, context, llm)
async def run(
self,
context: [List[Message]],
plan: Plan = None,
code_steps: str = "",
system_msg: str = None,
**kwargs,
self,
context: [List[Message]],
plan: Plan = None,
system_msg: str = None,
**kwargs,
) -> str:
context.append(Message(content=self.REUSE_CODE_INSTRUCTION, role="user"))
# context.append(Message(content=self.REUSE_CODE_INSTRUCTION, role="user"))
prompt = self.process_msg(context, system_msg)
code_content = await self.llm.aask_code(prompt, **kwargs)
return code_content["code"]
@ -99,15 +98,15 @@ class WriteCodeByGenerate(BaseWriteAnalysisCode):
class WriteCodeWithTools(BaseWriteAnalysisCode):
"""Write code with help of local available tools. Choose tools first, then generate code to use the tools"""
def __init__(self, name: str = "", context=None, llm=None, schema_path=None):
super().__init__(name, context, llm)
self.schema_path = schema_path
self.available_tools = {}
if self.schema_path is not None:
self._load_tools(schema_path)
def _load_tools(self, schema_path):
"""Load tools from yaml file"""
yml_files = schema_path.glob("*.yml")
@ -115,7 +114,7 @@ class WriteCodeWithTools(BaseWriteAnalysisCode):
module = yml_file.stem
with open(yml_file, "r", encoding="utf-8") as f:
self.available_tools[module] = yaml.safe_load(f)
def _parse_recommend_tools(self, module: str, recommend_tools: list) -> dict:
"""
Parses and validates a list of recommended tools, and retrieves their schema from registry.
@ -132,15 +131,15 @@ class WriteCodeWithTools(BaseWriteAnalysisCode):
for tool in recommend_tools:
if tool in available_tools:
valid_tools.append(tool)
tool_catalog = {tool: self.available_tools[module][tool] for tool in valid_tools}
return tool_catalog
async def _tool_recommendation(
self,
task: str,
code_steps: str,
available_tools: dict,
self,
task: str,
code_steps: str,
available_tools: dict,
) -> list:
"""
Recommend tools for the specified task.
@ -162,26 +161,26 @@ class WriteCodeWithTools(BaseWriteAnalysisCode):
rsp = await self.llm.aask_code(prompt, **tool_config)
recommend_tools = rsp["recommend_tools"]
return recommend_tools
async def run(
self,
context: List[Message],
plan: Plan = None,
code_steps: str = "",
column_info: str = "",
**kwargs,
self,
context: List[Message],
plan: Plan = None,
column_info: str = "",
**kwargs,
) -> Tuple[List[Message], str]:
task_type = plan.current_task.task_type
available_tools = self.available_tools.get(task_type, {})
special_prompt = ML_SPECIFIC_PROMPT.get(task_type, "")
code_steps = plan.current_task.code_steps
finished_tasks = plan.get_finished_tasks()
code_context = [remove_comments(task.code) for task in finished_tasks]
code_context = "\n\n".join(code_context)
if len(available_tools) > 0:
available_tools = {k: v["description"] for k, v in available_tools.items()}
recommend_tools = await self._tool_recommendation(
plan.current_task.instruction,
code_steps,
@ -189,9 +188,9 @@ class WriteCodeWithTools(BaseWriteAnalysisCode):
)
tool_catalog = self._parse_recommend_tools(task_type, recommend_tools)
logger.info(f"Recommended tools: \n{recommend_tools}")
module_name = ML_MODULE_MAP[task_type]
prompt = TOOL_USAGE_PROMPT.format(
user_requirement=plan.goal,
history_code=code_context,
@ -202,8 +201,8 @@ class WriteCodeWithTools(BaseWriteAnalysisCode):
module_name=module_name,
tool_catalog=tool_catalog,
)
else:
prompt = GENERATE_CODE_PROMPT.format(
user_requirement=plan.goal,
@ -213,7 +212,7 @@ class WriteCodeWithTools(BaseWriteAnalysisCode):
special_prompt=special_prompt,
code_steps=code_steps,
)
tool_config = create_func_config(CODE_GENERATOR_WITH_TOOLS)
rsp = await self.llm.aask_code(prompt, **tool_config)
context = [Message(content=prompt, role="user")]

View file

@ -56,7 +56,6 @@ class MLEngineer(Role):
# memory for working on each task, discarded each time a task is done
self.working_memory = Memory()
async def _plan_and_act(self):
### Actions in a multi-agent multi-turn setting ###
@ -81,7 +80,7 @@ class MLEngineer(Role):
logger.info(f"ready to take on task {task}")
# take on current task
code, result, success, code_steps = await self._write_and_exec_code()
code, result, success = await self._write_and_exec_code()
# ask for acceptance, users can other refuse and change tasks in the plan
review, task_result_confirmed = await self._ask_review(trigger=ReviewConst.TASK_REVIEW_TRIGGER)
@ -95,7 +94,6 @@ class MLEngineer(Role):
# tick off this task and record progress
task.code = code
task.result = result
task.code_steps = code_steps
self.plan.finish_current_task()
self.working_memory.clear()
@ -109,7 +107,7 @@ class MLEngineer(Role):
if confirmed_and_more:
self.working_memory.add(Message(content=review, role="user", cause_by=AskReview))
await self._update_plan(review)
elif "redo" in review:
# Ask the Role to redo this task with help of review feedback,
# useful when the code run is successful but the procedure or result is not what we want
@ -142,7 +140,7 @@ class MLEngineer(Role):
return success, code
async def _write_and_exec_code(self, max_retry: int = 3):
code_steps = (
self.plan.current_task.code_steps = (
await WriteCodeSteps().run(self.plan)
if self.use_code_steps
else ""
@ -175,7 +173,7 @@ class MLEngineer(Role):
elif not self.use_tools or self.plan.current_task.task_type == "other":
logger.info("Write code with pure generation")
code = await WriteCodeByGenerate().run(
context=context, plan=self.plan, code_steps=code_steps, temperature=0.0
context=context, plan=self.plan, temperature=0.0
)
debug_context = [self.get_useful_memories(task_exclude_field={'result', 'code_steps'})[0]]
cause_by = WriteCodeByGenerate
@ -185,7 +183,6 @@ class MLEngineer(Role):
tool_context, code = await WriteCodeWithTools(schema_path=schema_path).run(
context=context,
plan=self.plan,
code_steps=code_steps,
column_info=self.data_desc.get("column_info", ""),
)
debug_context = tool_context
@ -212,7 +209,7 @@ class MLEngineer(Role):
if ReviewConst.CHANGE_WORD[0] in review:
counter = 0 # redo the task again with help of human suggestions
return code, result, success, code_steps
return code, result, success
async def _ask_review(self, auto_run: bool = None, trigger: str = ReviewConst.TASK_REVIEW_TRIGGER):
auto_run = auto_run or self.auto_run
@ -249,7 +246,7 @@ class MLEngineer(Role):
update_plan_from_rsp(rsp, self.plan)
self.working_memory.clear()
async def _reflect(self):
context = self.get_memories()
context = "\n".join([str(msg) for msg in context])
@ -280,7 +277,7 @@ class MLEngineer(Role):
context_msg = [Message(content=context, role="user")]
return context_msg + self.get_working_memories()
def get_working_memories(self) -> List[Message]:
return self.working_memory.get()

View file

@ -78,10 +78,10 @@ class Task(BaseModel):
dependent_task_ids: list[str] = [] # Tasks prerequisite to this Task
instruction: str = ""
task_type: str = ""
code_steps: str = ""
code: str = ""
result: str = ""
is_finished: bool = False
code_steps: str = ""
class Plan(BaseModel):