diff --git a/metagpt/actions/write_analysis_code.py b/metagpt/actions/write_analysis_code.py index 3e91f4b14..2c45281f9 100644 --- a/metagpt/actions/write_analysis_code.py +++ b/metagpt/actions/write_analysis_code.py @@ -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")] diff --git a/metagpt/roles/ml_engineer.py b/metagpt/roles/ml_engineer.py index 8ab3ac981..8f06a541c 100644 --- a/metagpt/roles/ml_engineer.py +++ b/metagpt/roles/ml_engineer.py @@ -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() diff --git a/metagpt/schema.py b/metagpt/schema.py index f91922535..8eb7e31ca 100644 --- a/metagpt/schema.py +++ b/metagpt/schema.py @@ -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):