diff --git a/metagpt/environment/mgx/mgx_env.py b/metagpt/environment/mgx/mgx_env.py index ada638f8e..6c7069b56 100644 --- a/metagpt/environment/mgx/mgx_env.py +++ b/metagpt/environment/mgx/mgx_env.py @@ -33,7 +33,7 @@ class MGXEnv(Environment): if user_defined_recipient: self._publish_message(message) # bypass team leader, team leader only needs to know but not to react - tl.rc.memory.add(message) + tl.rc.memory.add(self.move_message_info_to_content(message)) elif self.message_within_software_sop(message) and not self.has_user_requirement(): # Quick routing for messages within software SOP, bypassing TL. @@ -43,7 +43,7 @@ class MGXEnv(Environment): # Consider replacing this in the future. self._publish_message(message) if self.is_software_task_finished(message): - tl.rc.memory.add(message) + tl.rc.memory.add(self.move_message_info_to_content(message)) tl.finish_current_task() elif publicer == tl.profile: @@ -52,6 +52,7 @@ class MGXEnv(Environment): else: # every regular message goes through team leader + message = self.move_message_info_to_content(message) message.send_to.add(tl.name) tl.put_message(message) @@ -70,7 +71,7 @@ class MGXEnv(Environment): def message_within_software_sop(self, message: Message) -> bool: return message.sent_from in any_to_str_set([ProductManager, Architect, ProjectManager, Engineer, QaEngineer]) - def has_user_requirement(self, k=3) -> bool: + def has_user_requirement(self, k=2) -> bool: """A heuristics to check if there is a recent user intervention""" return any_to_str(UserRequirement) in [msg.cause_by for msg in self.history.get(k)] @@ -79,3 +80,16 @@ class MGXEnv(Environment): return message.cause_by in any_to_str_set([WritePRD, WriteDesign, WriteTasks, SummarizeCode]) or ( message.cause_by == any_to_str(WriteTest) and "Exceeding" in message.content ) + + def move_message_info_to_content(self, message: Message) -> Message: + """Two things here: + 1. Convert role, since role field must be reserved for LLM API, and is limited to, for example, one of ["user", "assistant", "system"] + 2. Add sender and recipient info to content, making TL aware, since LLM API only takes content as input + """ + if message.role in ["system", "user", "assistant"]: + sent_from = message.sent_from + else: + sent_from = message.role + message.role = "assistant" + message.content = f"from {sent_from} to {message.send_to}: {message.content}" + return message diff --git a/metagpt/roles/di/data_interpreter.py b/metagpt/roles/di/data_interpreter.py index 08a6b7f2b..2e1e0a2da 100644 --- a/metagpt/roles/di/data_interpreter.py +++ b/metagpt/roles/di/data_interpreter.py @@ -14,7 +14,7 @@ from metagpt.roles import Role from metagpt.schema import Message, Task, TaskResult from metagpt.strategy.task_type import TaskType from metagpt.tools.tool_recommend import BM25ToolRecommender, ToolRecommender -from metagpt.utils.common import CodeParser, role_raise_decorator +from metagpt.utils.common import CodeParser REACT_THINK_PROMPT = """ # User Requirement @@ -62,7 +62,7 @@ class DataInterpreter(Role): async def _think(self) -> bool: """Useful in 'react' mode. Use LLM to decide whether and what to do next.""" - user_requirement = self.get_memories()[0].content + user_requirement = self.get_memories()[-1].content context = self.working_memory.get() if not context: @@ -86,6 +86,7 @@ class DataInterpreter(Role): return Message(content=code, role="assistant", cause_by=WriteAnalysisCode) async def _plan_and_act(self) -> Message: + self._set_state(0) try: rsp = await super()._plan_and_act() await self.execute_code.terminate() @@ -153,7 +154,7 @@ class DataInterpreter(Role): logger.info(f"ready to {todo.name}") use_reflection = counter > 0 and self.use_reflection # only use reflection after the first trial - user_requirement = self.get_memories()[0].content # issue: 1)多次用户交互时,永远只读用户的第1次request;2)prerequisite没处理 + user_requirement = self.get_memories()[-1].content code = await todo.run( user_requirement=user_requirement, @@ -186,11 +187,3 @@ class DataInterpreter(Role): print(result) data_info = DATA_INFO.format(info=result) self.working_memory.add(Message(content=data_info, role="user", cause_by=CheckData)) - - @role_raise_decorator - async def run(self, with_message=None) -> Message | None: - if not self.rc.todo: - self.set_actions([WriteAnalysisCode]) - self._set_state(0) - - return await super().run(with_message) diff --git a/metagpt/roles/di/team_leader.py b/metagpt/roles/di/team_leader.py index 346535308..9f951799a 100644 --- a/metagpt/roles/di/team_leader.py +++ b/metagpt/roles/di/team_leader.py @@ -70,12 +70,8 @@ class TeamLeader(Role): self._set_state(-1) def get_memory(self, k=10) -> list[Message]: - mem = self.rc.memory.get(k=k) - for m in mem: - if m.role not in ["system", "user", "assistant"]: - m.content = f"from {m.role} to {m.send_to}: {m.content}" - m.role = "assistant" - return mem + """A wrapper with default value""" + return self.rc.memory.get(k=k) async def _think(self) -> bool: """Useful in 'react' mode. Use LLM to decide whether and what to do next.""" @@ -115,7 +111,7 @@ class TeamLeader(Role): """Useful in 'react' mode. Return a Message conforming to Role._act interface.""" self.run_commands(self.commands) self.task_result = TaskResult(result="Success", is_success=True) - msg = Message(content="Commands executed", role="user", send_to=self) + msg = Message(content="Commands executed", send_to="no one") # a dummy message to conform to the interface self.rc.memory.add(msg) return msg diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index b1773b739..9b15ab9a5 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -492,6 +492,8 @@ class Role(SerializationMixin, ContextMixin, BaseModel): await self.planner.process_task_result(task_result) rsp = self.planner.get_useful_memories()[0] # return the completed plan as a response + rsp.role = "assistant" + rsp.sent_from = self._setting self.rc.memory.add(rsp) # add to persistent memory