From 606f1b8f9cf60629f23c3ea8459a0a95c5b7103b Mon Sep 17 00:00:00 2001 From: yzlin Date: Tue, 30 Jan 2024 16:40:13 +0800 Subject: [PATCH] accept goal during run; move more logic from role to planner --- metagpt/plan/planner.py | 64 ++++++++++++++------- metagpt/roles/role.py | 28 +++------ tests/metagpt/roles/run_code_interpreter.py | 3 +- 3 files changed, 50 insertions(+), 45 deletions(-) diff --git a/metagpt/plan/planner.py b/metagpt/plan/planner.py index 87492e455..fea5f0f8d 100644 --- a/metagpt/plan/planner.py +++ b/metagpt/plan/planner.py @@ -44,6 +44,48 @@ class Planner(BaseModel): def current_task_id(self): return self.plan.current_task_id + async def update_plan(self, goal: str = "", max_tasks: int = 3, max_retries: int = 3): + if goal: + self.plan = Plan(goal=goal) + + plan_confirmed = False + while not plan_confirmed: + context = self.get_useful_memories() + rsp = await WritePlan().run(context, max_tasks=max_tasks, use_tools=self.use_tools) + self.working_memory.add(Message(content=rsp, role="assistant", cause_by=WritePlan)) + + # precheck plan before asking reviews + is_plan_valid, error = precheck_update_plan_from_rsp(rsp, self.plan) + if not is_plan_valid and max_retries > 0: + error_msg = f"The generated plan is not valid with error: {error}, try regenerating, remember to generate either the whole plan or the single changed task only" + logger.warning(error_msg) + self.working_memory.add(Message(content=error_msg, role="assistant", cause_by=WritePlan)) + max_retries -= 1 + continue + + _, plan_confirmed = await self.ask_review(trigger=ReviewConst.TASK_REVIEW_TRIGGER) + + update_plan_from_rsp(rsp=rsp, current_plan=self.plan) + + self.working_memory.clear() + + async def process_task_result(self, task_result: TaskResult): + # ask for acceptance, users can other refuse and change tasks in the plan + review, task_result_confirmed = await self.ask_review(task_result) + + if task_result_confirmed: + # tick off this task and record progress + await self.confirm_task(self.current_task, task_result, 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 + pass # simply pass, not confirming the result + + else: + # update plan according to user's feedback and to take on changed tasks + await self.update_plan() + async def ask_review( self, task_result: TaskResult = None, auto_run: bool = None, trigger: str = ReviewConst.TASK_REVIEW_TRIGGER ): @@ -74,28 +116,6 @@ class Planner(BaseModel): self.working_memory.add(Message(content=review, role="user", cause_by=AskReview)) await self.update_plan(review) - async def update_plan(self, max_tasks: int = 3, max_retries: int = 3): - plan_confirmed = False - while not plan_confirmed: - context = self.get_useful_memories() - rsp = await WritePlan().run(context, max_tasks=max_tasks, use_tools=self.use_tools) - self.working_memory.add(Message(content=rsp, role="assistant", cause_by=WritePlan)) - - # precheck plan before asking reviews - is_plan_valid, error = precheck_update_plan_from_rsp(rsp, self.plan) - if not is_plan_valid and max_retries > 0: - error_msg = f"The generated plan is not valid with error: {error}, try regenerating, remember to generate either the whole plan or the single changed task only" - logger.warning(error_msg) - self.working_memory.add(Message(content=error_msg, role="assistant", cause_by=WritePlan)) - max_retries -= 1 - continue - - _, plan_confirmed = await self.ask_review(trigger=ReviewConst.TASK_REVIEW_TRIGGER) - - update_plan_from_rsp(rsp=rsp, current_plan=self.plan) - - self.working_memory.clear() - def get_useful_memories(self, task_exclude_field=None) -> list[Message]: """find useful memories only to reduce context length and improve performance""" # TODO dataset description , code steps diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index 21e48a127..d176bbac3 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -452,10 +452,11 @@ class Role(SerializationMixin, is_polymorphic_base=True): async def _plan_and_act(self) -> Message: """first plan, then execute an action sequence, i.e. _think (of a plan) -> _act -> _act -> ... Use llm to come up with the plan dynamically.""" - ### Common Procedure in both single- and multi-agent setting ### - # create initial plan and update until confirmation - await self.planner.update_plan() + # create initial plan and update it until confirmation + goal = self.rc.memory.get()[-1].content # retreive latest user requirement + await self.planner.update_plan(goal=goal) + # take on tasks until all finished while self.planner.current_task: task = self.planner.current_task logger.info(f"ready to take on task {task}") @@ -463,25 +464,10 @@ class Role(SerializationMixin, is_polymorphic_base=True): # take on current task task_result = await self._act_on_task(task) - # ask for acceptance, users can other refuse and change tasks in the plan - review, task_result_confirmed = await self.planner.ask_review(task_result) + # process the result, such as reviewing, confirming, plan updating + await self.planner.process_task_result(task_result) - if task_result_confirmed: - # tick off this task and record progress - await self.planner.confirm_task(task, task_result, 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 - continue - - else: - # update plan according to user's feedback and to take on changed tasks - await self.planner.update_plan() - - completed_plan_memory = self.planner.get_useful_memories() # completed plan as a outcome - - rsp = completed_plan_memory[0] + rsp = self.planner.get_useful_memories()[0] # return the completed plan as a response self.rc.memory.add(rsp) # add to persistent memory diff --git a/tests/metagpt/roles/run_code_interpreter.py b/tests/metagpt/roles/run_code_interpreter.py index e41507256..379194534 100644 --- a/tests/metagpt/roles/run_code_interpreter.py +++ b/tests/metagpt/roles/run_code_interpreter.py @@ -23,10 +23,9 @@ async def run_code_interpreter(role_class, requirement, auto_run, use_tools, use """ if role_class == "ci": - role = CodeInterpreter(goal=requirement, auto_run=auto_run, use_tools=use_tools, tools=tools) + role = CodeInterpreter(auto_run=auto_run, use_tools=use_tools, tools=tools) else: role = MLEngineer( - goal=requirement, auto_run=auto_run, use_tools=use_tools, use_code_steps=use_code_steps,