【AI Agent系列】【MetaGPT多智能体学习】3. 开发一个简单的多智能体系统,兼看MetaGPT多智能体运行机制

本系列文章跟随《MetaGPT多智能体课程》(https://github.com/datawhalechina/hugging-multi-agent),深入理解并实践多智能体系统的开发。

本文为该课程的第四章(多智能体开发)的第一篇笔记。主要记录下多智能体的运行机制及跟着教程,实现一个简单的多智能体系统。

系列笔记

  • 【AI Agent系列】【MetaGPT多智能体学习】0. 环境准备 - 升级MetaGPT 0.7.2版本及遇到的坑
  • 【AI Agent系列】【MetaGPT多智能体学习】1. 再理解 AI Agent - 经典案例和热门框架综述
  • 【AI Agent系列】【MetaGPT多智能体学习】2. 重温单智能体开发 - 深入源码,理解单智能体运行框架

文章目录

  • 系列笔记
  • 0. 多智能体间互通的方式 - Enviroment组件
    • 0.1 多智能体间协作方式简介
    • 0.2 Environment组件 - 深入源码
      • 0.2.1 参数介绍
      • 0.2.2 add_roles函数 - 承载角色
      • 0.2.3 publish_message函数 - 接收角色发布的消息 / 环境中的消息被角色得到
      • 0.2.4 run函数 - 运行入口
  • 1. 开发一个简单的多智能体系统
    • 1.1 多智能体需求描述
    • 1.2 代码实现
      • 1.2.1 学生智能体
      • 1.2.2 老师智能体
      • 1.2.3 创建多智能体交流的环境
      • 1.2.4 运行
      • 1.2.5 完整代码
  • 2. 总结

0. 多智能体间互通的方式 - Enviroment组件

0.1 多智能体间协作方式简介

在上次课中,我也曾写过 多智能体运行机制 的笔记(这篇文章:【AI Agent系列】【MetaGPT】【深入源码】智能体的运行周期以及多智能体间如何协作)。其中我自己通过看源码,总结出了MetaGPT多智能体间协作的方式:

(1)每一个Role都在不断观察环境中的信息(_observe函数)
(2)当观察到自己想要的信息后,就会触发后续相应的动作
(3)如果没有观察到想要的信息,则会一直循环观察
(4)执行完动作后,会将产生的msg放到环境中(publish_message),供其它Role智能体来使用。

现在再结合《MetaGPT多智能体》课程的教案,发现还是理解地有些浅了,我只关注了智能体在关注自己想要的信息,观察到了之后就开始行动,而忽略了其背后的基础组件 - Enviroment。

0.2 Environment组件 - 深入源码

MetaGPT中对Environment的定义:环境,承载一批角色,角色可以向环境发布消息,可以被其他角色观察到。短短一句话,总结三个功能:

  • 承载角色
  • 接收角色发布的消息
  • 环境中的消息被角色得到

下面我们分开来细说。

0.2.1 参数介绍

首先来看下 Environment 的参数:

class Environment(ExtEnv):"""环境,承载一批角色,角色可以向环境发布消息,可以被其他角色观察到Environment, hosting a batch of roles, roles can publish messages to the environment, and can be observed by other roles"""model_config = ConfigDict(arbitrary_types_allowed=True)desc: str = Field(default="")  # 环境描述roles: dict[str, SerializeAsAny["Role"]] = Field(default_factory=dict, validate_default=True)member_addrs: Dict["Role", Set] = Field(default_factory=dict, exclude=True)history: str = ""  # For debugcontext: Context = Field(default_factory=Context, exclude=True)
  • desc:环境的描述
  • roles:字典类型,指定当前环境中的角色
  • member_addrs:字典类型,表示当前环境中的角色以及他们对应的状态
  • history:记录环境中发生的消息记录
  • context:当前环境的一些上下文信息

0.2.2 add_roles函数 - 承载角色

def add_roles(self, roles: Iterable["Role"]):"""增加一批在当前环境的角色Add a batch of characters in the current environment"""for role in roles:self.roles[role.profile] = rolefor role in roles:  # setup system message with rolesrole.set_env(self)role.context = self.context

从源码中看,这个函数的功能有两个:

(1)给 Environment 的 self.roles 参数赋值,上面我们已经知道它是一个字典类型,现在看来,它的 key 为 role 的 profile,值为 role 本身。

疑问: role 的 profile 默认是空(profile: str = ""),可以没有值,那如果使用者懒得写profile,这里会不会最终只有一个 Role,导致无法实现多智能体?欢迎讨论交流。

(2)给添加进来的 role 设置环境信息

Role的 set_env 函数源码如下,它又给env设置了member_addrs。有点绕?

def set_env(self, env: "Environment"):"""Set the environment in which the role works. The role can talk to the environment and can also receivemessages by observing."""self.rc.env = envif env:env.set_addresses(self, self.addresses)self.llm.system_prompt = self._get_prefix()self.set_actions(self.actions)  # reset actions to update llm and prefix

通过 add_roles 函数,将 role 和 Environment 关联起来。

0.2.3 publish_message函数 - 接收角色发布的消息 / 环境中的消息被角色得到

Environment 中的 publish_message 函数是 Role 在执行完动作之后,将自身产生的消息发布到环境中调用的接口。Role的调用方式如下:

def publish_message(self, msg):"""If the role belongs to env, then the role's messages will be broadcast to env"""if not msg:returnif not self.rc.env:# If env does not exist, do not publish the messagereturnself.rc.env.publish_message(msg)

通过上面的代码来看,一个Role只能存在于一个环境中?

然后看下 Environment 中源码:

def publish_message(self, message: Message, peekable: bool = True) -> bool:"""Distribute the message to the recipients.In accordance with the Message routing structure design in Chapter 2.2.1 of RFC 116, as already plannedin RFC 113 for the entire system, the routing information in the Message is only responsible forspecifying the message recipient, without concern for where the message recipient is located. How toroute the message to the message recipient is a problem addressed by the transport framework designedin RFC 113."""logger.debug(f"publish_message: {message.dump()}")found = False# According to the routing feature plan in Chapter 2.2.3.2 of RFC 113for role, addrs in self.member_addrs.items():if is_send_to(message, addrs):role.put_message(message)found = Trueif not found:logger.warning(f"Message no recipients: {message.dump()}")self.history += f"\n{message}"  # For debugreturn True

其中重点是这三行代码,检查环境中的所有Role是否订阅了该消息(或该消息是否应该发送给环境中的某个Role),如果订阅了,则调用Role的put_message函数,Role的 put_message函数的作用是将message放到本身的msg_buffer中:

for role, addrs in self.member_addrs.items():if is_send_to(message, addrs):role.put_message(message)

这样就实现了将环境中的某个Role的Message放到环境中,并通知给环境中其它的Role的机制。

0.2.4 run函数 - 运行入口

run函数的源码如下:

async def run(self, k=1):"""处理一次所有信息的运行Process all Role runs at once"""for _ in range(k):futures = []for role in self.roles.values():future = role.run()futures.append(future)await asyncio.gather(*futures)logger.debug(f"is idle: {self.is_idle}")

k = 1, 表示处理一次消息?为什么要有这个值,难道还能处理多次?意义是什么?

该函数其实就是遍历一遍当前环境中的所有Role,然后运行Role的run函数(运行单智能体)。

Role的run里面,就是用该环境观察信息,行动,发布信息到环境。这样多个Role的运行就通过 Environment 串起来了,这些之前已经写过了,不再赘述,见:【AI Agent系列】【MetaGPT】【深入源码】智能体的运行周期以及多智能体间如何协作。

1. 开发一个简单的多智能体系统

复现教程中的demo。

1.1 多智能体需求描述

  • 两个智能体:学生 和 老师
  • 任务及预期流程:学生写诗 —> 老师给改进意见 —> 学生根据意见改进 —> 老师给改进意见 —> … n轮 … —> 结束

1.2 代码实现

1.2.1 学生智能体

学生智能体的主要内容就是根据指定的内容写诗。

因此,首先定义一个写诗的Action:WritePoem

然后,定义学生智能体,指定它的动作就是写诗(self.set_actions([WritePoem])

那么它什么时候开始动作呢?通过 self._watch([UserRequirement, ReviewPoem]) 设置其观察的信息。当它观察到环境中有了 UserRequirement 或者 ReviewPoem 产生的信息之后,开始动作。UserRequirement为用户的输入信息类型。

class WritePoem(Action):name: str = "WritePoem"PROMPT_TEMPLATE: str = """Here is the historical conversation record : {msg} .Write a poem about the subject provided by human, Return only the content of the generated poem with NO other texts.If the teacher provides suggestions about the poem, revise the student's poem based on the suggestions and return.your poem:"""async def run(self, msg: str):prompt = self.PROMPT_TEMPLATE.format(msg = msg)rsp = await self._aask(prompt)return rspclass Student(Role):name: str = "xiaoming"profile: str = "Student"def __init__(self, **kwargs):super().__init__(**kwargs)self.set_actions([WritePoem])self._watch([UserRequirement, ReviewPoem])async def _act(self) -> Message:logger.info(f"{self._setting}: ready to {self.rc.todo}")todo = self.rc.todomsg = self.get_memories()  # 获取所有记忆# logger.info(msg)poem_text = await WritePoem().run(msg)logger.info(f'student : {poem_text}')msg = Message(content=poem_text, role=self.profile,cause_by=type(todo))return msg

1.2.2 老师智能体

老师的智能体的任务是根据学生写的诗,给出修改意见。

因此创建一个Action为ReviewPoem

然后,创建老师的智能体,指定它的动作为Review(self.set_actions([ReviewPoem])

它的触发实际应该是学生写完诗以后,因此,加入self._watch([WritePoem])

class ReviewPoem(Action):name: str = "ReviewPoem"PROMPT_TEMPLATE: str = """Here is the historical conversation record : {msg} .Check student-created poems about the subject provided by human and give your suggestions for revisions. You prefer poems with elegant sentences and retro style.Return only your comments with NO other texts.your comments:"""async def run(self, msg: str):prompt = self.PROMPT_TEMPLATE.format(msg = msg)rsp = await self._aask(prompt)return rspclass Teacher(Role):name: str = "laowang"profile: str = "Teacher"def __init__(self, **kwargs):super().__init__(**kwargs)self.set_actions([ReviewPoem])self._watch([WritePoem])async def _act(self) -> Message:logger.info(f"{self._setting}: ready to {self.rc.todo}")todo = self.rc.todomsg = self.get_memories()  # 获取所有记忆poem_text = await ReviewPoem().run(msg)logger.info(f'teacher : {poem_text}')msg = Message(content=poem_text, role=self.profile,cause_by=type(todo))return msg

1.2.3 创建多智能体交流的环境

前面我们已经知道了多智能体之间的交互需要一个非常重要的组件 - Environment。

因此,创建一个供多智能体交流的环境,通过 add_roles 加入智能体。

然后,当用户输入内容时,将该内容添加到环境中publish_message,从而触发相应智能体开始运行。这里cause_by=UserRequirement代表消息是由用户产生的,学生智能体关心这类消息,观察到之后就开始行动了。

classroom = Environment()classroom.add_roles([Student(), Teacher()])classroom.publish_message(Message(role="Human", content=topic, cause_by=UserRequirement,send_to='' or MESSAGE_ROUTE_TO_ALL),peekable=False,
)

1.2.4 运行

加入相应头文件,指定交互次数,就可以开始跑了。注意交互次数,直接决定了你的程序的效果和你的钱包!

import asynciofrom metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.environment import Environmentfrom metagpt.const import MESSAGE_ROUTE_TO_ALLasync def main(topic: str, n_round=3):while n_round > 0:# self._save()n_round -= 1logger.debug(f"max {n_round=} left.")await classroom.run()return classroom.historyasyncio.run(main(topic='wirte a poem about moon'))

1.2.5 完整代码

import asynciofrom metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.environment import Environmentfrom metagpt.const import MESSAGE_ROUTE_TO_ALLclassroom = Environment()class WritePoem(Action):name: str = "WritePoem"PROMPT_TEMPLATE: str = """Here is the historical conversation record : {msg} .Write a poem about the subject provided by human, Return only the content of the generated poem with NO other texts.If the teacher provides suggestions about the poem, revise the student's poem based on the suggestions and return.your poem:"""async def run(self, msg: str):prompt = self.PROMPT_TEMPLATE.format(msg = msg)rsp = await self._aask(prompt)return rspclass ReviewPoem(Action):name: str = "ReviewPoem"PROMPT_TEMPLATE: str = """Here is the historical conversation record : {msg} .Check student-created poems about the subject provided by human and give your suggestions for revisions. You prefer poems with elegant sentences and retro style.Return only your comments with NO other texts.your comments:"""async def run(self, msg: str):prompt = self.PROMPT_TEMPLATE.format(msg = msg)rsp = await self._aask(prompt)return rspclass Student(Role):name: str = "xiaoming"profile: str = "Student"def __init__(self, **kwargs):super().__init__(**kwargs)self.set_actions([WritePoem])self._watch([UserRequirement, ReviewPoem])async def _act(self) -> Message:logger.info(f"{self._setting}: ready to {self.rc.todo}")todo = self.rc.todomsg = self.get_memories()  # 获取所有记忆# logger.info(msg)poem_text = await WritePoem().run(msg)logger.info(f'student : {poem_text}')msg = Message(content=poem_text, role=self.profile,cause_by=type(todo))return msgclass Teacher(Role):name: str = "laowang"profile: str = "Teacher"def __init__(self, **kwargs):super().__init__(**kwargs)self.set_actions([ReviewPoem])self._watch([WritePoem])async def _act(self) -> Message:logger.info(f"{self._setting}: ready to {self.rc.todo}")todo = self.rc.todomsg = self.get_memories()  # 获取所有记忆poem_text = await ReviewPoem().run(msg)logger.info(f'teacher : {poem_text}')msg = Message(content=poem_text, role=self.profile,cause_by=type(todo))return msgasync def main(topic: str, n_round=3):classroom.add_roles([Student(), Teacher()])classroom.publish_message(Message(role="Human", content=topic, cause_by=UserRequirement,send_to='' or MESSAGE_ROUTE_TO_ALL),peekable=False,)while n_round > 0:# self._save()n_round -= 1logger.debug(f"max {n_round=} left.")await classroom.run()return classroom.historyasyncio.run(main(topic='wirte a poem about moon'))
  • 运行结果

在这里插入图片描述

2. 总结

总结一下整个流程吧,画了个图。

在这里插入图片描述
从最外围环境 classroom开始,用户输入的信息通过 publish_message 发送到所有Role中,通过Role的 put_message 放到自身的 msg_buffer中。

当Role运行run时,_observemsg_buffer中提取信息,然后只过滤自己关心的消息,例如Teacher只过滤出来自WritePoem的,其它消息虽然在msg_buffer中,但不处理。同时,msg_buffer中的消息会存入memory中。

run完即执行完相应动作后,通过publish_message将结果消息发布到环境中,环境的publish_message又将这个消息发送个全部的Role,这时候所有的Role的msg_buffer中都有了这个消息。完成了智能体间信息的一个交互闭环。


站内文章一览

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/714201.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C/C++基础语法

C/C基础语法 文章目录 C/C基础语法头文件经典问题链表链表基础操作 秒数转换闰年斐波那契数列打印n阶菱形曼哈顿距离菱形图案的定义大数计算 输入输出格式化输入输出getline()函数解决cin只读入一个单词的问题fgets读入整行输出字符数组(两种方式puts和printf&#…

day10_oop

今日内容 零、 复习昨日 一、作业 二、继承 三、重写 四、this和super 五、访问修饰符 零、 复习昨日 数组创建的两种方式 new int[3];new int[]{值,值2,…}存值: 数组名[下标] 值 构造方法什么作用?有参无参构造什么区别? 创建对象无参创建出的对象属性是默认值有参创建出的…

【力扣白嫖日记】602.好友申请II:谁有最多的好友

前言 练习sql语句,所有题目来自于力扣(https://leetcode.cn/problemset/database/)的免费数据库练习题。 今日题目: 602.好友申请II:谁有最多的好友 表:RequestAccepted 列名类型requester_idintaccept…

外卖店优先级

题目描述 ”饱了么”外卖系统中维护着N 家外卖店,编号1~N。每家外卖店都有一个优先级,初始时(0时刻)优先级都为0。 每经过1个时间单位,如果外卖店没有订单,则优先级会减少1,最低减到0;而如果外卖店有订单,则…

【AIGC】微笑的秘密花园:红玫瑰与少女的美好相遇

在这个迷人的画面中,我们目睹了一个迷人的时刻,女子则拥有一头柔顺亮丽的秀发,明亮的眼睛如同星河般璀璨,优雅而灵动,她的微笑如春日暖阳,温暖而又迷人。站在红玫瑰花瓣的惊人洪水中。 在一片湛蓝无云的晴…

Liberod的License申请

Liberod的License申请 找到license申请的路径 查找C盘的磁盘序列号 键盘的win+R,输入cmd 输入vol,然后回车 图中的DiskID就是填写你C盘序列号的位置,填写完成后点击Register,几秒钟后会提示你,预计45分钟后会发送到你的邮箱

docker-mysql:5.7安装

1、下载mysql:5.7镜像 [rootlocalhost ~]# docker search mysql (某个XXX镜像名字) [rootlocalhost ~]# docker pull mysql:5.7 按装之前查看一下是否按装过mysql。如果安装过会占用3306端口。 [rootlocalhost ~]# ps -ef | grep mysql 2、安装 # -d:后台运行 #…

C语言基础(五)——结构体与C++引用

七、结构体与C引用 7.1 结构体的定义、初始化、结构体数组 C 语言提供结构体来管理不同类型的数据组合。通过将不同类型的数据组合成一个整体,方便引用 例如,一名学生有学号、姓 名、性别、年龄、地址等属性,如果针对学生的学号、姓名、年龄…

MJ V7 在 V6 Beta 发布后即将推出,即将到来的人工智能 API 访问!

让我们深入了解 MidJourney 的新功能 在发布官方 Beta 之前总结 V6 Alpha 随着 MidJourney V6 Alpha 上周成为默认版本,该团队现在正在努力在过渡到官方 Beta 版本之前进行进一步的改进: 一组 3 个视觉一致性功能 1 — 升级的“风格参考”功能 这将是…

高中数学:分式函数值域的求法

一、求值域的两种基本思路 1、根据函数图像和定义域求出值域。 难点:画出函数图像 2、研究函数单调性和定义域求出值域。 二、函数图像画法 高中所学的分式函数,基本由反比例函数平移得到。 复杂分式函数图像画法的两个要点: a、找垂直、…

mysql 常用命令练习

管理表格从表中查询数据从多个表查询修改数据sql变量类型 管理表格 创建一个包含三列的新表 CREATE TABLE products (id INT,name VARCHAR(255) NOT NULL,price INT DEFAULT 0,PRIMARY KEY(id) // 自增 ); 从数据库中删除表 DROP TABLE product; 向表中添加新列 ALTER TAB…

如何优化阿里云幻兽帕鲁/Palworld的多人联机性能,并避免内存溢出导致的异常退出游戏?

优化阿里云幻兽帕鲁/Palworld的多人联机性能并避免内存溢出导致的异常退出游戏,可以采取以下几种方法: 选择合适的内存配置:由于幻兽帕鲁是一个对内存需求较高的游戏,建议选择至少16GB的内存。对于不同的玩家数量,可以…

【ArcGIS】渔网分割提取栅格图+网格化分析图绘制

ArcGIS按渔网分割提取栅格图并绘制网格化分析图 准备数据操作步骤步骤1:创建渔网(Create Fishnet)步骤2:栅格数据处理步骤3:栅格插值步骤4:数据关联 参考 网格化的目的是让各个数据更加标准化的进行统计。因…

自动化测试系列 —— UI自动化测试!

UI 测试是一种测试类型,也称为用户界面测试,通过该测试,我们检查应用程序的界面是否工作正常或是否存在任何妨碍用户行为且不符合书面规格的 BUG。了解用户将如何在用户和网站之间进行交互以执行 UI 测试至关重要,通过执行 UI 测试…

《AI纪元:幻域探险》

游戏项目名称:《AI纪元:幻域探险》 游戏类型:AI驱动的角色扮演探险游戏(RPG) 背景设定: 《AI纪元:幻域探险》设定在一个名为“幻域”的广阔虚拟世界。这个世界由高度发达的AI技术支持&#xff0…

SpringCloud-同步异步通讯比较

本文详细探讨了同步通讯和异步通讯在信息传递中的区别,以及它们分别带来的优势和不足。通过对支付流程的案例分析,突显了同步通讯可能面临的阻塞和服务依赖问题,而异步通讯通过引入事件驱动模式和消息代理(Broker)成功…

SQL Server 开发环境配置教程(SSMS+SQL Prompt)

背景 记录一下 SQL Server 常用开发软件 体验了各种数据库IDE(DBeaver、Navicat、DataGrip)之后综合下来还是感觉 SSMSSQL Prompt 对于 SQL Server 最好用,所以在此记录一下配置过程 数据库可视化管理工具SSMS 官方下载地址: https://learn.microsoft…

Java基础数据结构之栈

一.什么是栈 栈是一种特殊的线性表,它只允许在固定的一端进行元素的添加与使用,且遵循先进后出的原则。添加取用元素的一端称为栈顶,另一端称为栈底。出栈和入栈都是操作栈顶元素 二.栈的模拟实现 栈的底层是一个数组 这是里面的成员变量以…

智能汽车加速车规级存储应用DS2431P+TR 汽车级EEPROM 存储器IC

DS2431PT&R是一款1024位1-Wire EEPROM芯片,由四页存储区组成,每页256位。数据先被写入一个8字节暂存器中,经校验后复制到EEPROM存储器。该器件的特点是,四页存储区相互独立,可以单独进行写保护或进入EPROM仿真模式…

ssm172旅行社管理系统的设计与实现

** 🍅点赞收藏关注 → 私信领取本源代码、数据库🍅 本人在Java毕业设计领域有多年的经验,陆续会更新更多优质的Java实战项目希望你能有所收获,少走一些弯路。🍅关注我不迷路🍅** 一 、设计说明 1.1 研究…