1. 引言
🤔 上节《用Coze扣子轻松搭个Bot,从此告别"标题党"》提到了 信息流,让我想起了另一个需求:
希望每天早上起床洗漱时,能够看到订制过的聚合信息:天气、待办、热搜、深圳地铁微博等~
🥰 就好像有一个 专属秘书 跟我汇报工作一样,之前的实现思路:
搞个云服务器跑爬虫脚本,定时爬数据保存数据库里中,写个查数据的接口,最后写个App调接口拉下数据。
😏 把 Coze的官方文档 的官方文档完整看完,感jio 完全可以搭个Bot来实现我的需求,几个关键的要素:
- 数据库:支持用户通过自然语言来增删改查数据库,不过一个Bot目前只能创建一张表。
- 知识库 :感觉能当 静态配置文件 使用,AI可以从这里读取一些固化数据,比如存一些要比如存要爬取url列表。
- 大模型 :感觉它最大的作用就是 理解用户意图 ,将它理解到的意图和处理逻辑相匹配,然后调用对应的 插件或工作流,执行任务后输出返回。
- 节点和插件 :本质上都是 API接口调用的封装 ,如果平台提供的插件不够用或不好用,可以自己找个 API接口 (第三方或自己实现) 封装个 自定义插件。配置起来也很简单:配个API的url、请求头和输入输出参数就完事了。
- 代码 :上节提到过代码在一个异步的main()函数中执行,入参Args,返回值是Output。笔者就在想,能不能在上面写 爬虫代码 😏?先试了下支持 import 导包,接着依次试了下导入常见的Python抓包库如urllib、requests、aiohttp_requests 等都提示找不到库,正当我一筹莫展的时候,我发现了下方的 尝试AI Ctrl + I,直接用AI生成下代码看看?
😶 喔吼,提供了 request_async 库来模拟请求,接着又试了下其它库,如bs4、pypeteer都提示找不到库,看来 只支持模拟请求 ,不支持自动化爬取 ,数据解析的话,估计只能写 正则 来提取了。
🥰 关于Coze提供的 "积木" 都里了然于心了,接下来具体实践下,猜猜坑~
2. 数据库
2.1. 新建Bot
新建一个Bot,AI生成的Logo太Low了,直接用 剪映的Dreamina 生成一张妹子图,感觉跟小秘的身份更贴切~
创建完,随手写个简单提示词:
😑 试了下,发现颜文字和emoji表情不会同时出现,em...读者更喜欢哪种呢?
😋 感觉颜文字更卡哇伊一些?接着试下Coze提供的数据库玩法~
2.2. 建表
编排 页找到 记忆-数据库:
点击+号,可以直接 自定义表格 ,也可以 使用模板 然后在二次编辑:
接着随手定义了四个字段 (配置项信息要谨慎填写哦!它会用于LLM意图理解):
创建后,下方可以看到 新建的表,点击右侧的两个小图标可以对表进行编辑或删除。
2.3. 增删改查
表建完,接着试试跟Bot聊天实现表的增删改查,数据可能有些错误,没关系,后面通过添加约束来提高准确性,先录几条待办吧:
Bot提示已经记录了,我们点下右上角的 已存数据库,看看数据是否真的入库:
接着试试查:
然后是改 (2333,说修改成功,实际是没有改到的,因为task_time字段不等,哈哈哈😄):
最后在试试删:
单删和全删都是可以的,😂 不过因为没有添加 约束,执行过程不太靠谱,等下写个专门处理待办的工作流。
2.4. 骚操作:一表多用
🤭 前面说了一个Bot只能建一个表,工作流那里也只能使用 记忆库 结点,但在我们的场景中,需要存储多种类型的数据。比如我们这里,除了存储待办外,还需要存储想查询天气的城市信息?咋办?添加一个 代表数据类型的字段 就完事了,把前面建的表删掉,然后新建一个保存用户提交记录的表:
通过这个 record_type 来区分,提示词那里稍微改改:
接着录点数据试试:
bash
待办:周四下午1点去小白家里吃饭
录入城市:深圳市南山区
录入城市:广州市白云区、上海市黄浦区
待办:大年三十晚上7点看饺子包春晚
待办:明早五点起来收拾行李回家,记得带身份证
打开数据库,可以看到数据都正确录入了:
接着试试通过对话的方式分别查看录入的信息:
可以,数据库的玩法就体验到这里,接着开始完善具体实现细节~
3. 完善提示
🤡 我本来想用 工作流 来单独处理待办的,却发现并不支持操作数据库,只会给我返回没啥用的 SQL语句😂
又折腾了一下代码节点,也不支持操作数据库,em... 看来只能在 人设与回复逻辑 那里操作数据库了,可玩性骤减啊... 先写出 判定用户意图执行对应操作的提示词:
😑 花了几个小时,反复测试和修改提示词,em...语雀大模型的误判率还挺高,难顶,最终的提示词:
勉强能答对,拆分好技能,接着一个个完善~
3.1. 待办处理
😑 这里把我整麻了,太不稳定了,我的提示词:
然后经常出错、失败和不符合预期的输出结果:
- 没有生成raw_sql字段:Failed: missing parameter: raw_sql
- raw_sql 没包含 record_create_time 字段:一直报 参数校验未通过,年前会默认生成,今天怎么瞎搞都不行,无奈只能将这个字段改为非必填。
- 不稳定的输出结果,约束输出格式完全没用,少返回数据等等...
反复调整提示词还是dio样,还不支持手动调 ts-TableMemory 插件执行特定sql,无奈只能放弃待办了...
3.2. 天气查询
😑 和上面同样的问题,数据库操作插件太不靠谱了,我的提示词:
😑 问天气,直接跳过第一步ts-TableMemory查数据库,去调墨迹天气插件,传参不是北京就是上海,醉了
😞 难道一次只能调用一个插件?唉,放弃数据库了,改用 知识库 写死几个城市试试,虽然和我们的初衷违背:每个用户动态关注想关注城市的天气信息 ┓( ´∀` )┏。新建一个知识库:
依次点击 新增单元 → 文本格式 → 自定义 → 下一步 → 输入单元名 → 创建分段:
工作流支持 知识库 节点,直接创建一个工作流:
新建一个 知识库节点,做下简单配置:
试运行,看下输出结果:
可以,知识库里配置的城市信息都拿到了,接着添加一个 墨迹天气 的插件,点击 批处理,参数值设置为知识库的outputList:
city 字段设置为item1里的值:
运行看看:
得嘞,天气信息都获取到了😄,这里直接把省市区都塞到city字段里了,正确传参应该是 拆分省市区 来传对应参数的。所以需要对 知识库 的城市信息做下处理,提取下省市区,思路有两,一一实现下😆
3.2.1. Python正则代码提取省市区
无它,写个 正则 提取即可,随手搜个 省市区/县/镇/乡/村提取 的 正则表达式 就好了,新建 代码 节点,写出提取代码:
python
import re
# 匹配省市区的正则
pattern = re.compile(r'(?P<province>[^省]+省|自治区|直辖市)(?P<city>[^市]+市|自治州)(?P<county>[^县]+县|[^区]+区|[^市]+市|[^旗]+旗|自治县)?(?P<other>.*)', re.S)
async def main(args: Args) -> Output:
params = args.params
result_list = []
address_list = params["inputList"]
# 遍历地址,提取省市区
for address in address_list:
match = pattern.search(address['output'])
if match:
result_dict = {}
province = match.group('province')
city = match.group('city')
county = match.group('county')
other = match.group('other')
if province:
result_dict['province'] = province
if city:
result_dict['city'] = city
if county:
result_dict['county'] = county
result_list.append(result_dict)
ret: Output = {
"outputList": result_list,
}
return ret
添加输入参数,运行看下输出:
接着要配置下输出值中 子项的数据类型 ,否则后面会 索引不到子级字段!!!你不配置就会介样:
配置下:
然后调下天气插件的输入参数:
阔以,天气都获取到了,代码的方式跑通了,接着试试大模型~
3.2.2. 利用大模型提取省市区
🤭 这就简单了,直接写提示词,让它帮我们提取下省市区,然后塞到对应字段中:
这里有一点要注意,大模型的返回值不支持 Object类型和Array<Object>类型 (灰色不给选),所以这里得拆分成三个String数组输出。
😛当然,也可以指定输出结果为 固定格式的Json ,然后接个 代码节点,解析Json字符串作为入参,生成Array 输出。
天气插件配置下入参:
运行结果和 代码节点 一致,简单对比下两种方式的特点:
- 代码提取:提取速度快(0.063s),硬编码只支持处理简单和标准的地址格式,支持返回Object类型的数据。
- 大模型提取:提取速度较慢(4s),支持处理复杂和非标准的地址格式,不支持返回Object类型的数据。
3.2.3. 格式化输出结果
加个代码节点,格式化下天气的输出结果:
python
async def main(args: Args) -> Output:
params = args.params
weather_infos = params['weather_infos']
city_infos = params['city_infos']
output = ""
for index, weather_info in enumerate(weather_infos):
city_info = city_infos[index]
weather_dict = weather_info["data"]
# 判空避免获取不到天气数据奔溃
if weather_dict:
weather_data = weather_info["data"][0]
output += "- 【{}{}】{} - {}~{}°C".format(city_info["city"],city_info["county"], weather_data["condition"], weather_data["temp_low"], weather_data["temp_high"])
if index != len(weather_infos) - 1:
output += "\n"
ret: Output = {
"output": output,
}
return ret
搞点输入运行下看看:
最后跟 结束节点 串起来,试运行看看:
ok,查询天气信息的工作流就到这里,发布 下工作流,然后编排提示词那里,指定使用此工作流:
接着在 预览和调试 处,随手输入一个 天气:
🥳 可以,是想要的效果,巴适得很~
3.3. 新闻热点
然后是新闻热点,同样整个工作流:
点击插件,搜下 新闻 ,只发现一个 头条新闻 插件,添加下,发现只支持传递一个 关键词 参数:
运行下看看:
我期望的是这样的热点新闻:
结果改了好几个关键词 (如: 头条热榜) 都不行,数据也只有4-5条,而且有些新闻是很早之前的,这哪里是新闻,这是 旧闻 吧 ****🐶 。
🤔 自带 新闻插件 不给力,可以换个思路:用 搜索插件 搜索,然后用 网页信息提取插件 进行内容提取,最终整合下信息。😳 平台目前支持两个搜索插件:必应搜索 和 头条搜索 。主要参数都差不多:新闻关键词(必填) 、搜索条数 、偏移。另外,头条搜索包含两个插件工具:
- search :搜索用户询问的内容,比必应多了个search_id 参数用于分页加载更多。
- browse:从url链接获取正文信息,参数:url、prompt(有关url链接内容的问题)。
两个插件都用上,搜索关键词 实时热点新闻,运行看看输出结果,对比下搜索结果:
必应搜索 直接返回了各种 带热搜的站点 ,头条搜索 返回的结则有些咋,比如这个搜索结果大部分跟《热搜》这部电影有关。🤔 权衡之下还是决定用必应,然后这样玩:
- 代码节点:获取站点url(做下去重),返回url列表;
- url页面内容读取插件:批处理获取下热点新闻的内容;
- 代码节点:使用split('\n')将content字段分割成多条新闻;
- 大模型:批处理生成热点新闻的概要列表返回。
然后就是创建对应的节点:
3.3.1. 提取站点url的代码节点
就提取热搜站点的url保存到列表中返回,顺带用set()做下简单去重:
python
async def main(args: Args) -> Output:
params = args.params
news_input = params["input"]
url_list = []
for news in news_input:
url_list.append(news["url"])
# 顺带用set做下去重
ret: Output = {
"urlList": list(set(url_list)),
}
return ret
3.3.2. 插件提取页面热搜内容
这里用的 头条搜索的search插件 ,相比起 Link Reader插件 速度更快,毕竟这里要执行 批处理:
3.3.3. 分割新闻标题的代码节点
就是用split('\n')对content的内容进行分割,拆成一个字符串数组返回:
python
async def main(args: Args) -> Output:
params = args.params
output_list = params["content"]
news_list = []
for output in output_list:
news_list.extend(output["data"]["content"].split('\n'))
ret: Output = {
"newsList": news_list,
}
return ret
运行输出结果:
可以看到新闻标题还是粘连在一起了,而且拼接格式不太确定,所以没法直接 硬编码 的方式进行提取,试下用 大模型 节点来提取一波~
3.3.4. 提取新闻标题的大模型结点
简单写下提取新闻标题的 提示词,并对输出结果添加约束,以保证输出结果符合我们的预期:
但是得出的输出结果:
难道是输入内容太长了,消耗了太多Token:
后面我又删减了一些,还是不行,我又试着把输入和提示词复制到另一个国产AI上:
😄 2333,直接就结束对话,大概率是触发了某些 敏感词 或 违禁词,试下丢GPT4上瞅瞅:
果然,后面反复尝试,发现是几个 人名 导致的,所以前面的代码节点还需要对相关字符串做下 替换。新闻条数也得做下限制,不然会报其它的错误,调整完后的运行结果:
可以,发布下工作流,改下提示词:
发送 新闻热点 看看:
勉强算是拿到了新闻热点了,最后完善下消息汇总吧~
3.4. 信息汇总
😑 em...这一步的设想就是将多种数据组合到一起返回,待办就不掺和了,就调下天气和新闻吧,写下提示词:
发送 早 看看:
┓( ´∀` )┏ 行吧,人设和回复逻辑 这里 只会调用一个插件或工作流,这个AI小秘就暂且搞到这吧,仅仅依靠平台提供的插件,很难达到我们的预期,很多功能需要自己写插件实现...
😏 对了年前偶然发现Coze支持把Bot发布到 豆包 上了:
😆 啧啧,选个温柔御姐的声线,听着AI小秘汇报,体验直接拉满!😁 不过,具体要审核多久不知道,过年前提交的 Bot-标题党滚粗 还处于 审核中。虽然别人搜不到你的Bot,但你自个是可以偷着乐的哈~
4. 小结
😐 磕磕碰碰,勉强把AI小秘给堆完了,尽管和我们的期望大相径庭,但
捋一捋期间遇到的问题吧,当然,也可能是我的使用姿势不对导致的,欢迎评论区指出:
- 数据库
- 一个Bot只支持一个数据库表。
- 不能在工作流中操作数据库 ,大模型和代码节点都不行,你只能在 人设与回复逻辑 那里通过 自然语言 让AI理解,然后由它 自动生成sql 并调用 ts-TableMemory.tableExecute 插件对数据库进行增删改查,整个过程你是没法干预的。
- 数据库表设计得稍稍复杂点,经常会出现:没生成raw_sql字段 、字段遗漏导致参数校验不通过 、返回条数和查询结果数量不一致 等问题。
- ts-TableMemory.tableExecute 插件是没开放的,没法自己手写sql操作数据库表。
- 自带插件
- 批处理+试运行 ,容易报错,且报错意义不明:RPCError [调用HTTP接口失败,插件调用失败],感觉是做了 接口防刷 触发的报错。😳 但文档或者插件描述也没说调用频次限制啥的..
- 云雀大模型
- 时不时抽风,报错啥提示也没,有时会返回一个莫名其妙的输出,即便你加了约束。
- 没提供用户唯一标识获取方法
- 想自定义插件,不知道怎么做用户关联
- 节点折叠
- 节点很多的时候,操作起来不太方便,如果能支持整个折叠就好了,比如就剩下:节点图标+节点名称。
🤡 以上就是我在搭Bot期间遇到的问题,em... 可能我上来就定位错了,想弄一个 功能大而全的Bot 。只是现在的扣子更适合 实现单一功能的Bot,等以后支持API输出了,再来完善这个AI小秘吧🤖~
BotID:7331752198227116068