书接上篇文章 【AI Agent系列】【MetaGPT】7. 一句话订阅专属信息 - 订阅智能体进阶,实现一个更通用的订阅智能体,我们跑通了一个多智能体的例子。但是相信很多同学跟我一样懵懵懂懂,对多智能体间如何协作,消息如何传递还是不太了解。今天这篇文章,我们就来学习下MetaGPT中多智能体如何协作,深入源码,一层层剖析。
单智能体的例子可看这篇文章:【AI的未来 - AI Agent系列】【MetaGPT】2. 实现自己的第一个Agent
@role_raise_decorator
async def run(self, with_message=None) -> Message | None:
"""Observe, and think and act based on the results of the observation"""
if with_message:
msg = None
if isinstance(with_message, str):
msg = Message(content=with_message)
elif isinstance(with_message, Message):
msg = with_message
elif isinstance(with_message, list):
msg = Message(content="\n".join(with_message))
if not msg.cause_by:
msg.cause_by = UserRequirement
self.put_message(msg)
if not await self._observe():
# If there is no new information, suspend and wait
logger.info(f"{self._setting}: no news. waiting.")
return
rsp = await self.react()
# Reset the next action to be taken.
self.rc.todo = None
# Send the response message to the Environment object to have it relay the message to the subscribers.
self.publish_message(rsp)
return rsp
对照着上面这张图和这段代码,我们来详细看下一个智能体的运行周期。
一个智能体的运行周期都在这个Role
类的run
函数中,它接收一个with_message
参数。
(1) 对 with_message
参数的处理,如果with_message
参数不是None,会执行下面三句指令:
if not msg.cause_by:
msg.cause_by = UserRequirement
self.put_message(msg)
cause_by是什么?
self.put_message(msg)干了什么?
def put_message(self, message):
"""Place the message into the Role object's private message buffer."""
if not message:
return
self.rc.msg_buffer.push(message)
(2)执行 self._observe()
函数,这个函数的作用是观察环境信息,当环境中出现了想要观察的信息,会返回信息的长度,否则长度为0,也就是会进入if not await self._observe()
的条件内,直接return,不触发后续动作的执行。
(3)触发相应动作的执行:rsp = await self.react()
(4) self.rc.todo = None
,这里如果不设置为None会怎样?
(5) 添加执行结果到环境中,self.publish_message(rsp)
源码如下:
async def _observe(self, ignore_memory=False) -> int:
"""Prepare new messages for processing from the message buffer and other sources."""
# Read unprocessed messages from the msg buffer.
news = []
if self.recovered:
news = [self.latest_observed_msg] if self.latest_observed_msg else []
if not news:
news = self.rc.msg_buffer.pop_all()
# Store the read messages in your own memory to prevent duplicate processing.
old_messages = [] if ignore_memory else self.rc.memory.get()
self.rc.memory.add_batch(news)
# Filter out messages of interest.
self.rc.news = [
n for n in news if (n.cause_by in self.rc.watch or self.name in n.send_to) and n not in old_messages
]
self.latest_observed_msg = self.rc.news[-1] if self.rc.news else None # record the latest observed msg
# Design Rules:
# If you need to further categorize Message objects, you can do so using the Message.set_meta function.
# msg_buffer is a receiving buffer, avoid adding message data and operations to msg_buffer.
news_text = [f"{i.role}: {i.content[:20]}..." for i in self.rc.news]
if news_text:
logger.debug(f"{self._setting} observed: {news_text}")
return len(self.rc.news)
这个函数主要是观察环境中有没有需要本role处理的消息,有处理的消息返回消息长度,没有处理的消息,返回0,该role将结束动作周期,不执行实际动作。
(1)recovered状态还没用到,不知道具体作用是啥,看代码是使用上一次的消息内容。以后用到再仔细说。
(2)news = self.rc.msg_buffer.pop_all()
取出msg_buffer中的所有消息。
(3)重点在这一句代码,从news中筛选出本role关注的消息
self.rc.news = [
n for n in news if (n.cause_by in self.rc.watch or self.name in n.send_to) and n not in old_messages
]
self.rc.watch
或者 n.send_to
def _watch(self, actions: Iterable[Type[Action]] | Iterable[Action]):
"""Watch Actions of interest. Role will select Messages caused by these Actions from its personal message
buffer during _observe.
"""
self.rc.watch = {any_to_str(t) for t in actions}
(4)return len(self.rc.news)
返回关注的消息的数量
async def react(self) -> Message:
"""Entry to one of three strategies by which Role reacts to the observed Message"""
if self.rc.react_mode == RoleReactMode.REACT:
rsp = await self._react()
elif self.rc.react_mode == RoleReactMode.BY_ORDER:
rsp = await self._act_by_order()
elif self.rc.react_mode == RoleReactMode.PLAN_AND_ACT:
rsp = await self._plan_and_act()
self._set_state(state=-1) # current reaction is complete, reset state to -1 and todo back to None
return rsp
该函数根据RoleReactMode规划动作的执行顺序:
并在执行完动作后,将状态state置成-1。
def _set_state(self, state: int):
"""Update the current state."""
self.rc.state = state
logger.debug(f"actions={self.actions}, state={state}")
self.rc.todo = self.actions[self.rc.state] if state >= 0 else None
def publish_message(self, msg):
"""If the role belongs to env, then the role's messages will be broadcast to env"""
if not msg:
return
if not self.rc.env:
# If env does not exist, do not publish the message
return
self.rc.env.publish_message(msg)
如果Role设置了env环境,则会向环境中广播这条msg。目前还没用到。
我将上述智能体的运行周期总结成下图,以更清晰的看到里面函数和参数的配合:
理解了单智能体的运行周期后,很容易理解多智能体间的协作。
看下图(图片来自:https://docs.deepwisdom.ai/main/zh/guide/tutorials/multi_agent_101.html):
其实就是:
(1)每一个Role都在不断观察环境中的信息(_observe函数)
(2)当观察到自己想要的信息后,就会触发后续相应的动作
(3)如果没有观察到想要的信息,则会一直循环观察
(4)执行完动作后,会将产生的msg放到环境中(publish_message),供其它Role智能体来使用。
拿我们实现过的例子来看(【AI Agent系列】【MetaGPT】7. 一句话订阅专属信息 - 订阅智能体进阶,实现一个更通用的订阅智能体):
class SubscriptionAssistant(Role):
"""Analyze user subscription requirements."""
name: str = "同学小张的订阅助手"
profile: str = "Subscription Assistant"
goal: str = "analyze user subscription requirements to provide personalized subscription services."
constraints: str = "utilize the same language as the User Requirement"
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self._init_actions([ParseSubRequirement, RunSubscription]) ## 2. 先解析用户需求,然后运行订阅
self._watch([UserRequirement, WriteCrawlerCode]) ## 触发
我们实现了一个SubscriptionAssistant的智能体,它_watch了UserRequirement用户消息和WriteCrawlerCode消息。
运行后,它就会_observe环境:
本文就先写到这里了,欢迎各位批评指正,可+v jasper_8017 一起学习。最后,点个赞呗!
以下文章从最基础的开始,循序渐进的入门MetaGPT智能体开发。