【AI Agent系列】【MetaGPT多智能体学习】6. 多智能体实战 - 基于MetaGPT实现游戏【你说我猜】(附完整代码)

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

本文为该课程的第四章(多智能体开发)的第四篇笔记。今天我们来完成第四章的作业:

基于 env 或 team 设计一个你的多智能体团队,尝试让他们完成 你画我猜文字版 ,要求其中含有两个agent,其中一个agent负责接收来自用户提供的物体描述并转告另一个agent,另一个agent将猜测用户给出的物体名称,两个agent将不断交互直到另一个给出正确的答案

系列笔记

  • 【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组件开发你的第一个智能体团队
  • 【AI Agent系列】【MetaGPT多智能体学习】5. 多智能体案例拆解 - 基于MetaGPT的智能体辩论(附完整代码)

文章目录

  • 系列笔记
  • 0. 需求分析
  • 1. 写代码 - 初版
    • 1.1 智能体1 - Describer实现
      • 1.1.1 Action定义 - DescribeWord
      • 1.1.2 Role定义 - Describer
    • 1.2 智能体2 - Guesser实现
      • 1.2.1 Action定义 - GuessWord
      • 1.2.2 Role定义 - Gusser
    • 1.3 定义Team,运行及结果
  • 2. 修改代码 - 效果优化
    • 2.1 存在的问题及分析
    • 2.2 Prompt优化
    • 2.3 回答正确后如何立刻停止游戏
    • 2.4 如何输出“游戏失败”的结果
  • 3. 完整代码
  • 4. 拓展 - 与人交互,人来猜词
  • 5. 总结

0. 需求分析

从上面的需求描述来看,你说我猜 游戏需要两个智能体:

  • 智能体1:Describer,用来接收用户提供的词语,并给出描述
  • 智能体2:Guesser,用来接收智能体1的描述,猜词

1. 写代码 - 初版

1.1 智能体1 - Describer实现

智能体1 Describer的任务是根据用户提供的词语,用自己的话描述出来。

1.1.1 Action定义 - DescribeWord

重点是 Prompt,这里我设置的Prompt接收两个参数,第一个参数word为用户输入的词语,也就是答案。第二个参数是Describer智能体的描述历史,因为在实际游戏过程中,描述是不会与前面的描述重复的。另外还设置了每次描述最多20个字,用来限制token的消耗。

class DescribeWord(Action):"""Action: Describe a word in your own language"""PROMPT_TMPL: str = """## 任务你现在在玩一个你画我猜的游戏,你需要用你自己的语言来描述"{word}"## 描述历史之前你的描述历史:{context}## 你必须遵守的限制1. 描述长度不超过20个字2. 描述中不能出现"{word}"中的字3. 描述不能与描述历史中的任何一条描述相同"""name: str = "DescribeWord"async def run(self, context: str, word: str):prompt = self.PROMPT_TMPL.format(context=context, word=word)logger.info(prompt)rsp = await self._aask(prompt)print(rsp)return rsp

1.1.2 Role定义 - Describer

(1)设置其 Action 为 DescribeWord
(2)设置其关注的消息来源为 UserRequirement 和 GuessWord
(3)重点重写了 _act 函数。

因为前面的Prompt中需要历史的描述信息,而描述是其自身发出的,因此历史描述信息的获取为:

if msg.sent_from == self.name:context = "\n".join(f"{msg.content}") # 自己的描述历史

另外,也在这里加了判断是否猜对了词语的逻辑:

elif msg.sent_from == "Gusser" and msg.content.find(self.word) != -1:print("回答正确!")return Message()

当回答对了之后,直接返回。

完整代码如下:

class Describer(Role):name: str = "Describer"profile: str = "Describer"word: str = ""def __init__(self, **data: Any):super().__init__(**data)self.set_actions([DescribeWord])self._watch([UserRequirement, GuessWord])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 DescribeWordmemories = self.get_memories() # 获取全部的记忆context = ""for msg in memories:if msg.sent_from == self.name:context = "\n".join(f"{msg.content}") # 自己的描述历史elif msg.sent_from == "Gusser" and msg.content.find(self.word) != -1:print("回答正确!")return Message()print(context)rsp = await todo.run(context=context, word=self.word)msg = Message(content=rsp,role=self.profile,cause_by=type(todo),sent_from=self.name,)self.rc.memory.add(msg)return msg

1.2 智能体2 - Guesser实现

智能体2 - Guesser,用来接收智能体1的描述,猜词。

1.2.1 Action定义 - GuessWord

与 DescribeWord Action的Prompt类似,猜词的Prompt接收一个context来表示之前它的猜词历史,避免它老重复猜同一个词,陷入死循环。然后一个description来接收Describer的描述语句。

class GuessWord(Action):"""Action: Guess a word from the description"""PROMPT_TMPL: str = """## 背景你现在在玩一个你画我猜的游戏,你的任务是根据给定的描述,猜一个词语。## 猜测历史之前你的猜测历史:{context}## 轮到你了现在轮到你了,你需要根据描述{description}猜测一个词语,并遵循以下限制:### 限制1. 猜测词语不超过5个字2. 猜测词语不能与猜测历史重复3. 只输出猜测的词语,NO other texts"""name: str = "GuessWord"async def run(self, context: str, description: str):prompt = self.PROMPT_TMPL.format(context=context, description=description)logger.info(prompt)rsp = await self._aask(prompt)return rsp

1.2.2 Role定义 - Gusser

(1)设置其 Action 为 GuessWord
(2)设置其关注的消息来源为 DescribeWord
(3)重点重写了 _act 函数。

因为前面的Prompt中需要历史的猜词信息,而猜词是其自身发出的,因此猜词历史信息的获取为:

if msg.sent_from == self.name:context = "\n".join(f"{msg.content}")

Describer的描述信息获取为:

elif msg.sent_from == "Describer":description = "\n".join(f"{msg.content}")

完整代码如下:


class Gusser(Role):name: str = "Gusser"profile: str = "Gusser"def __init__(self, **data: Any):super().__init__(**data)self.set_actions([GuessWord])self._watch([DescribeWord])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 DescribeWordmemories = self.get_memories() # 获取全部的记忆context= ""description = ""for msg in memories:if msg.sent_from == self.name:context = "\n".join(f"{msg.content}")elif msg.sent_from == "Describer":description = "\n".join(f"{msg.content}")print(context)rsp = await todo.run(context=context, description=description)msg = Message(content=rsp,role=self.profile,cause_by=type(todo),sent_from=self.name,)self.rc.memory.add(msg)print(rsp)return msg

1.3 定义Team,运行及结果

async def start_game(idea: str, investment: float = 3.0, n_round: int = 10):team = Team()team.hire([Describer(word=idea),Gusser(), ])team.invest(investment)team.run_project(idea)await team.run(n_round=n_round)def main(idea: str, investment: float = 3.0, n_round: int = 10):if platform.system() == "Windows":asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())asyncio.run(start_game(idea, investment, n_round))if __name__ == "__main__":fire.Fire(main("篮球"))

运行结果如下:

智能体产生描述:
在这里插入图片描述

猜词,检测结果:

在这里插入图片描述
可以看到,运行成功了,也能进行简单的交互。但是还是能看出不少问题的。

下面是进一步优化的过程。

2. 修改代码 - 效果优化

2.1 存在的问题及分析

(1)猜对答案后,它后面还是在循环运行,直到运行完刚开始设置的运行轮数:n_round: int = 10。如上面的运行结果,后面一直在输出“回答正确”。

(2)看下图的运行结果,回答了英文,导致一直认为不是正确答案。并且一直在重复这个词,所以,Prompt还需要优化:

在这里插入图片描述

(3)10轮后结束运行,如果这时候没有猜对答案,没有输出“你失败了”类似的文字。

总结下主要问题:

  • 回答正确后如何立刻停止游戏
  • Prompt需要优化
  • 如何输出“游戏失败”的结果

2.2 Prompt优化

Prompt优化的原则是,有啥问题堵啥问题…

(1)它既然输出了英文词语,那就限制它不让它输出英文单词,只输出中文。
(2)它重复输出了之前的猜词,说明猜词历史的限制没有生效,改变话术各种试(没有好的方法,只有各种试)。

修改之后的 Prompt:

class DescribeWord(Action):"""Action: Describe a word in your own language"""PROMPT_TMPL: str = """## 任务你现在在玩一个你画我猜的游戏,你需要用你自己的语言来描述"{word}"## 描述历史之前你的描述历史:{context}## 你必须遵守的限制1. 描述长度不超过20个字2. 描述中不能出现与"{word}"中的任何一个字相同的字,否则会有严重的惩罚。例如:描述的词为"雨伞",那么生成的描述中不能出现"雨","伞","雨伞"3. 描述不能与描述历史中的任何一条描述相同, 例如:描述历史中已经出现过"一种工具",那么生成的描述就不能再是"一种工具""""
class GuessWord(Action):"""Action: Guess a word from the description"""PROMPT_TMPL: str = """## 任务你现在在玩一个你画我猜的游戏,你需要根据描述"{description}"猜测出一个词语## 猜测历史之前你的猜测历史:{context}### 你必须遵守的限制1. 猜测词语不超过5个字,词语必须是中文2. 猜测词语不能与猜测历史重复3. 只输出猜测的词语,NO other texts"""

优化之后的运行效果,虽然还是有点小问题(描述中出现了重复和出现了答案中的字),但最终效果还行吧… :

在这里插入图片描述

2.3 回答正确后如何立刻停止游戏

await team.run(n_round=n_round) 之后,不运行完 n_round 是不会返回的,而 Team 组件目前也没有接口来设置停止运行。因此想要立刻停止游戏,用Team组件几乎是不可能的(有方法的欢迎指教)。

所以我想了另一种办法:既然无法立刻停止游戏,那就停止两个智能体的行动,让他们一直等待n_round完就行了,就像等待游戏时间结束。

代码修改也很简单:

elif msg.sent_from == "Gusser" and msg.content.find(self.word) != -1:print("回答正确!")return ""

只要在回答正确后,直接return一个空字符串就行。为什么这样就可以?看源码:

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

在运行完动作_act后,往环境中放结果消息,如果为空,就不忘环境中放消息了。这样Guesser也就接收不到 Describer 的消息,也就不动作了。剩下的 n_round 就是在那空转了。

看下运行效果:

在这里插入图片描述
可以看到,只输出了一次“回答正确”,之后就没有其余打印了,直到程序结束。

2.4 如何输出“游戏失败”的结果

如果 n_round 运行完之后,还没有猜对结果,就要宣告游戏失败了。怎么获取这个结果呢?

程序运行结束,只能是在这里返回:await team.run(n_round=n_round)

我们将它的返回值打出来看下是什么:

result = await team.run(n_round=n_round)
print(result)

打印结果如下:

在这里插入图片描述
可以看到它的返回结果就是所有的对话历史。那么判断游戏是否失败就好说了,有很多种方法,例如直接比较用户输入的词语是否与这个结果中的最后一行相同:

result = result.split(':')[-1].strip(' ')
if (result.find(idea) != -1):print("恭喜你,猜对了!")
else:print("很遗憾,你猜错了!")

运行效果:

在这里插入图片描述

3. 完整代码


import asyncio
from typing import Any
import platformimport 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 DescribeWord(Action):"""Action: Describe a word in your own language"""PROMPT_TMPL: str = """## 任务你现在在玩一个你画我猜的游戏,你需要用你自己的语言来描述"{word}"## 描述历史之前你的描述历史:{context}## 你必须遵守的限制1. 描述长度不超过20个字2. 描述中不能出现与"{word}"中的任何一个字相同的字,否则会有严重的惩罚。例如:描述的词为"雨伞",那么生成的描述中不能出现"雨","伞","雨伞"3. 描述不能与描述历史中的任何一条描述相同, 例如:描述历史中已经出现过"一种工具",那么生成的描述就不能再是"一种工具""""name: str = "DescribeWord"async def run(self, context: str, word: str):prompt = self.PROMPT_TMPL.format(context=context, word=word)logger.info(prompt)rsp = await self._aask(prompt)# print(rsp)return rspclass GuessWord(Action):"""Action: Guess a word from the description"""PROMPT_TMPL: str = """## 任务你现在在玩一个你画我猜的游戏,你需要根据描述"{description}"猜测出一个词语## 猜测历史之前你的猜测历史:{context}### 你必须遵守的限制1. 猜测词语不超过5个字,词语必须是中文2. 猜测词语不能与猜测历史重复3. 只输出猜测的词语,NO other texts"""name: str = "GuessWord"async def run(self, context: str, description: str):prompt = self.PROMPT_TMPL.format(context=context, description=description)logger.info(prompt)rsp = await self._aask(prompt)return rspclass Describer(Role):name: str = "Describer"profile: str = "Describer"word: str = ""def __init__(self, **data: Any):super().__init__(**data)self.set_actions([DescribeWord])self._watch([UserRequirement, GuessWord])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 DescribeWordmemories = self.get_memories() # 获取全部的记忆context = ""for msg in memories:if msg.sent_from == self.name:context += f"{msg.content}\n" # 自己的描述历史elif msg.sent_from == "Gusser" and msg.content.find(self.word) != -1:print("回答正确!")return ""# print(context)rsp = await todo.run(context=context, word=self.word)msg = Message(content=rsp,role=self.profile,cause_by=type(todo),sent_from=self.name,)self.rc.memory.add(msg)return msgclass Gusser(Role):name: str = "Gusser"profile: str = "Gusser"def __init__(self, **data: Any):super().__init__(**data)self.set_actions([GuessWord])self._watch([DescribeWord])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 DescribeWordmemories = self.get_memories() # 获取全部的记忆context= ""description = ""for msg in memories:if msg.sent_from == self.name:context += f"{msg.content}\n"elif msg.sent_from == "Describer":description += f"{msg.content}\n"print(context)rsp = await todo.run(context=context, description=description)msg = Message(content=rsp,role=self.profile,cause_by=type(todo),sent_from=self.name,)self.rc.memory.add(msg)# print(rsp)return msgasync def start_game(idea: str, investment: float = 3.0, n_round: int = 10):team = Team()team.hire([Describer(word=idea),Gusser(), ])team.invest(investment)team.run_project(idea)result = await team.run(n_round=n_round)result = result.split(':')[-1].strip(' ')if (result.find(idea) != -1):print("恭喜你,猜对了!")else:print("很遗憾,你猜错了!")def main(idea: str, investment: float = 3.0, n_round: int = 3):if platform.system() == "Windows":asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())asyncio.run(start_game(idea, investment, n_round))if __name__ == "__main__":fire.Fire(main("打篮球运行"))

4. 拓展 - 与人交互,人来猜词

可以做下拓展,将猜词的Role换成你自己,你自己来猜词,与智能体进行交互。这实现起来比较简单。

代表人的智能体,只需要在实例化智能体时,将 Role 的 is_human 属性置为 true 即可:

team.hire([Describer(word=idea),Gusser(is_human=True),  # is_human=True 代表这个角色是人类,需要你的输入])

运行效果:

在这里插入图片描述
还可以引入另一个智能体来自动出词语。大家可以思考下应该怎么实现。

5. 总结

本文我们利用MetaGPT的Team组件实现了一个“你说我猜”的游戏。因为游戏比较简单,所以整体逻辑也比较简单。重点在于Prompt优化比较费劲,还有就是要注意何时结束游戏等细节。最后,也向大家展示了一下如何让人参与到游戏中。


站内文章一览

在这里插入图片描述

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

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

相关文章

java垃圾回收

垃圾回收 一个对象如果不再使用,需要手动释放,否则就会出现内存泄漏。我们称这种释放对象的过程为垃圾回收,而需要程序员编写代码进行回收的方式为手动回收。 内存泄漏指的是不再使用的对象在系统中未被回收,内存泄漏的积累可能…

【ArcGIS Pro二次开发】(83):ProWindow和WPF的一些技巧

在ArcGIS Pro二次开发中,SDK提供了一种工具界面【ArcGIS Pro ProWindow】。 关于ProWindow的用法,之前写过一篇基础的教程: 【ArcGIS Pro二次开发】(13):ProWindow的用法_arcgispro二次开发教程-CSDN博客 主要是对几个常用控件…

【嵌入式实践】【芝麻】【设计篇-2】从0到1给电动车添加指纹锁:项目可行性分析

0. 前言 该项目是基于stm32F103和指纹模块做了一个通过指纹锁控制电动车的小工具。支持添加指纹、删除指纹,电动车进入P档等待时计时,计时超过5min则自动锁车,计时过程中按刹车可中断P档状态,同时中断锁车计时。改项目我称之为“芝…

EMR StarRocks实战——猿辅导的OLAP演进之路

目录 一、数据需求产生 二、OLAP选型 2.1 需求 2.2 调研 2.3 对比 三、StarRocks的优势 四、业务场景和技术方案 4.1 整体的数据架构 4.2 BI自助/报表/多维分析 4.3 实时事件分析 4.5 直播教室引擎性能监控 4.4 B端业务后台—斑马 4.5 学校端数据产品—飞象星球 4…

Ajax(黑马学习笔记)

Ajax介绍 Ajax概述 我们前端页面中的数据,如下图所示的表格中的学生信息,应该来自于后台,那么我们的后台和前端是互不影响的2个程序,那么我们前端应该如何从后台获取数据呢?因为是2个程序,所以必须涉及到…

【ACM】—蓝桥杯大一暑期集训Day3

🚀欢迎来到本文🚀 🍉个人简介:陈童学哦,目前学习C/C、算法、Python、Java等方向,一个正在慢慢前行的普通人。 🏀系列专栏:陈童学的日记 💡其他专栏:CSTL&…

langchain学习笔记(九)

RunnableBranch: Dynamically route logic based on input | 🦜️🔗 Langchain 基于输入的动态路由逻辑,通过上一步的输出选择下一步操作,允许创建非确定性链。路由保证路由间的结构和连贯。 有以下两种方法执行路由 1、通过Ru…

Vue开发实例(一)Vue环境搭建第一个项目

Vue环境搭建&第一个项目 一、环境搭建二、安装Vue脚手架三、创建Vue项目 一、环境搭建 下载方式从官网下载:http://nodejs.cn/download/ 建议下载v12.16.0版本以上的,因为版本低无法创建Vue的脚手架 检验是否安装成功 配置环境变量 新增NODE_HOME&…

win11系统中nginx简单的代理配置

一.背景 为了公司安排的师带徒任务。 操作系统版本:win11家庭版 nginx版本:1.24.0 二.配置代理 之前文章已经说明了nginx简单的安装,要看阅读这个文章哈。web服务器nginx下载及在win11的安装-CSDN博客 1.配置需求识别 前端服务nginx(80…

【探索AI】十七 深度学习之第3周:卷积神经网络(CNN)(一)-CNN的基本原理与结构

第3周:卷积神经网络(CNN) CNN的基本原理与结构 常见的卷积层、池化层与全连接层 LeNet、AlexNet等经典CNN模型 实践:使用CNN进行图像分类任务 CNN的基本原理与结构 引言与背景介绍 卷积神经网络(CNN)是…

双周回顾#007 - 前端与后端

前端的问题不是难,而是它面对最终用户。只要用户的喜好和口味发生变化,前端就必须跟上。 这导致前端不得不快速变化,因为用户的口味正在越来越快地改变。 后端不需要面对最终用户,需要解决的都是一些经典的计算机科学问题&#…

什么是Vue指令?请列举一些常见的Vue指令以及它们的用法

Vue.js 是一款流行的前端框架,它的指令(Directives)是 Vue.js 提供的一种特殊属性,用于在模板中对 DOM 元素进行直接操作。指令通常是以 v- 开头的特殊属性,用于响应式地将数据绑定到 DOM 元素上。 在 Vue 中&#xf…

C语言初阶—函数(函数的声明和定义,函数递归)

函数声明: 1.告诉编译器有一个函数叫什么,参数是什么,返回类型是什么,但是具体是不是存在,函数声明决定不了。 2.函数的声明一般出现在函数使用之前,要满足先声明后使用。 3.函数的声明一般要放在头文件中。…

Launch学习

参考博客: (1) 史上最全的launch的解析来啦,木有之一欧 1 ROS工作空间简介 2 元功能包 src目录下可以包含多个功能包,假设需要使用机器人导航模块,但是这个模块中包含着地图、定位、路径规划等不同的功能包,它们的逻…

agent内存马

搭建一个简单的Servlet项目 ServletDemo package com.naihe;import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;…

vue2+若依框架plus交互 路由介绍

本周及寒假 参加了校企合作的工程过程管理,和学长学姐一起写项目,之前学了vue也没有应用,然后对框架很多组件的用法不太了解,前期耽误了一些时间。 框架模块 首先是框架模块的介绍 api存了一些系统管理及发送请求的方法 例如p…

【python】`assert`断言语句

assert是一个断言语句,用于在代码中检查某个条件是否为真。 如果条件为假,将触发AssertionError 异常,从而指示存在错误。

在您的下一个项目中选择 Golang 和 Node.js 之间的抉择

作为一名软件开发者,我总是在寻找构建应用程序的最快、最高效的工具。在速度和处理复杂任务方面,我认为 Golang 和 Node.js 是顶尖技术。两者在性能方面都享有极高的声誉。但哪一个更快——Golang 还是 Node?我决定深入一些硬核基准测试&…

java-ssm-jsp-宠物护理预定系统

java-ssm-jsp-宠物护理预定系统 获取源码——》公主号:计算机专业毕设大全

物联网与智慧城市:融合创新,塑造未来城市生活新图景

一、引言 在科技飞速发展的今天,物联网与智慧城市的融合创新已成为推动城市发展的重要力量。物联网技术通过连接万物,实现信息的智能感知、传输和处理,为智慧城市的构建提供了无限可能。智慧城市则运用物联网等先进技术,实现城市…