作业:你画我猜
目录:
- 初次尝试版本
- 改进版本
#解决消息传递机制 - 实测案例
参考资料:
MetaGPT github:https://github.com/geekan/MetaGPT
MetaGPT 官方文档:https://docs.deepwisdom.ai/main/zh/guide/get_started/introduction.html
MetaGPT 官方教程(飞书):https://deepwisdom.feishu.cn/wiki/KhCcweQKmijXi6kDwnicM0qpnEf
Datawhale 合作课程:https://spvrm23ffj.feishu.cn/docx/RZNpd5uXfoebTPxMXCFcWHdMnZb
初次尝试版本
设计逻辑:
Human: 出题
Drawer: 接收Human消息, 根据题目比划
Guesser:接收Drawer消息, 根据比划猜题
Drawer: 接收Guesser消息, 对比答案是否正确。若不正确,继续比划。
Guesser【重复】
加载需要模块:
import asyncio
import platform
from typing import Any
import fire
from metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.team import Team
参考辩论的写法,Action设计:
class Draw(Action):
"""Action: By given an object, describe it without saying the name itself."""
PROMPT_TEMPLATE: str = """
## BACKGROUND
Suppose you are playing the game "You draw, I Guess". You are the actor to 'draw'.
### The question
{question}
## Previous guess
{context}
## YOUR TURN
Now it's your turn, observe previous round. If the latest answer is EXACTLY the same as the question, you win and response "WIN".
Otherwise, describe the question without saying the word itself. Try new aspects other then previous rounds.
DO NOT mention the question itself in your response!
If the question is a combination of multiple single words. Those words are alos not allowed present in your response.
Your will respone in Chinese:
"""
name: str = "draw"
async def run(self, context: str, question:str, topic: str):
prompt = self.PROMPT_TEMPLATE.format(context=context, question=question, topic=topic)
# logger.info(prompt)
rsp = await self._aask(prompt)
return rsp
class Guess(Action):
"""Action: By given a descriptin, guess the word or phase."""
PROMPT_TEMPLATE: str = """
## BACKGROUND
Suppose you are playing the game "You draw, I Guess". You are the actor to 'guess'.
### The topic
{topic}
## The description
{context}
## YOUR TURN
Now it's your turn, observe the description. Guess what it is, wihtout any other words.
Your respone:
"""
name: str = "Guesser"
async def run(self, context: str, topic: str):
prompt = self.PROMPT_TEMPLATE.format(context=context, topic=topic)
# logger.info(prompt)
rsp = await self._aask(prompt)
return rsp
Roles 设计:
class Drawer(Role):
name: str = "Drawer"
profile: str = "Drawer"
topic: str = ""
def __init__(self, **data: Any):
super().__init__(**data)
self.set_actions([Draw])
self._watch([UserRequirement, Guess])
async def _act(self) -> Message:
logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
todo = self.rc.todo
memories = self.get_memories()
context = "\n".join(f"{msg.sent_from}: {msg.content}" for msg in memories)
question = ""
for msg in memories:
if msg.role == "Human":
question = f"{msg.content}"
rsp = await todo.run(context=context, topic=self.topic, question=question)
msg = Message(
content=rsp,
role=self.profile,
cause_by=type(todo),
sent_from=self.name,
)
self.rc.memory.add(msg)
return msg
class Guesser(Role):
name: str = "Guesser"
profile: str = "Guesser"
topic: str = ""
def __init__(self, **data: Any):
super().__init__(**data)
self.set_actions([Guess])
self._watch([Draw])
async def _act(self) -> Message:
logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
todo = self.rc.todo
memories = self.get_memories(k=1)
context = "\n".join(f"{msg.sent_from}: {msg.content}" for msg in memories)
# print(context)
rsp = await todo.run(context=context, topic=self.topic)
msg = Message(
content=rsp,
role=self.profile,
cause_by=type(todo),
sent_from=self.name,
)
self.rc.memory.add(msg)
return msg
最后team封装:
async def drawAguess(idea: str, topic: str, investment: float = 3.0, n_round: int = 5):
"""Run a team of presidents and watch they quarrel. :)"""
team = Team()
team.hire([
Drawer(topic=topic) ,
Guesser(topic=topic)])
team.invest(investment)
team.run_project(idea)
await team.run(n_round=n_round)
idea= "冰球"
topic = "运动"
investment = 3.0
n_round = 5
asyncio.run(drawAguess(idea, topic, investment, n_round))
问题:
- 辩论例子中的
_observe
模块困惑了我一整天,它的写法会导致Drawer接收不到消息,导致程序直接结束。也没有报错,太难排查了,没时间进一步研究了。 - Drawer没法很好区分人类的题目和Guesser的答案,导致有概率出现一上来就说答对了的情况。后续有待研究改进。
改进版本
经过研究,已经能够非常好的完成你画我猜了。
- 环境中的消息通过send_to能够正确传递了
- 增加了描述技巧和猜题技巧的提示词
- 增加了GPT和GLM分别扮演“画”和“猜”角色的能力,看看谁比较厉害
有待改进的地方:
- 一次输入多个词,让智能体自动完成多轮游戏
- 策略1: 增加问题派发员
- 策略2:在策略1的基础上,让派发员自己生成题目
完整测试代码:
import asyncio
import platform
from typing import Any
import fire
from metagpt.actions import Action, UserRequirement
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.team import Team
from metagpt.config2 import Config
gpt4 = Config.default()
glm4 = Config.from_home("zhipu-glm4.yaml")
class Draw(Action):
"""Action: By given an object, describe it without saying the name itself."""
PROMPT_TEMPLATE: str = """
## BACKGROUND
Suppose you are playing the game "You draw, I Guess". You are the actor to 'draw'.
### The question
{question}
## Previous guess
{previous}
## YOUR TURN
Now it's your turn, Describe the question without saying the word itself.
If "Previous guess" have content, try something new other then previous used.
DO NOT mention the question itself in your response!
If the question is a combination of multiple words, ANY SINGLE word arr NOT allowed be present in your response.
技巧:
1. 如果意思正确但是字数不同,请描述正确答案的文字长度,如“三个字”,“四个字”
2. 如果是数字表示方式不同,请描述正确答案的数字类型,如“中文数字”,“阿拉伯数字“
3. 如果给出的答案意思正确但是语言不匹配时,请描述正确答案的语言,如“改用中文”,“答案是英文”
注意,不要强调规则,请直接说出描述本身。
Your will respone in Chinese:
"""
name: str = "draw"
async def run(self, previous: str, question:str, topic: str):
prompt = self.PROMPT_TEMPLATE.format(previous=previous, question=question, topic=topic)
# logger.info(prompt)
rsp = await self._aask(prompt)
return rsp
class Guess(Action):
"""Action: By given a descriptin, guess the word or phase."""
PROMPT_TEMPLATE: str = """
## BACKGROUND
Suppose you are playing the game "You draw, I Guess". You are the actor to 'guess'.
### The topic
{topic}
## The description
{context}
## YOUR TURN
Now it's your turn, observe the description. Guess what it is, wihtout any other characters.
Use the same language as the description.
再次强调,请直接说答案,不要带有任何其他描述;也不要带有任何标点符号
Your respone:
"""
name: str = "Guesser"
async def run(self, context: str, topic: str):
prompt = self.PROMPT_TEMPLATE.format(context=context, topic=topic)
# logger.info(prompt)
rsp = await self._aask(prompt)
return rsp
class Drawer(Role):
name: str = "Drawer"
profile: str = "Drawer"
topic: str = ""
def __init__(self, **data: Any):
super().__init__(**data)
self.set_actions([Draw])
self._watch([UserRequirement, Guess])
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
memories = self.get_memories()
mem0 = memories.pop(0)
if mem0.role == "Human":
question = f"{mem0.content}"
else:
raise ValueError(f"Question not found: {mem0}")
if len(memories) > 0:
mem9 = memories[-1]
latestAnswer = f"{mem9.content}"
previousTry = "\n".join(f"{msg.sent_from}: {msg.content}" for msg in memories)
else:
latestAnswer = ""
previousTry = ""
if latestAnswer == question:
totalTries = len([msg for msg in memories if msg.role == "Guesser"])
rsp = f"Win after {totalTries} tries!"
logger.info(rsp)
next_to = "Human"
else:
rsp = await todo.run(previous=previousTry, topic=self.topic, question=question)
next_to = "Guesser"
msg = Message(
content=rsp,
role=self.profile,
cause_by=type(todo),
sent_from=self.name,
send_to=next_to,
)
self.rc.memory.add(msg)
return msg
class Guesser(Role):
name: str = "Guesser"
profile: str = "Guesser"
topic: str = ""
def __init__(self, **data: Any):
super().__init__(**data)
self.set_actions([Guess])
self._watch([Draw])
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
memories = self.get_memories(k=1)
context = "\n".join(f"{msg.sent_from}: {msg.content}" for msg in memories)
# print(context)
rsp = await todo.run(context=context, topic=self.topic)
msg = Message(
content=rsp,
role=self.profile,
cause_by=type(todo),
sent_from=self.name,
send_to="Drawer"
)
self.rc.memory.add(msg)
return msg
async def drawAguess(idea: str, topic: str, investment: float = 3.0, n_round: int = 5):
"""Run a team of presidents and watch they quarrel. :)"""
team = Team()
team.hire([
Drawer(topic=topic,config=glm4) ,
Guesser(topic=topic,config=gpt4)
])
team.invest(investment)
team.run_project(idea, send_to="Drawer")
await team.run(n_round=n_round)
def main(q: str, topic:str, investment: float = 3.0, n_round: int = 10):
"""
:param topic: Guess topic, such as "Sports/Movie/Star"
:param q(uestion): A word, phase to play. Should be a subject belong to the topic.
: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(drawAguess(q, topic, investment, n_round))
if __name__ == "__main__":
fire.Fire(main)
实测案例
python learn/drawAguess.py --q "深圳" --topic "地名"
INFO | __main__:_act:98 - Drawer(Drawer): to do Draw(Draw)
这是中国一个著名的经济特区,位于广东省,毗邻香港,是一个年轻而充满活力的城市。描述:这是一个由两个汉字组成的城市名,具有改革开放的重要意义。
IINFO | __main__:_act:154 - Guesser(Guesser): to do Guess(Guess)
深圳
INFO | __main__:_act:98 - Drawer(Drawer): to do Draw(Draw)
INFO | __main__:_act:119 - Win after 1 tries!
尝试了很多,大多数都能像这样一次答对,总体还是不错的。
python learn/drawAguess.py --q "百元之恋" --topic "电影"
NFO | __main__:_act:98 - Drawer(Drawer): to do Draw(Draw)
这是一部讲述拳击和人生挑战的日本电影,描述了一个女性从自暴自弃到自我救赎的过程。这部电影的名字由三个字组成,其中包含了货币单位和一种情感。
INFO | __main__:_act:154 - Guesser(Guesser): to do Guess(Guess)
百円の恋
INFO | __main__:_act:98 - Drawer(Drawer): to do Draw(Draw)
这是一部以拳击和自我成长为主题的日本电影,描绘了主角从失意沉沦到重燃斗志的心路历程。电影的名字由三个字组成,反映了金钱与爱情的双重含义,其中“元”字代表了货币单位,“恋”字则表达了情感主题。名字中的第一个字是中文数字。
INFO | __main__:_act:154 - Guesser(Guesser): to do Guess(Guess)
千与千寻
INFO | __main__:_act:98 - Drawer(Drawer): to do Draw(Draw)
这是一部日本电影,讲述了主角通过拳击运动找回自我和爱情的故事。影片的名字由三个汉字组成,表达了金钱与情感的关系,开头的字是中文数字“百”,而结尾的字代表了爱情。名字中既体现了影片中金钱的元素,也暗示了主角在经历挑战后对生活的热爱。
INFO | __main__:_act:154 - Guesser(Guesser): to do Guess(Guess)
百万爱情宣言
INFO | __main__:_act:98 - Drawer(Drawer): to do Draw(Draw)
这是一部描绘女性通过拳击找回生活意义和爱情的日本电影。片名由三个汉字组成,反映了影片中金钱与情感交织的主题,开头的字使用了中文数字,最后一个字代表了爱情。
INFO | __main__:_act:154 - Guesser(Guesser): to do Guess(Guess)
一亿兆恋情
INFO | __main__:_act:98 - Drawer(Drawer): to do Draw(Draw)
这是一部以拳击为背景,讲述女性从低谷中站起,重新找回生活热情和爱情的日本电影。影片的名字由三个汉字组成,开头的字是中文数字,代表着货币的价值,而最后一个字则是对爱的简称。这个名字既体现了电影中金钱的元素,也隐喻了主人公情感的转变。
INFO | __main__:_act:154 - Guesser(Guesser): to do Guess(Guess)
百万爱
这个实例,其实第一轮已经猜出来了,但是因为语言没有匹配上,所以进入了下一轮。但是下一轮中并没有及时给予语言上的提示,导致越来越离谱。