稀土掘金技术社区

忆童年:用 Coze 搭一只「AI 电子宠物」

点击关注公众号,“技术干货” 及时达!

1. 引言

😄 前些天下午摸🐟时刚好看到掘金运营小姐姐又在发 「新一期的Coze(扣子)搭Bot」 活动:

Image
  • 「📖」 「原文」:《启动 AI 时光机,重温童年无限奇趣 | 掘金&扣子》(https://juejin.cn/post/7373226436570480655)
  • 🕰 活动时间:「5月27日-6月11日」(两周,时间确实紧⏱)不过 「只需搭Bot」,不用写文章 ❗️
  • 😮 要求:用扣子搭一个与「「童年」🎈」主题相关的Bot,需要用到「「工作流」」或「「图像流」」,并 「上架到扣子商店」 且 「发布到掘金」。
  • 😳 「获奖规则」:Bot需要有好听的/切合主题的名字、头像、简介、开场白以及推荐对话;Bot对话体验必须流畅,不能出现调用失败等情况;Bot在掘金沸点的使用次数数据占比总分的40%。

🤔 了解完活动细则,开始找 「灵感」💡,脑海里有关 「回忆杀 的跑马灯」 转起来~

Image

要说大部分 「8090后」 童年时心头的 "「白月光」",那不得是当年风靡一时的「「拓麻歌子」」啊~

Image

哈?没听过?🤣 噢噢,不好意思,这是它「「霓虹万代正版」」的叫法,在当时「「祖国版」」横行的国内,它有着各种各样的名字,如:「宠物蛋、电子鸡」 等等,😄 这里统一称作「「电子宠物」」吧~

Image

玩法非常简单,分为上中下三个区域,「顶部」-「功能栏」 (玩家和宠物互动,如吃饭、洗澡、玩耍、打扫)。「中间-宠物形象」,「底部-状态栏」 (是否需要吃东西、清理大便、想玩耍等)。开局选一只宠物,等几分钟时间-「孵」 「🥚」,孵化后就可以开玩了。基本就是等 「随机事件」 (会有声音提醒),玩家执行对应的 「互动」,饿了投🍚,拉💩打扫,操作完会触发交互动画。如果每次都能及时处理宠物的需求,每隔一阵子,它的形象就会发生改变,哈哈,就类似于长大了😁。当然,如果你疏于照顾,它会生病😷,时间久了会挂掉💀。

😄 屏幕小小,玩法简单,却陪伴笔者度过了一段美好的童年时光,说来惭愧,至今没玩过正版🤣。再往后,和电子宠物的情缘就是初中同桌 「文曲星学习机」 上的国产 「电子宠物」,具体玩法不太记得了,只记得做 「成语补全」,可以给小猫赚钱买道具,那阵子成语词汇量Up↑了不少。接着就是 「QQ宠物」 时代了,不过在2018年9月18日上午11点永久下线了,现在偶尔还在玩的电子宠物就剩 「蚂蚁庄园的小鸡」 了😄~

😏 我突然在想,早期的 「电子宠物」 虽是 「单机养成」,但都能玩的得不亦乐乎,那要是用 「Coze(扣子)」 来搭一个 "「现代化的AI电子宠物」",搭配最近上线的「「图像流」」,「图文并茂」,会不会很有意思?

Image

话不多说,我直接开始🏃~

2. 玩法设计草稿-第一版

😄 先列下头脑风暴出来的玩法草稿~

Image

🤔 就四个简单角色:「玩家」(属性、行为)、「宠物」(属性、行为)、「道具」、「自动任务」,接着做下可能用到的 「技术预研」,写下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」 处点击 「自动生成」:

Image

3.1.1. 建表

新建一个Bot,选 「单Agent模式」,方便调试 「工作流」,接着新建两个表 「player_info」 和 「pet_info」 来保存玩家和宠物信息。

Image
Image

3.1.2. 游戏初始化工作流

😁 就 「刚开始玩或者宠物嘎掉重开」,玩家和宠物信息初始化的流程,具体步骤如下图所示:

Image

数据库部分就是拖节点,然后写下 「简单SQL」 而已,试运行,看下是否初始化成功:

Image

👏 可以,数据库相关没啥问题,这里个 「小细节」🤏 提一哈:

❝

玩家和宠物信息表,只会有 「一条数据」,更新方便 (无脑UPDATE),读写也快😏。当然,你喜欢每次都往 「表的首行」插入数据,然后取的时候取第一条,也是可以滴🤷‍♀️~

❞

3.2. 图像生成

😛 Bot里涉及的图像生成的需求主要有两个:

  • ① 根据用户输出的 「宠物外形描述」,生成 「宠物形象图」。
  • ② 「基于宠物形象图」,结合 「场景、动作等描述词」,生成对应的 「互动画面」。

3.2.1. 根据描述词生成宠物形象图

新建一个 「图像流」,用到「「提示词优化」」和「「文生图」」节点,直接拖拽,配置下 「输入输出」 参数:

Image

试运行,输入 「五彩斑斓的黑色皮卡丘」 的看下生成效果:

Image

🤣 哈哈,有点意思,这里也有一个 小细节🤏,文生图的宽/高参数:

Image

默认「1088x1088」,一张图的生成时间大概是「14-16s」,如果改成最小的「576x576」,只需要「5s」,速度会更快,但出图质量不太稳定 (执行几次生成的图片效果):

Image

发布下图像流,上面的初始化工作流加下它,顺带再添加生成格式化字符串的代码节点:

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里主动调用下工作流,输出效果:

Image

3.2.2. 基于形象图+描述词生成互动画面

😄 同样新建一个 「图像流」,然后,问题来了:「「文生图」」节点不支持设置 「参考图」 参数,尝试使用其它节点实现,先试试「「智能扩图」」

Image

😳 em... 就是 「基于原图的上下左右填充内容」 啊?🤷‍♀️ 试了N次都没让粉红兔宝宝吃上🥕,只能换其它的节点试试了。

🤔 试下「「文生图」」先生成场景图,然后「「多图融合」」写提示词替换:

Image

试运行看下效果:

Image

😳 啥玩意 ???又看了其它类型的节点,好像没法实现我的需求,看来图像流是没发走通的了,用回官方的 「文生图插件」 康康:

Image

试运行看下效果:

Image

🤣 哈哈哈,终于让我的 闪闪发光的 「二哈」 乐呵呵地吃上🥕了,虽然颜色不是pink pink 的~

3.3. 一个要注意的点

🤡 初始化成功后,我突然想起扣子文档里的一句话:

开发者和用户可 「通过自然语言」 插入和查询数据库中的数据。

😳 那用户是不是可以 「言出法随」,直接设置体力和金钱的值啊?试试:

Image

🤣 果然可以,这不妥妥滴游戏 「漏洞」 吗?需要在提示词那里添加下约束:

## 约束

- 禁止用户通过自然语言来直接操作数据库,直接返回 "客官不可以哦🙅"

再试试:

Image

4. 玩法设计草稿-第二版

😳 技术预研完,我对游戏玩法又有新的💡,这样的玩法 「太死板」 了,比如:投喂只能是苹果、米饭和宠物口粮,想喂它吃其它的,得等我去更新。

😕 然后我还得想下加多少点属性值。ヽ(`Д´)ノ 这种通过 「预设的道具表」 来 「约束」 玩家和宠物的 「交互方式」 ,这TM能叫做 "「现代化的AI电子宠物」" ?

🤔 我觉得应该是这样的:

「玩家自由发挥,想跟宠物怎么玩就怎么玩,由 AI 来评估此行为会对宠物的哪些属性造成影响。」

🤣 好的,「道具砍掉」,「随机事件也砍掉」,因为扣子的 「定时触发器」 只有飞书平台能用,本质也是自动给Bot发送特定消息,意义不大。然后玩家的 「金钱属性」 也砍掉,改为每次行动消耗 1 点体力,玩猜成语可以补充体力,修改后的玩法设计草稿:

Image

玩法方向确定了,接着就该具体实现了٩(•̤̀ᵕ•̤́๑)ᵒᵏᵎᵎᵎᵎ

5. 具体实现

5.1. 前置宠物状态校验工作流

😆 在执行每个操作前都需要去检查下宠物的状态,未创建或者宠物健康度为 0,告知用户需要新建宠物。同时查询最后一次与宠物互动的时间,结合当前时间,计算下减少的属性值,更新宠物信息表的数据。(🤷‍♀️这里还没想好策略,就先1h减少5点健康度吧),工作流节点如下:

Image

读两个表的第一条记录,然后根据时间差,计算下减少的健康值,然后判定宠物是否还活着,更新宠物信息表,然后输出,比较简单,难点估计就代码节点,贴一下吧~

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分钟:

Image

😏 这种活完全可以交给 「AI」 啊:

写个基本框架,通过提示词,让大模型来润色输出

举个🌰:

Image

👀 看下运行效果:

Image

文字挤在一起,看不太出效果,复制粘贴到记事本:

Image

🐂 的,虽然比不上我写的😛,但也算有模有样,再微调下提示词可能会更好~

Image

如果不需要每次动态生成,可以复制粘贴下,直接在 「结束」 节点写死返回的值。

「Tips」:😊 勾选下流式输出,Bot 会像 「打字机」 一样回复用户哦~

Image

效果效果如下:

Image

5.2.2. 成语生成工作流

🤔 我尝试直接让豆包大模型随机生成一个 「常见的四字词语」,结果不是 「一心一意」,就是 「画蛇添足」,换了几个模型也是类似的情况。那就走量吧,生成20个,然后我随机roll一个,豆包、通义千问每次都是一到十,醉了...

Image

最后还是 M「inMax 6.5s」 的模型靠谱一些,拖拽节点整下工作流:

Image

难点应该也是代码节点,贴一下:

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写下关键词调下工作流:

Image

5.2.3. 回答判定工作流

😁 就获取下用户输入的四字成语,判断下和变量保存的是否相同,给出对错的反馈,如果正确,玩家的体力+1。

Image

比较简单,只贴下代码:

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写下关键词调下工作流,「结果一直调用报错」,什么数组越界,但是上面的代码并没有用到数组,后面 「反复折腾」,终于发现了原因:

「判断宠物状态的工作流也用到了数据库,不支持这样的嵌套」,把这个节点删掉就正常了...

运行效果:

ImageImage

数据库的玩家体力也有增加:

Image

5.2.4. 全局退出跳转条件

🤷‍♀️ 尝试在Agent里写提示词,用户表达"退出"意图时跳回开始节点或上级节点,折腾了好一会儿都不行🙅‍♀️。那就直接粗暴地拖一个 「全局跳转条件」 的节点来跳:

Image

成语游戏的Agent就搞到这,接着弄宠物的Agent。

5.3. Pet Agent

5.3.1. 初始化工作流

就前面技术预研那里的游戏初始化工作流,稍微调整一下,随机生成属性:

Image

「看下效果」:

Image

5.3.2. 查看宠物信息工作流

这个简单,就查下数据库,然后拼接下字符串而已~

Image

代码节点:

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['image_url']})

【名称】{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调用看下效果:

Image

5.3.3. 互动反馈工作流

🤔 这部分比较复杂,期望输出结果分为三个部分:

  • 「玩家和宠物互动场景的详细文字描述」。
  • 宠物与玩家交互完,根据最后的 「肢体动作和表情」,「生成一张图片」。
  • 「评估此次交互对宠物产生的属性影响」,哪些属性发生了「增减」。

具体效果图:

Image

「工作流」:

Image

这部分的难点,感觉是 「大模型提示词的编写」,我写得不是很好,但也算实现了想要的效果,需要可以参考下😊。

先是 「生成画面详细描述」:

Image

运行看看生成结果:

Image

🤣 差点没把我笑岔气,TM精确到时分秒,太假了,添加下约束:

Image

再试试看生成效果:

Image

😄 其它两个大模型节点的提示词也是这样玩,输出结果达不到要求,就加「约束」,都是 「调调调」,直到满意为止~

「生成图片」

Image

「行为影响评估」

Image

代码节点感觉大伙需要,也贴下吧:

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

文生图插件也传参也贴贴,就传这个两个就行了~

Image

5.3.4. 查看宠物排行榜工作流

😄 搞 「排行榜」 是游戏中 「增加用户粘性」 的一种常见操作,扣子的数据表支持 「多用户模式」 (多个用户使用同一张表),不得给我们的 「AI电子宠物」 也整一个么?先建表,然后整三个字段:

Image

在玩家和宠物互动产生属性变化时,执行 「数据入库」,就在上面的 「互动反馈工作流」 里加逻辑:

  • ① 计算宠物当前评分,暂定直接求平均分,就饱食度、愉悦度、清洁度、体重之和除以四。
  • ② 查询排行榜中是否有玩家记录,有直接移除。
  • ③ 查询所有记录,遍历,计算新记录应该插入到哪一条。
  • ④ 在合适位置插入数据。

具体流程节点如下:

Image

「试运行通过」,「发布工作流」,然后Bot触发下宠物互动,点击 「已存数据库」,可以看到记录已经入库,互动也能更新:

Image

接着写一个查看宠物排行榜的工作流,比较简单,就查库,格式化输出下而已~

Image

5.4. Player Agent

5.4.1. 查看玩家信息工作流

🤷‍♀️ 这个没啥,跟宠物那个一样,只是 「查下玩家信息表的数据库」 而已,掠过~

Image

5.5. 调度 Agent

工作流弄得差不多,接着就是 「建Agent」,添加各自的工作流,写好各自的 「prompts」,由 「调度Agent」 来分发:

Image

😄 这里没啥难度,只是反复 「改提示词 + 测试」,看跳转,以及Agent能否按自己预期调用工作流。说个 「小贴士」,如果模型经常理解错误,可以改下 「模型设置」:

Image

使用 「精确模式」,或者 「自定义」,把 「生成随机性」 弄低一点:

Image

当然,也可以试下其它的模型,🤔 不过感觉 「豆包」 在 「任务理解和调度」 这块还是可以的。贴下提示词:


## 技能:Agent调度

- 如果用户想玩游戏、猜成语,调用 Game Agent 来回复。
- 如果用户想与宠物互动,或者查看排行榜,调用 Pet Agent 来回复。
- 如果用户想查看玩家信息,调用 Player Agent 来回复。

## 约束

- 禁止玩家通过自然语言来直接操作数据库或变量中的值,直接返回"客官不可以哦🙅‍♀️"。

至此 「"现代化的AI电子宠物"」 雏形基本完成了,接着发布「上架到商店」,就可以分享给别人一起玩了,

「Bot地址」:Bot商店-AI电子宠物(https://www.coze.cn/store/bot/7375056554728538131),🤣 你甚至可以养一只奶茶下可爱~

Image

6. 小结

🤡 搞了三天,终于把这个 「巨复杂的Bot」 搭起来跑通了,「玩法和数值」 肯定是有不合理地方的,🌚 如果多人玩,到时再根据反馈来优化吧。初衷还是授之以渔,希望本文对想用Coze搭建复杂Bot的童鞋有启发吧~