1. 引言
😄 前些天下午摸🐟时刚好看到掘金运营小姐姐又在发 新一期的Coze(扣子)搭Bot 活动:
- 📖 原文:《启动 AI 时光机,重温童年无限奇趣 | 掘金&扣子》
- 🕰 活动时间: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 datetime
async 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 time
async 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一个,豆包、通义千问每次都是一到十,醉了...
最后还是 MinMax 6.5s 的模型靠谱一些,拖拽节点整下工作流:
难点应该也是代码节点,贴一下:
import random
async 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 json
async 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电子宠物,🤣 你甚至可以养一只奶茶下可爱~
6. 小结
🤡 搞了三天,终于把这个 巨复杂的Bot 搭起来跑通了,玩法和数值 肯定是有不合理地方的,🌚 如果多人玩,到时再根据反馈来优化吧。初衷还是授之以渔,希望本文对想用Coze搭建复杂Bot的童鞋有启发吧~