diff --git a/metagpt/actions/di/write_analysis_code.py b/metagpt/actions/di/write_analysis_code.py index aaf4911f2..4d21b2cec 100644 --- a/metagpt/actions/di/write_analysis_code.py +++ b/metagpt/actions/di/write_analysis_code.py @@ -43,6 +43,7 @@ class WriteAnalysisCode(Action): tool_info: str = "", working_memory: list[Message] = None, use_reflection: bool = False, + memory: list[Message] = None, **kwargs, ) -> str: structual_prompt = STRUCTUAL_PROMPT.format( @@ -52,7 +53,8 @@ class WriteAnalysisCode(Action): ) working_memory = working_memory or [] - context = self.llm.format_msg([Message(content=structual_prompt, role="user")] + working_memory) + memory = memory or [] + context = self.llm.format_msg(memory + [Message(content=structual_prompt, role="user")] + working_memory) # LLM call if use_reflection: diff --git a/metagpt/roles/di/data_analyst.py b/metagpt/roles/di/data_analyst.py index 1debd681c..d1ef92c35 100644 --- a/metagpt/roles/di/data_analyst.py +++ b/metagpt/roles/di/data_analyst.py @@ -1,17 +1,20 @@ from __future__ import annotations import re -from typing import List from pydantic import Field, model_validator from metagpt.actions.di.execute_nb_code import ExecuteNbCode from metagpt.actions.di.write_analysis_code import WriteAnalysisCode from metagpt.logs import logger -from metagpt.prompts.di.data_analyst import EXTRA_INSTRUCTION, TASK_TYPE_DESC, CODE_STATUS, BROWSER_INFO +from metagpt.prompts.di.data_analyst import ( + CODE_STATUS, + EXTRA_INSTRUCTION, + TASK_TYPE_DESC, +) from metagpt.prompts.di.role_zero import ROLE_INSTRUCTION from metagpt.roles.di.role_zero import RoleZero -from metagpt.schema import TaskResult, Message +from metagpt.schema import Message, TaskResult from metagpt.strategy.experience_retriever import ExpRetriever, KeywordExpRetriever from metagpt.tools.tool_recommend import BM25ToolRecommender, ToolRecommender from metagpt.tools.tool_registry import register_tool @@ -21,12 +24,12 @@ from metagpt.tools.tool_registry import register_tool class DataAnalyst(RoleZero): name: str = "David" profile: str = "DataAnalyst" - goal: str = "Take on any data-related tasks, such as data analysis, machine learning, deep learning, web browsing, web scraping, web searching, web deployment, terminal operation, git and github operation, etc." + goal: str = "Take on any data-related tasks, such as data analysis, machine learning, deep learning, web browsing, web scraping, web searching, web deployment, terminal operation, etc." instruction: str = ROLE_INSTRUCTION + EXTRA_INSTRUCTION task_type_desc: str = TASK_TYPE_DESC tools: list[str] = ["Plan", "DataAnalyst", "RoleZero", "Browser"] - custom_tools: list[str] = ["machine learning", "web scraping", "Terminal"] + custom_tools: list[str] = ["web scraping", "Terminal"] custom_tool_recommender: ToolRecommender = None experience_retriever: ExpRetriever = KeywordExpRetriever() @@ -40,39 +43,26 @@ class DataAnalyst(RoleZero): self.custom_tool_recommender = BM25ToolRecommender(tools=self.custom_tools) def _update_tool_execution(self): - self.tool_execution_map.update({ - "DataAnalyst.write_and_exec_code": self.write_and_exec_code, - }) + self.tool_execution_map.update( + { + "DataAnalyst.write_and_exec_code": self.write_and_exec_code, + } + ) - async def parse_browser_actions(self, memory: List[Message]) -> List[Message]: - memory = await super().parse_browser_actions(memory) - browser_actions = [] - for index, msg in enumerate(memory): - if msg.cause_by == "browser": - browser_url = re.search('URL: (.*?)\\n', msg.content).group(1) - pattern = re.compile(r"Command Browser\.(\w+) executed") - browser_actions.append({ - 'command': pattern.match(memory[index - 1].content).group(1), - 'current url': browser_url - }) - if browser_actions: - browser_actions = BROWSER_INFO.format(browser_actions=browser_actions) - self.rc.working_memory.add(Message(content=browser_actions, role="user", cause_by="browser")) - return memory - - async def write_and_exec_code(self, instruction: str = ""): - """Write a code block for current task and execute it in an interactive notebook environment. - - Args: - instruction: The specific task description for which the code needs to be written. - """ + async def write_and_exec_code(self): + """Write a code block for current task step and execute it in an interactive notebook environment.""" counter = 0 success = False await self.execute_code.init_code() # plan info - plan_status = self.planner.get_plan_status() - plan_status = plan_status + f"\nFurther Task Instruction: {instruction}" + try: + plan_status = self.planner.get_plan_status() + plan_status = re.sub( + r"### execution result.*?(?=## Current Task|## Task Guidance)", "", plan_status, flags=re.DOTALL + ) + except AttributeError as e: + return "All tasks have been completed. Please check to end or append task first before writing code." # tool info if self.custom_tool_recommender: @@ -84,8 +74,8 @@ class DataAnalyst(RoleZero): while not success and counter < 3: ### write code ### - logger.info(f"ready to WriteAnalysisCode") - use_reflection = (counter > 0 and self.use_reflection) # only use reflection after the first trial + logger.info("ready to WriteAnalysisCode") + use_reflection = counter > 0 and self.use_reflection # only use reflection after the first trial code = await self.write_code.run( user_requirement=self.planner.plan.goal, @@ -93,6 +83,7 @@ class DataAnalyst(RoleZero): tool_info=tool_info, working_memory=self.rc.working_memory.get(), use_reflection=use_reflection, + memory=self.rc.memory.get(self.memory_k), ) self.rc.working_memory.add(Message(content=code, role="assistant", cause_by=WriteAnalysisCode)) @@ -108,9 +99,9 @@ class DataAnalyst(RoleZero): task_result = TaskResult(code=code, result=result, is_success=success) self.planner.current_task.update_task_result(task_result) - status = 'Success' if success else 'Failed' + status = "Success" if success else "Failed" output = CODE_STATUS.format(code=code, status=status, result=result) if success: - output += 'The code written has been executed successfully.' + output += "The code written has been executed successfully." self.rc.working_memory.clear() return output