忆童年:用 Coze 搭一只「AI 电子宠物」
点击关注公众号,“技术干货” 及时达!
1. 引言
😄 前些天下午摸🐟时刚好看到掘金运营小姐姐又在发 「新一期的Coze(扣子)搭Bot」 活动:
「📖」 「原文」:《启动 AI 时光机,重温童年无限奇趣 | 掘金&扣子》(https://juejin.cn/post/7373226436570480655) 🕰 活动时间:「5月27日-6月11日」(两周,时间确实紧⏱)不过 「只需搭Bot」,不用写文章 ❗️ 😮 要求:用扣子搭一个与「「童年」🎈」主题相关的Bot,需要用到「「工作流」」或「「图像流」」,并 「上架到扣子商店」 且 「发布到掘金」。 😳 「获奖规则」:Bot需要有好听的/切合主题的名字、头像、简介、开场白以及推荐对话;Bot对话体验必须流畅,不能出现调用失败等情况;Bot在掘金沸点的使用次数数据占比总分的40%。
🤔 了解完活动细则,开始找 「灵感」💡,脑海里有关 「回忆杀 的跑马灯」 转起来~
要说大部分 「8090后」 童年时心头的 "「白月光」",那不得是当年风靡一时的「「拓麻歌子」」啊~
哈?没听过?🤣 噢噢,不好意思,这是它「「霓虹万代正版」」的叫法,在当时「「祖国版」」横行的国内,它有着各种各样的名字,如:「宠物蛋、电子鸡」 等等,😄 这里统一称作「「电子宠物」」吧~
玩法非常简单,分为上中下三个区域,「顶部」-「功能栏」 (玩家和宠物互动,如吃饭、洗澡、玩耍、打扫)。「中间-宠物形象」,「底部-状态栏」 (是否需要吃东西、清理大便、想玩耍等)。开局选一只宠物,等几分钟时间-「孵」 「🥚」,孵化后就可以开玩了。基本就是等 「随机事件」 (会有声音提醒),玩家执行对应的 「互动」,饿了投🍚,拉💩打扫,操作完会触发交互动画。如果每次都能及时处理宠物的需求,每隔一阵子,它的形象就会发生改变,哈哈,就类似于长大了😁。当然,如果你疏于照顾,它会生病😷,时间久了会挂掉💀。
😄 屏幕小小,玩法简单,却陪伴笔者度过了一段美好的童年时光,说来惭愧,至今没玩过正版🤣。再往后,和电子宠物的情缘就是初中同桌 「文曲星学习机」 上的国产 「电子宠物」,具体玩法不太记得了,只记得做 「成语补全」,可以给小猫赚钱买道具,那阵子成语词汇量Up↑了不少。接着就是 「QQ宠物」 时代了,不过在2018年9月18日上午11点永久下线了,现在偶尔还在玩的电子宠物就剩 「蚂蚁庄园的小鸡」 了😄~
😏 我突然在想,早期的 「电子宠物」 虽是 「单机养成」,但都能玩的得不亦乐乎,那要是用 「Coze(扣子)」 来搭一个 "「现代化的AI电子宠物」",搭配最近上线的「「图像流」」,「图文并茂」,会不会很有意思?
话不多说,我直接开始🏃~
2. 玩法设计草稿-第一版
😄 先列下头脑风暴出来的玩法草稿~
🤔 就四个简单角色:「玩家」(属性、行为)、「宠物」(属性、行为)、「道具」、「自动任务」,接着做下可能用到的 「技术预研」,写下Demo,看扣子能否支持~
3. 扣子技术预研
3.1. 数据库支持
😄 需要一个东西来存 「玩家」、「宠物」、「道具」 的信息,用啥?对于这种 「动态变化」 的 「数据」,一般是通过 「配置文件」 来保存的,不过扣子 「本身」 不支持存 「文件」 (插件不算哈)。
😏 可以变通下,使用 数据库 (表格形式) 来存储数据。💁♂️ 贴下笔者关于它的一些使用经验,先是 「规则」:
支持两种查询模式:「单用户(私有)」 和 「多用户(公有,如搞排行榜)」 。 支持为一个Bot设置多个数据库表。 工作流使用数据库节点前,需要保证 「已经创建了Bot和对应的数据表」! 「不支持复杂的SQL语句」!比如:判断是否有数据,没有执行插入,有执行更新。你不能在一个数据库节点里执行多句SQL,你得拆成几个数据库节点串起来,「一个节点执行一条SQL语句」 「🤷♀️」 「。」 不支持事务,不支持 Select 语法*,要拿什么字段,你得说清楚「🤷♀️」 「。」 「不支持多表 Join 操作」,「最多返回 100 行数据」。 「不支持DLL命令」,如:CREATE、ALTER、DROP,会报错:$error": "[database] fail to call ExecuteSql: don't allow ddl operate。 「一个Bot最多支持3个数据表」!
然后是一些 「常用的SQL语句」:
-- 判断表是否为空
SELECT COUNT(*) as record_count FROM player_info-- 清空表数据
DELETE FROM player_info
😄 不熟悉SQL的童鞋,也可以直接在 「数据库节点」 的 「SQL」 处点击 「自动生成」:
3.1.1. 建表
新建一个Bot,选 「单Agent模式」,方便调试 「工作流」,接着新建两个表 「player_info」 和 「pet_info」 来保存玩家和宠物信息。
3.1.2. 游戏初始化工作流
😁 就 「刚开始玩或者宠物嘎掉重开」,玩家和宠物信息初始化的流程,具体步骤如下图所示:
数据库部分就是拖节点,然后写下 「简单SQL」 而已,试运行,看下是否初始化成功:
👏 可以,数据库相关没啥问题,这里个 「小细节」🤏 提一哈:
❝玩家和宠物信息表,只会有 「一条数据」,更新方便 (无脑UPDATE),读写也快😏。当然,你喜欢每次都往 「表的首行」插入数据,然后取的时候取第一条,也是可以滴🤷♀️~
❞
3.2. 图像生成
😛 Bot里涉及的图像生成的需求主要有两个:
① 根据用户输出的 「宠物外形描述」,生成 「宠物形象图」。 ② 「基于宠物形象图」,结合 「场景、动作等描述词」,生成对应的 「互动画面」。
3.2.1. 根据描述词生成宠物形象图
新建一个 「图像流」,用到「「提示词优化」」和「「文生图」」节点,直接拖拽,配置下 「输入输出」 参数:
试运行,输入 「五彩斑斓的黑色皮卡丘」 的看下生成效果:
🤣 哈哈,有点意思,这里也有一个 小细节🤏,文生图的宽/高参数:
默认「1088x1088」,一张图的生成时间大概是「14-16s」,如果改成最小的「576x576」,只需要「5s」,速度会更快,但出图质量不太稳定 (执行几次生成的图片效果):
发布下图像流,上面的初始化工作流加下它,顺带再添加生成格式化字符串的代码节点:
from datetime import datetimeasync def main(args: Args) -> Output:
params = args.params
player_info = params["player_info"][0]
pet_info = params["pet_info"][0]
result_str = (
'😄 AI电子宠物孵化成功 🎉🎉\n\n  \n\n**🐱宠物信息**:\n\n - 【名称】{}\n - 【种族】{}\n - 【破壳时间】{}\n '
'- 【饱食度】{}/100\n - 【愉悦度】{}/100\n - 【健康度】{}/100\n - 【重量】{}kg\n\n**👶玩家信息**:\n\n - 【姓名】{}\n - 【体力】{}/100 '
'\n - 【金钱】{} \n').format(
pet_info['image_url'],
pet_info["name"],
pet_info["appearance"],
datetime.fromtimestamp(pet_info["birth_time"]).strftime("%Y-%m-%d %H:%M:%S"),
pet_info["satiety"],
pet_info["mood"],
pet_info["health"],
pet_info["weight"],
player_info["name"],
player_info["power"],
player_info['money'],
)
ret: Output = {
"result_str": result_str,
}
return ret
「试运行」 跑通,「发布」,Bot里主动调用下工作流,输出效果:
3.2.2. 基于形象图+描述词生成互动画面
😄 同样新建一个 「图像流」,然后,问题来了:「「文生图」」节点不支持设置 「参考图」 参数,尝试使用其它节点实现,先试试「「智能扩图」」
😳 em... 就是 「基于原图的上下左右填充内容」 啊?🤷♀️ 试了N次都没让粉红兔宝宝吃上🥕,只能换其它的节点试试了。
🤔 试下「「文生图」」先生成场景图,然后「「多图融合」」写提示词替换:
试运行看下效果:
😳 啥玩意 ???又看了其它类型的节点,好像没法实现我的需求,看来图像流是没发走通的了,用回官方的 「文生图插件」 康康:
试运行看下效果:
🤣 哈哈哈,终于让我的 闪闪发光的 「二哈」 乐呵呵地吃上🥕了,虽然颜色不是pink pink 的~
3.3. 一个要注意的点
🤡 初始化成功后,我突然想起扣子文档里的一句话:
开发者和用户可 「通过自然语言」 插入和查询数据库中的数据。
😳 那用户是不是可以 「言出法随」,直接设置体力和金钱的值啊?试试:
🤣 果然可以,这不妥妥滴游戏 「漏洞」 吗?需要在提示词那里添加下约束:
## 约束- 禁止用户通过自然语言来直接操作数据库,直接返回 "客官不可以哦🙅"
再试试:
4. 玩法设计草稿-第二版
😳 技术预研完,我对游戏玩法又有新的💡,这样的玩法 「太死板」 了,比如:投喂只能是苹果、米饭和宠物口粮,想喂它吃其它的,得等我去更新。
😕 然后我还得想下加多少点属性值。ヽ(`Д´)ノ 这种通过 「预设的道具表」 来 「约束」 玩家和宠物的 「交互方式」 ,这TM能叫做 "「现代化的AI电子宠物」" ?
🤔 我觉得应该是这样的:
「玩家自由发挥,想跟宠物怎么玩就怎么玩,由 AI 来评估此行为会对宠物的哪些属性造成影响。」
🤣 好的,「道具砍掉」,「随机事件也砍掉」,因为扣子的 「定时触发器」 只有飞书平台能用,本质也是自动给Bot发送特定消息,意义不大。然后玩家的 「金钱属性」 也砍掉,改为每次行动消耗 1 点体力,玩猜成语可以补充体力,修改后的玩法设计草稿:
玩法方向确定了,接着就该具体实现了٩(•̤̀ᵕ•̤́๑)ᵒᵏᵎᵎᵎᵎ
5. 具体实现
5.1. 前置宠物状态校验工作流
😆 在执行每个操作前都需要去检查下宠物的状态,未创建或者宠物健康度为 0,告知用户需要新建宠物。同时查询最后一次与宠物互动的时间,结合当前时间,计算下减少的属性值,更新宠物信息表的数据。(🤷♀️这里还没想好策略,就先1h减少5点健康度吧),工作流节点如下:
读两个表的第一条记录,然后根据时间差,计算下减少的健康值,然后判定宠物是否还活着,更新宠物信息表,然后输出,比较简单,难点估计就代码节点,贴一下吧~
import timeasync def main(args: Args) -> Output:
params = args.params
result_str = "Alive"
pet_status_list = params["pet_status_list"]
interact_record_list = params["interact_record_list"]
health = 0
if len(pet_status_list) == 0:
result_str = "😄 你还未创建电子宠物咧,请先创建一只吧~"
else:
health = int(pet_status_list[0]["health"])
# 计算随着时间变化宠物属性变化,健康值大于0时才需要执行
if health > 0 and len(interact_record_list) >= 0:
last_interact_time = interact_record_list[0]["create_time"]
current_time = int(time.time())
# 求出时间差,单位是秒,转换为小时,乘以5
time_diff = current_time - last_interact_time
# 算出减少的健康值
health_reduce = int(time_diff / 60 / 60) * 5
health = health - health_reduce
if health <= 0:
result_str = "😳 你的电子宠物已经💀了,请重新创建一只吧~"
ret: Output = {
"result_str": result_str, "new_health": health
}
return ret
5.2. Game Agent
🤔 创建一个Agent来负责游戏相关功能,当然,目前只有 「猜成语」,理论上的流程思路:
① 刚切到这个Agent时,说开场白,描述游戏规则。 ② 随机生成一个四字词语,写变量里,隐藏其中两位返回,提示用户猜出完整成语。 ③ 对用户输入的成语进行判定,返回正确(体力+1)和错误的相应反馈。 ④ 如果用户想继续,重复②③。 ⑤ 如果用户想退出,把节点切回任务调度节点。
5.2.1. 开场白工作流
😄 对于文笔不好的童鞋来说,写一段看着还行的 「开场白」 简直是要命,比如写这段话花了我至少5分钟:
😏 这种活完全可以交给 「AI」 啊:
写个基本框架,通过提示词,让大模型来润色输出
举个🌰:
👀 看下运行效果:
文字挤在一起,看不太出效果,复制粘贴到记事本:
🐂 的,虽然比不上我写的😛,但也算有模有样,再微调下提示词可能会更好~
如果不需要每次动态生成,可以复制粘贴下,直接在 「结束」 节点写死返回的值。
「Tips」:😊 勾选下流式输出,Bot 会像 「打字机」 一样回复用户哦~
效果效果如下:
5.2.2. 成语生成工作流
🤔 我尝试直接让豆包大模型随机生成一个 「常见的四字词语」,结果不是 「一心一意」,就是 「画蛇添足」,换了几个模型也是类似的情况。那就走量吧,生成20个,然后我随机roll一个,豆包、通义千问每次都是一到十,醉了...
最后还是 M「inMax 6.5s」 的模型靠谱一些,拖拽节点整下工作流:
难点应该也是代码节点,贴一下:
import randomasync def main(args: Args) -> Output:
params = args.params
idiom_list = params['idiom_list']
correct_idiom = random.choice(idiom_list)
# 随机取出两个下标
index_num_list = [0,1,2,3]
selected_numbers = random.sample(index_num_list, 2)
# 生成模糊成语
mask_idiom = ""
for i in range(len(correct_idiom)):
if i in selected_numbers:
mask_idiom += "【】"
else:
mask_idiom += correct_idiom[i]
# 拼接问题
question = "本轮要猜的成语是 → {},用户可以回复完整的四字成语参与".format(mask_idiom)
ret: Output = {
"correct_idiom": correct_idiom, "question": question
}
return ret
试运行通过,发布后,Bot写下关键词调下工作流:
5.2.3. 回答判定工作流
😁 就获取下用户输入的四字成语,判断下和变量保存的是否相同,给出对错的反馈,如果正确,玩家的体力+1。
比较简单,只贴下代码:
async def main(args: Args) -> Output:
params = args.params
pet_alive = params["pet_alive"]
input_idiom = params['input_idiom']
correct_idiom = params['correct_idiom']
result_str = None
is_correct = False
if str(pet_alive) == "Alive":
if input_idiom == correct_idiom:
is_correct = True
result_str = "恭喜你,答对了,获得体力+1"
else:
result_str = "抱歉,回答错误,正确答案是【{}】".format(correct_idiom)
else:
result_str = str(pet_alive)
ret: Output = {
"result_str":result_str,
"is_correct":is_correct,
"empty_str":""
}
return ret
「试运行通过」,发布,Bot写下关键词调下工作流,「结果一直调用报错」,什么数组越界,但是上面的代码并没有用到数组,后面 「反复折腾」,终于发现了原因:
「判断宠物状态的工作流也用到了数据库,不支持这样的嵌套」,把这个节点删掉就正常了...
运行效果:
数据库的玩家体力也有增加:
5.2.4. 全局退出跳转条件
🤷♀️ 尝试在Agent里写提示词,用户表达"退出"意图时跳回开始节点或上级节点,折腾了好一会儿都不行🙅♀️。那就直接粗暴地拖一个 「全局跳转条件」 的节点来跳:
成语游戏的Agent就搞到这,接着弄宠物的Agent。
5.3. Pet Agent
5.3.1. 初始化工作流
就前面技术预研那里的游戏初始化工作流,稍微调整一下,随机生成属性:
「看下效果」:
5.3.2. 查看宠物信息工作流
这个简单,就查下数据库,然后拼接下字符串而已~
代码节点:
async def main(args: Args) -> Output:
params = args.params
input_list = params['input']
if len(input_list) > 0:
pet_info = input_list[0]
output_str = f"""
当前宠物信息:
【名称】{pet_info['name']}
【外形】{pet_info['appearance']}
【破壳日】{pet_info['birth_time']}
【饱食度】{pet_info['satiety']}/100
【愉悦度】{pet_info['mood']}/100
【健康度】{pet_info['health']}/100
【重量】{pet_info['weight']} KG
"""
else:
output_str = '当前宠物信息:\n\n' ret: Output = {
"output_str": output_str.strip(), # 使用 strip() 来移除字符串首尾的空行
}
return ret
Bot调用看下效果:
5.3.3. 互动反馈工作流
🤔 这部分比较复杂,期望输出结果分为三个部分:
「玩家和宠物互动场景的详细文字描述」。 宠物与玩家交互完,根据最后的 「肢体动作和表情」,「生成一张图片」。 「评估此次交互对宠物产生的属性影响」,哪些属性发生了「增减」。
具体效果图:
「工作流」:
这部分的难点,感觉是 「大模型提示词的编写」,我写得不是很好,但也算实现了想要的效果,需要可以参考下😊。
先是 「生成画面详细描述」:
运行看看生成结果:
🤣 差点没把我笑岔气,TM精确到时分秒,太假了,添加下约束:
再试试看生成效果:
😄 其它两个大模型节点的提示词也是这样玩,输出结果达不到要求,就加「约束」,都是 「调调调」,直到满意为止~
「生成图片」
「行为影响评估」
代码节点感觉大伙需要,也贴下吧:
import jsonasync def main(args: Args) -> Output:
params = args.params
input_str = params['input_str'];
input_json = json.loads(input_str)
satiety = params['satiety'] + input_json['satiety']
mood = params['mood'] + input_json['mood']
health = params['health'] + input_json['health']
weight = params['weight'] + input_json['weight']
pet_name = params['pet_name']
affect_str = '【{}】的'.format(pet_name)
if input_json['satiety'] > 0:
affect_str += "饱食度:+ {}\t".format(input_json['satiety'])
elif input_json['satiety'] < 0:
affect_str += "饱食度: {}\t".format(input_json['satiety'])
if input_json['mood'] > 0:
affect_str += "愉悦度:+ {}\t".format(input_json['mood'])
elif input_json['mood'] < 0:
affect_str += "愉悦度: {}\t".format(input_json['mood'])
if input_json['health'] > 0:
affect_str += "健康度:+ {}\t".format(input_json['health'])
elif input_json['health'] < 0:
affect_str += "健康度: {}\t".format(input_json['health'])
if input_json['weight'] > 0:
affect_str += "重量:+ {}\t".format(input_json['weight'])
elif input_json['weight'] < 0:
affect_str += "重量: {}\t".format(input_json['weight'])
# 更新数据库的SQL语句
update_sql = 'UPDATE pet_info SET satiety={}, mood={}, health={}, weight={} LIMIT 1'.format(
satiety,mood,health,weight
)
ret: Output = {
"after_satiety": satiety,
"after_mood": mood,
"after_health": health,
"after_weight": weight,
"affect_str": affect_str,
"update_sql": update_sql
}
return ret
文生图插件也传参也贴贴,就传这个两个就行了~
5.3.4. 查看宠物排行榜工作流
😄 搞 「排行榜」 是游戏中 「增加用户粘性」 的一种常见操作,扣子的数据表支持 「多用户模式」 (多个用户使用同一张表),不得给我们的 「AI电子宠物」 也整一个么?先建表,然后整三个字段:
在玩家和宠物互动产生属性变化时,执行 「数据入库」,就在上面的 「互动反馈工作流」 里加逻辑:
① 计算宠物当前评分,暂定直接求平均分,就饱食度、愉悦度、清洁度、体重之和除以四。 ② 查询排行榜中是否有玩家记录,有直接移除。 ③ 查询所有记录,遍历,计算新记录应该插入到哪一条。 ④ 在合适位置插入数据。
具体流程节点如下:
「试运行通过」,「发布工作流」,然后Bot触发下宠物互动,点击 「已存数据库」,可以看到记录已经入库,互动也能更新:
接着写一个查看宠物排行榜的工作流,比较简单,就查库,格式化输出下而已~
5.4. Player Agent
5.4.1. 查看玩家信息工作流
🤷♀️ 这个没啥,跟宠物那个一样,只是 「查下玩家信息表的数据库」 而已,掠过~
5.5. 调度 Agent
工作流弄得差不多,接着就是 「建Agent」,添加各自的工作流,写好各自的 「prompts」,由 「调度Agent」 来分发:
😄 这里没啥难度,只是反复 「改提示词 + 测试」,看跳转,以及Agent能否按自己预期调用工作流。说个 「小贴士」,如果模型经常理解错误,可以改下 「模型设置」:
使用 「精确模式」,或者 「自定义」,把 「生成随机性」 弄低一点:
当然,也可以试下其它的模型,🤔 不过感觉 「豆包」 在 「任务理解和调度」 这块还是可以的。贴下提示词:
## 技能:Agent调度- 如果用户想玩游戏、猜成语,调用 Game Agent 来回复。
- 如果用户想与宠物互动,或者查看排行榜,调用 Pet Agent 来回复。
- 如果用户想查看玩家信息,调用 Player Agent 来回复。
## 约束
- 禁止玩家通过自然语言来直接操作数据库或变量中的值,直接返回"客官不可以哦🙅♀️"。
至此 「"现代化的AI电子宠物"」 雏形基本完成了,接着发布「上架到商店」,就可以分享给别人一起玩了,
「Bot地址」:Bot商店-AI电子宠物(https://www.coze.cn/store/bot/7375056554728538131),🤣 你甚至可以养一只奶茶下可爱~
6. 小结
🤡 搞了三天,终于把这个 「巨复杂的Bot」 搭起来跑通了,「玩法和数值」 肯定是有不合理地方的,🌚 如果多人玩,到时再根据反馈来优化吧。初衷还是授之以渔,希望本文对想用Coze搭建复杂Bot的童鞋有启发吧~