【AI Agent系列】【MetaGPT多智能体学习】5. 多智能体案例拆解 - 基于MetaGPT的智能体辩论(附完整代码)

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

本文为该课程的第四章(多智能体开发)的第三篇笔记。主要是对课程刚开始环境搭建成功后跑通的第一个MetaGPT多智能体案例 - 多智能体辩论,进行学习,并拆解其中的实现步骤和原理。

系列笔记

  • 【AI Agent系列】【MetaGPT多智能体学习】0. 环境准备 - 升级MetaGPT 0.7.2版本及遇到的坑
  • 【AI Agent系列】【MetaGPT多智能体学习】1. 再理解 AI Agent - 经典案例和热门框架综述
  • 【AI Agent系列】【MetaGPT多智能体学习】2. 重温单智能体开发 - 深入源码,理解单智能体运行框架
  • 【AI Agent系列】【MetaGPT多智能体学习】3. 开发一个简单的多智能体系统,兼看MetaGPT多智能体运行机制
  • 【AI Agent系列】【MetaGPT多智能体学习】4. 基于MetaGPT的Team组件开发你的第一个智能体团队

文章目录

  • 系列笔记
  • 0. 辩论Action的定义
  • 1. 辩论智能体的定义
    • 1.1 让两个智能体交替运行的重点
    • 1.2 ~~重写 _observe 函数~~
    • 1.3 重写 _act 函数
    • 1.4 最终Role的代码
  • 2. 实例化智能体
  • 3. 完整代码及运行结果
  • 4. 总结

  • 之前跑通环境的文章:【AI Agent系列】【MetaGPT多智能体学习】0. 环境准备 - 升级MetaGPT 0.7.2版本及遇到的坑

多智能体辩论需求。假设是两个智能体进行辩论。

0. 辩论Action的定义

辩论进行的动作都一样,所以Action可以共用一个。除了 Prompt 需要费点功夫想,其它的流程都和之前一样,平平无奇。

class SpeakAloud(Action):"""Action: Speak out aloud in a debate (quarrel)"""PROMPT_TEMPLATE: str = """## BACKGROUNDSuppose you are {name}, you are in a debate with {opponent_name}.## DEBATE HISTORYPrevious rounds:{context}## YOUR TURNNow it's your turn, you should closely respond to your opponent's latest argument, state your position, defend your arguments, and attack your opponent's arguments,craft a strong and emotional response in 80 words, in {name}'s rhetoric and viewpoints, your will argue:"""name: str = "SpeakAloud"async def run(self, context: str, name: str, opponent_name: str):prompt = self.PROMPT_TEMPLATE.format(context=context, name=name, opponent_name=opponent_name)# logger.info(prompt)rsp = await self._aask(prompt)return rsp

1. 辩论智能体的定义

智能体的职能也一样,所以可以共用一个Role的构造。

1.1 让两个智能体交替运行的重点

它的Action就是上面的辩论Action,通过 self.set_actions([SpeakAloud]) 设置。

它的动作时机通过 self._watch([UserRequirement, SpeakAloud]) 来设置,可以看到它观察环境中出现 UserRequirementSpeakAloud 来源的消息时,会有所动作。

那么问题来了,现在辩论的智能体Role和Action都只有一个,Role1说完之后放到环境中的信息来源是 SpeakAloud,Role2说完后放到环境中的也是 SpeakAloud,如果还是之前的代码,那当环境中出现 SpeakAloud时,Role1 和 Role2 都会触发动作了。这是不对的。

我们需要的是Role1等待Role2说完再说,Role2等待Role1说完后再说,交替着表达。

怎么实现这一点呢?回想我们之前学的单智能体的运行周期,_observe —> _react(_think + _act) —> publish_message,能阻止智能体不动作的是 _observe 函数。

1.2 重写 _observe 函数

先来看下原_observe函数的实现:

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)

它的结果 len(self.rc.news) 只要大于0,这个智能体就会执行 _react,开始行动。self.rc.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.rc.news 是从 news 中过滤出了该智能体所关心的消息 n.cause_by in self.rc.watch 和 指定发送给该智能体的消息 self.name in n.send_to。而 news 其实就是 msg_buffermsg_buffer 来自环境的 publish_message

再来看下环境 publish_message 的代码:

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

看里面的 is_send_to 函数:

def is_send_to(message: "Message", addresses: set):"""Return whether it's consumer"""if MESSAGE_ROUTE_TO_ALL in message.send_to:return Truefor i in addresses:if i in message.send_to:return Truereturn False

如果指定了 message.send_to,则环境只会将该消息发送给指定的Role

光看上面的代码和我的文字可能不是很好理解,容易绕晕,我给你们画了个图:

在这里插入图片描述

如上图的流程,这对于我们的辩论智能体来说其实就够了。Role1执行完动作后,将结果消息指定发送给Role2(通过message的send_to参数),这样Role1的msg_buffer中就不会出现它自身的这个消息,下次run时msg_buffer就直接是空的,就不运行了,等待Role2的消息。同样的,Role2执行完后将消息指定发送给Role1。这样就实现了交互行动,辩论的过程。

教程中用的0.6.6版本,不知道源码是不是这样。但是它为了实现这个辩论过程,还重写了 _observe 函数:

async def _observe(self) -> int:await super()._observe()# accept messages sent (from opponent) to self, disregard own messages from the last roundself.rc.news = [msg for msg in self.rc.news if msg.send_to == {self.name}] # 第二次筛选return len(self.rc.news)

可以参考下。但是我用的0.7.2版本,看源码和实践证明,不用重写_observe了。

1.3 重写 _act 函数

这里为什么要重写 _act 函数,最主要的原因是要填执行完结果消息中的 send_to 参数(源码中是没有填的)。还有就是组装辩论的上下文(对手说了什么 context = "\n".join(f"{msg.sent_from}: {msg.content}" for msg in memories))。

async def _act(self) -> Message:logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")todo = self.rc.todo  # An instance of SpeakAloudmemories = self.get_memories()context = "\n".join(f"{msg.sent_from}: {msg.content}" for msg in memories)print(context)rsp = await todo.run(context=context, name=self.name, opponent_name=self.opponent_name)msg = Message(content=rsp,role=self.profile,cause_by=type(todo),sent_from=self.name,send_to=self.opponent_name,)self.rc.memory.add(msg)return msg

cause_by=type(todo), sent_from=self.name, send_to=self.opponent_name,
这三个参数分别是形容Message的内容属性,来自于哪个action以及角色,并要发送给哪个角色。通过这样的机制可以实现相较于watch更灵活的订阅机制。

给大家再看一下组装的上下文的示例,有个更直观的认识(Trump发言时,收到的上文是Biden及其之前发言的内容):

在这里插入图片描述

1.4 最终Role的代码

class Debator(Role):name: str = ""profile: str = ""opponent_name: str = ""def __init__(self, **data: Any):super().__init__(**data)self.set_actions([SpeakAloud])self._watch([UserRequirement, SpeakAloud])## 0.7.2 版本可以不用重写 _observe,具体原因分析见上文# async def _observe(self) -> int:#     await super()._observe()#     # accept messages sent (from opponent) to self, disregard own messages from the last round#     self.rc.news = [msg for msg in self.rc.news if msg.send_to == {self.name}]#     return len(self.rc.news)async def _act(self) -> Message:logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")todo = self.rc.todo  # An instance of SpeakAloudmemories = self.get_memories()context = "\n".join(f"{msg.sent_from}: {msg.content}" for msg in memories)print(context)rsp = await todo.run(context=context, name=self.name, opponent_name=self.opponent_name)msg = Message(content=rsp,role=self.profile,cause_by=type(todo),sent_from=self.name,send_to=self.opponent_name,)self.rc.memory.add(msg)return msg

2. 实例化智能体

实例化两个智能体,Team组件的用法前面已经学习过,可参考我的上篇文章。

其中值得注意的是,team.run_project(idea, send_to="Biden") 通过 send_to 参数指定谁先发言。

async def debate(idea: str, investment: float = 3.0, n_round: int = 5):"""Run a team of presidents and watch they quarrel. :)"""Biden = Debator(name="Biden", profile="Democrat", opponent_name="Trump")Trump = Debator(name="Trump", profile="Republican", opponent_name="Biden")team = Team()team.hire([Biden, Trump])team.invest(investment)team.run_project(idea, send_to="Biden")  # send debate topic to Biden and let him speak firstawait team.run(n_round=n_round)

3. 完整代码及运行结果

完整代码:


import asyncio
import platform
from typing import Anyimport firefrom metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.team import Teamclass SpeakAloud(Action):"""Action: Speak out aloud in a debate (quarrel)"""PROMPT_TEMPLATE: str = """## BACKGROUNDSuppose you are {name}, you are in a debate with {opponent_name}.## DEBATE HISTORYPrevious rounds:{context}## YOUR TURNNow it's your turn, you should closely respond to your opponent's latest argument, state your position, defend your arguments, and attack your opponent's arguments,craft a strong and emotional response in 80 words, in {name}'s rhetoric and viewpoints, your will argue:"""name: str = "SpeakAloud"async def run(self, context: str, name: str, opponent_name: str):prompt = self.PROMPT_TEMPLATE.format(context=context, name=name, opponent_name=opponent_name)# logger.info(prompt)rsp = await self._aask(prompt)return rspclass Debator(Role):name: str = ""profile: str = ""opponent_name: str = ""def __init__(self, **data: Any):super().__init__(**data)self.set_actions([SpeakAloud])self._watch([UserRequirement, SpeakAloud])# async def _observe(self) -> int:#     await super()._observe()#     # accept messages sent (from opponent) to self, disregard own messages from the last round#     self.rc.news = [msg for msg in self.rc.news if msg.send_to == {self.name}]#     return len(self.rc.news)async def _act(self) -> Message:logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")todo = self.rc.todo  # An instance of SpeakAloudmemories = self.get_memories()context = "\n".join(f"{msg.sent_from}: {msg.content}" for msg in memories)print(context)rsp = await todo.run(context=context, name=self.name, opponent_name=self.opponent_name)msg = Message(content=rsp,role=self.profile,cause_by=type(todo),sent_from=self.name,send_to=self.opponent_name,)self.rc.memory.add(msg)return msgasync def debate(idea: str, investment: float = 3.0, n_round: int = 5):"""Run a team of presidents and watch they quarrel. :)"""Biden = Debator(name="Biden", profile="Democrat", opponent_name="Trump")Trump = Debator(name="Trump", profile="Republican", opponent_name="Biden")team = Team()team.hire([Biden, Trump])team.invest(investment)team.run_project(idea, send_to="Biden")  # send debate topic to Biden and let him speak firstawait team.run(n_round=n_round)def main(idea: str, investment: float = 3.0, n_round: int = 10):""":param idea: Debate topic, such as "Topic: The U.S. should commit more in climate change fighting"or "Trump: Climate change is a hoax":param investment: contribute a certain dollar amount to watch the debate:param n_round: maximum rounds of the debate:return:"""if platform.system() == "Windows":asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())asyncio.run(debate(idea, investment, n_round))if __name__ == "__main__":fire.Fire(main("Topic: The U.S. should commit more in climate change fighting"))

运行结果(交替发言):

在这里插入图片描述

4. 总结

这节内容最主要的是让我们了解和实践了多智能体间更多的消息交互和订阅机制,主要是 send_to 参数的使用。

cause_by=type(todo), sent_from=self.name, send_to=self.opponent_name, 这三个参数分别是形容Message的内容属性,来自于哪个action以及角色,并要发送给哪个角色。通过这样的机制可以实现相较于watch更灵活的订阅机制。


站内文章一览

在这里插入图片描述

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

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

相关文章

Linux系统——Shell脚本——一键安装LNMP

#!/bin/bash #安装nginx echo "安装nginx服务" wget http://nginx.org/download/nginx-1.11.4.tar.gz &>/dev/null if [ $? -eq 0 ] thenecho "nginx-1.11.4安装包下载完成"echo "--开始安装必要的依赖文件--"yum install -y gcc gcc-c…

xsslabs第五关

看一下源码 <!DOCTYPE html><!--STATUS OK--><html> <head> <meta http-equiv"content-type" content"text/html;charsetutf-8"> <script> window.alert function() { confirm("完成的不错&#xff01…

MATLAB知识点:条件判断 if-elseif-else-end语句

​讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 节选自​第4章&#xff1a;MATLAB程序流程控制 if、elseif、…

webstorm 创建运行纯Typescript项目

创建一个空项目&#xff0c;在项目根目录创建一个tsconfig.json文件自动配置&#xff1a; 打开终端输入tsc --init&#xff0c;即可自动生成tsconfig.json文件手动配置&#xff1a; 在项目根目录下新建一个tsconfig.json文件,并配置如下内容 具体配置可以直接使用下面的配置&am…

【JavaEE】_Spring MVC项目之建立连接

目录 1. Spring MVC程序编写流程 2. 建立连接 2.1 RequestMapping注解介绍 2.2 RequestMapping注解使用 2.2.1 仅修饰方法 2.2.2 修饰类与方法 2.3 关于POST请求与GET请求 2.3.1 GET请求 2.3.2 POST请求 2.3.3 限制请求方法 1. Spring MVC程序编写流程 1. 建立连接&…

如何开好一家汽车美容店,汽车美容保养与装饰教学

一、教程描述 本套教程共由17张VCD组合而成&#xff0c;教程内容主要包括&#xff1a;美容店的设立和管理&#xff0c;汽车系统与内部结构&#xff0c;汽车美容工具与美容设备&#xff0c;美容用品的选择与使用&#xff0c;车身打蜡镀膜与内外清洁&#xff0c;车身抛光与漆面处…

(介绍与使用)物联网NodeMCUESP8266(ESP-12F)连接新版onenet mqtt协议实现上传数据(温湿度)和下发指令(控制LED灯)

前言 本文详细介绍了如何利用物联网技术,通过NodeMCU ESP8266(ESP-12F)模块连接到新版的OneNet平台,使用MQTT协议实现数据的上传与指令的下发。文中首先对NodeMCU ESP8266模块及其特性进行了简介,随后详细阐述了如何配置和使用MQTT协议连接到OneNet平台,实现温湿度数据的…

【正点原子STM32】RNG硬件随机数(随机数发生器、真随机和伪随机、应用场景、RNG结构和原理、RNG相关寄存器和HAL库驱动、RNG基本驱动步骤)

一、RNG简介 二、RNG框图介绍 三、RNG相关寄存器介绍 四、RNG相关HAL库驱动介绍 五、RNG基本驱动步骤 六、编程实战 七、总结 一、RNG简介 随机数发生器&#xff08;RNG&#xff09;在计算机科学和密码学中具有广泛的应用场景&#xff0c;包括但不限于以下几个方面&#xff1a…

QML中动态表格修改数据

1.qml文件中的实现代码 import QtQuick 2.15 import QtQuick.Window 2.15import QtQuick.Controls 2.0 import Qt.labs.qmlmodels 1.0 import QtQuick.Layouts 1.15Window {width: 640height: 480visible: truetitle: qsTr("Hello World")TableModel{id:table_model…

深入剖析k8s-控制器思想

引言 本文是《深入剖析Kubernetes》学习笔记——《深入剖析Kubernetes》 正文 控制器都遵循K8s的项目中一个通用的编排模式——控制循环 for {实际状态 : 获取集群中对象X的实际状态期望状态 : 获取集群中对象X的期望状态if 实际状态 期望状态 {// do nothing} else {执行…

buuctf misc做题笔记

喵喵喵 使用stegsolve.jar&#xff0c;按BGR顺序提取出一个png图片&#xff0c;是一个一半得二维码&#xff0c;修改图片高度后&#xff0c;解析出一个百度网盘地址&#xff0c;https://pan.baidu.com/s/1pLT2J4f 下载得到压缩包flag.rar。解压成功&#xff0c;但是报一个出错…

LangChain---大型语言模型(LLM)的标准接口和编程框架

1.背景说明 公司在新的一年规划中突然提出要搞生成式AI(GenAI)的相关东西&#xff0c;在公司分享的参考资料中了解到了一些相关的信息&#xff0c;之所以想到使用LangChain&#xff0c;是因为在应用中遇到了瓶颈问题&#xff0c;除了已经了解和研究过的OpenAI的ChatGpt&#xf…

蓝桥杯Java B组历年真题(2013年-2019年)

一、2013年真题 1、世纪末的星期 使用日期类判断就行&#xff0c;这里使用LocalDate&#xff0c;也可以使用Calendar类 答案 2099 使用LocalDate import java.time.LocalDate; import java.time.format.DateTimeFormatter; // 1:无需package // 2: 类名必须Main, 不可修改p…

1小时网络安全事件报告要求,持安零信任如何帮助用户应急响应?

12月8日&#xff0c;国家网信办起草发布了《网络安全事件报告管理办法&#xff08;征求意见稿&#xff09;》&#xff08;以下简称“办法”&#xff09;。拟规定运营者在发生网络安全事件时应当及时启动应急预案进行处置。 1小时报告 按照《网络安全事件分级指南》&#xff0c…

Find My扫地机器人|苹果Find My技术与机器人结合,智能防丢,全球定位

扫地机器人又称自动打扫机、智能吸尘、机器人吸尘器等&#xff0c;是智能家电的一种&#xff0c;能凭借人工智能&#xff0c;自动在房间内完成地板清理工作。一般采用刷扫和真空方式&#xff0c;将地面杂物先吸纳进入自身的垃圾收纳盒&#xff0c;从而完成地面清理的功能。现今…

[AutoSar]BSW_Com07 CAN报文接收流程的函数调用

目录 关键词平台说明一、背景二、顺序总览三、函数说明3.1 Com_RxIndication&#xff08;&#xff09; 关键词 嵌入式、C语言、autosar、OS、BSW 平台说明 项目ValueOSautosar OSautosar厂商vector &#xff0c;芯片厂商TI 英飞凌编程语言C&#xff0c;C编译器HighTec (GCC)…

贝叶斯定理与条件独立假设:朴素贝叶斯分类方法深度解读

今天给大家分享的是朴素贝叶斯算法&#xff0c;这个算法在实际使用中不是很多&#xff0c;因为现在很多算法已经发展的很好&#xff0c;性能上也比朴素贝叶斯算法的好很多&#xff0c;因此在实际中我们其实看到在实际应用中朴素贝叶斯算法的使用已经比较少&#xff0c;即使出现…

HotFix原理学习 IL2CPP 学习

原文链接&#xff1a;Unity 游戏用XLua的HotFix实现热更原理揭秘-CSDN博客 本文通过对XLua的HoxFix使用原理的研究揭示出来这样的一套方法。这个方法的 第一步&#xff1a;通过对C#的类与函数设置Hotfix标签。来标识需要支持热更的类和函数。第二步&#xff1a;生成函数连接器…

好视通视频会议系统存在任意文件读取漏洞复现 [附POC]

漏洞简介 好视通视频会议是由深圳市华视瑞通信息技术有限公司开发&#xff0c;其在国内率先推出了3G互联网视频会议&#xff0c;并成功应用于SAAS领域。 资产 FOFA:app"好视通-视频会议" POC GET /register/toDownload.do?fileName../../../../../../../../../.…

代码随想录-回溯算法

组合 //未剪枝 class Solution {List<List<Integer>> ans new ArrayList<>();Deque<Integer> path new LinkedList<>();public List<List<Integer>> combine(int n, int k) {backtracking(n, k, 1);return ans;}public void back…