背景
上大学的时候,非常喜爱玩lol,今年随着年龄越来越大,手速与反应慢了很多,白银局打的都费劲,也就不想玩了。后面lol出了壹个云顶之弈,这个玩法不需要手速与反应,我偶尔玩一下。
我段位很低,常年在铂金段位。因为工作与家庭的原因,没有时间去研究阵型,凭着自己的理解乱玩。
有次在看直播的时候,看见主播在壹个网站查询数据。某个英雄带哪个装备胜率顶尖。我得知有这么壹个网站,然后我自己玩的时候,不了解向英雄啥子装备,或不了解选啥子强化的时候,就跑到这个网站查询。但是这个网站很卡,打开一次要很久,查询方法对新手不太友好,所以我打算用coze做壹个辅助工具,只需要用户简单的输入,就能获得到自己想要的信息。
云顶之弈助手做完后,我自己体验了一下,轻轻松松上到了一区砖石,顶尖砖一44胜点,本来差一点上大师,那把之后运气就变差了,D差点自己想要的牌,连续几把没吃分,最初摆烂了,今年掉到了砖三了。
下面与我们同享一下这个助手,感兴趣的可以去体验一下。
关于Coze平台概述
扣子是新一代 AI 应用开发平台。无论你是否有编程基础,都可以在扣子上快速搭建基于大模型的各类 Bot,并将 Bot 发布到各个社交平台、通讯软件或部署到网站等其他渠道。
功能和优势无限拓展的能力集
扣子集成了丰富的插件工具,可以极大地拓展 Bot 的能力边界。
内置插件:目前平台已经集成了超过 60 款各类型的插件,包括资讯阅读、旅游出行、效率办公、图片理解等 API 及多模态模型。 你可以直接将这些插件添加到 Bot 中,丰富 Bot 能力。例如运用新闻插件,打造壹个可以播报全新时事新闻的 AI 新闻播音员。自定义插件:扣子平台也支持创建自定义插件。 你可以将已有的 API 能力通过参数设置的方法快速创建壹个插件让 Bot 调用。丰富的数据源
扣子提供了简单易用的姿势库功能来管理与存储数据,支持 Bot 和你自己的数据进行交互。无论是内容量巨大的本地文件还是某个网站的实时信息,都可以上传到姿势库中。这样,Bot 就可以运用姿势库中的内容回答问题了。
内容格式:姿势库支持添加文本格式、表格格式的数据。内容上传: 你可以将本地 TXT、PDF、DOCX、Excel、反恐精英V 格式的文档上传至姿势库,也可以基于 URL 获得在线网页内容与 API JSON 数据。同时支持直接在姿势库内添加自定义数据。持久化的记忆能力
扣子提供了方便 AI 交互的数据库记忆能力,可持久记下用户对话的重要参数或内容。
例如,创建壹个数据库来记录阅读笔记,包括书名、阅读进度与单人注释。有了数据库,Bot 就可以通过查询数据库中的数据来提供更准确的答案。
灵活的工作流设计
扣子的工作流功能可以用来处理逻辑复杂,且有较高稳定性标准的任务流。扣子提供了大量灵活可搭配的节点包括大语言模型 LLM、自定义代码、判断逻辑等,无论你是否有编程基础,都可以通过拖拉拽的方法快速搭建壹个工作流,例如:
创建壹个搜集电影点评的工作流,快速查看一部全新电影的点评和评分。创建壹个撰写行业研究报告的工作流,让 Bot 写一份 20 页的报告。运用体验
在做插件的过程中,遇到了很多问题,感觉Coze还有很长的路要走,后面会与我们同享一下我遇到的坑。
功能说明阵型主推
当用户输入阵型主推或和阵型相关的内容时,会为用户主推最热门的阵型。这里的热门阵型本来打算运用tactics网站里的胜率高的阵型,但是tactics里的数据都是国外的,国产环境与国外环境不一样,所以我这里运用的是官网的主推阵型。
点击查看详情按钮,可以查看阵型详情。
数据查询
输入棋子名称或装备名称或强化名称会根据和平精英率、前4率、平均排行为你主推和之最组合的强化、阵型、装备、羁绊。
这里输入的名称,可以少输一些字,但是不能有错字。
我们可以在bot商店里搜索云顶之弈助手体验插件。
bots开发过程人设和回复逻辑
告知coze根据用户不同的输入调用不同的工作流。
这里我写了两个工作流,壹个是查询热门阵型,另外壹个用来查询棋子、装备、强化信息。
热门阵型热门阵型工作流
热门阵型工作流里主要调用了云顶热门阵型插件(yunding_hot_list),然后把插件里返回的数据返回出去,这里有个需要注意的地方,我被卡了一段时间,还是群里的大佬帮助我化解的,结束节点这里,如果你想运用CAG展示用户问题答案,结束节点这里最好配置为运用设定内容指定回答,
如果运用默认回答玩法,会输出乱七八糟的东西。
热门阵型插件
工作流里支持调用插件,大家写壹个查询云顶热门阵型的插件在工作流中运用。
插件这里开发语言我挑选nodejs,调用云顶官网接口查询数据,官网地址:英雄联盟.qq.com/tft/#/index。
插件完整代码
import { Output } from "@/typings/yunding_hot_list/yunding_hot_list";import { unescape } from 'querystring';/** * Each file needs to export a function named `handler`. This function is the entrance to the Tool. * @param {Object} args.input - input parameters, you can get test input value by input.xxx. * @param {Object} args.logger - logger instance used to print logs, injected by runtime * @returns {*} The return data of the function, which should match the declared output parameters. * * Remember to fill in input/output in Metadata, it helps LLM to recognize and use tool. */export async function handler(): Promise<Output> { // 获得棋子数据 async function getChessMap() { const { data } = await fetch('https://game.gtimg.cn/images/英雄联盟/act/img/tft/js/chess.js').then(res => res.json()); return data.reduce((prev, cur) => { prev[cur.chessId] = cur; return prev; }, {}); } // 获得羁绊数据 async function getHexMap() { const { data } = await fetch('https://game.gtimg.cn/images/英雄联盟/act/img/tft/js/hex.js').then(res => res.json()); return Object.values(data).reduce((prev, cur: any) => { prev[cur.hexId] = cur; return prev; }, {}); } const [chessMap, hexMap] = await Promise.all([ getChessMap(), getHexMap(), ]); // 查询热门阵型 let data: any = await fetch('https://game.gtimg.cn/images/英雄联盟/act/tftzlkauto/json/lineupJson/s11/6/lineup_detail_total.json') .then(res => res.text()); data = JSON.parse(unescape(data.replace(/\\n/g, ''))); data.lineup_list.forEach(item => { item.detail = JSON.parse(item.detail); }); // 格式化数据输出 const formatedData = (data.lineup_list.map((item, index) => { return { rank: index + 1, name: item.detail.line_name, image: 'https://www.fluxyadmin.cn/file/yunding/1001.jpeg', chesses: item.detail.hero_location.map(item => chessMap[item.hero_id]?.displayName).filter(o => o).join(','), hexes: item.detail.hexbuff.recomm.split(',').map(item => hexMap[item]?.name).filter(o => o).join(','), races: item.detail.contact.toSorted((x, y) => y.num - x.num).map(item => `${item.num}${item.name}`).filter(o => o).join(' '), hex_info: item.detail.hex_info, early_info: item.detail.early_info, d_time: item.detail.d_time, equipment_info: item.detail.equipment_info, location_info: item.detail.location_info, location_info_2: item.detail.location_info_2, enemy_info: item.detail.enemy_info, url: `https://英雄联盟.qq.com/tft/#/lineupDetail/${item.id}/1/detail`, } })) return { list: formatedData, };};
配置插件的输入输出参数
自定义回答CAG
如果不想运用文字回答,可以运用自定义CAG形式展示答案。
向工作流绑定CAG
可以运用官方的CAG,也可以自定义,这里官方没有合适的,大家自定义壹个。
先创建壹个变量,默认值把插件返回的数据放进去。
剩下的就像一些低代码平台一样,可以通过拖拉拽设置CAG内容。
可以通过设置循环渲染,展示列表
文本内容可以绑定变量
为CAG绑定变量,绑定变量的我发现了个bug,好像只支持string类型的数据,其他类型数据即使工作流返回的数据类型与CAG需要的类型一样,也选差点。
效果展示
棋子、强化、装备信息查询
与上面热门阵型的设置差不多,也是在工作流中调用壹个插件返回数据,然后自定义CAG展示。
这里主要说明一下插件,最最初从网站获得数据在一块代码是写在插件里的,但是查询数据这几个接口,太慢了,所以我自己写了壹个接口,向查询的数据做壹个缓存,只要当前信息被查过一次,后面再查直接就从大家自己数据库中取就行了。
每日晚上12点,把数据向清掉,第二天全部数据从头查询,保证数据实时性。
数据来源网站:tactics.tools
对外提供接口的框架,运用的是midway node框架,我以前写过一篇midway入门文章。
因为棋子、装备、强化名称是固定的,所以大家先写壹个脚本把这些数据做成静态文件,就不用每次都去请求这些数据了。
然后写接口,数据库运用的是mongoosedb。
import { Controller, Get, Inject, Query } from '@midwayjs/core';import { DataService } from '../service/chess.service';import chess from '../data/chess.json';import items from '../data/items.json';import augments from '../data/augments.json';@Controller('/api')export class APIController { @Inject() dataService: DataService; @Get('/data') async getData(@Query('name') name: string) { // 判断用户输入的名称是棋子还是物品还是强化名称 const chessName = Object.keys(chess).find(o => o.includes(name)); if (chessName) { return this.dataService.getChessByName(chessName); } const itemName = Object.keys(items).find(o => o.includes(name)); if (itemName) { return this.dataService.getItemByName(itemName); } const augmentName = Object.keys(augments).find(o => o.includes(name)); if (augmentName) { return this.dataService.getAugmentByName(augmentName); } }}
service中调用tactics网站接口查询数据,下面以查询棋子信息为例。
async getChessInfo(name: string) { const code = Chess[name]; if (!code) return { name: 'error' }; // 查询数据库,如果存在则直接返回 let info: Data = await this.dataModel.findOne({ code }); if (info) { return info; } const data = (await axios( `https://d2.tft.tools/stats2/unit/1100/${code}/14101/1` ).then(res => res.data)) as any; info = { icon: `https://ap.tft.tools/img/face/${data.unitId}.jpg`, } as Data; info.count = data.base.count > 1000 ? (data.base.count / 1000).toFixed(2) + 'k' : data.base.count; info.rate = data.base.rate.toFixed(2) + '%'; info.won = data.base.won + '%'; info.top4 = data.base.top4 + '%'; info.place = data.base.place; info.name = zh.s11Unitsi18n[data.unitId]; // 计算平均排行,然后取前5名强化 info.augments = this.getAverageRanking(data.augments) .slice(0, 5) .map(cur => { return `${zh.s11Augmentsi18n[cur.id]}`.replace(/ /g, ''); }) .join(' '); // 计算平均排行,然后取前5名装备 info.items = this.getAverageRanking( data.items.map(item => ({ ...item, id: item.items })) ) .slice(0, 5) .map(item => { return `${zh.s11Itemsi18n[item.items]}`; }) .join(' '); // 计算平均排行,然后取前8名单位 info.units = [ ...this.getAverageRanking(data.units) .slice(0, 8) .map(item => `${zh.s11Unitsi18n[item.id]}`), name, ].join(' '); // 计算平均排行,然后取前十名羁绊 info.traits = this.getAverageRanking( data.traits.map(item => ({ ...item, id: `${item.id[0]}-${item.id[1]}` })) ) .slice(0, 10) .map(item => { const [id] = item.id.split('-'); return `${zh.s11Traitsi18n[id]}`; }) .join(' '); info.code = code; info.type = '棋子'; this.dataModel.create(info); return info; }
再写壹个定时任务,每日晚上12点清除数据。
import { Inject } from '@midwayjs/core';import { Job, IJob } from '@midwayjs/cron';import { DataService } from '../service/chess.service';@Job({ cronTime: '0 0 0 * * *', start: true,})export class DataClearJob implements IJob { @Inject() dataService: DataService; async onTick() { this.dataService.clear(); }}
写完接口中,部署到服务器,然后在插件中调用接口。
后续计划
本来我还做了壹个功能,把全部棋子的信息爬下来,然后上传到coze的姿势库,试了一下发现coze姿势库回答的不稳定。
我预备的测试姿势库数据
这是我的问题与它的回答,实际上大卖特卖的和平精英率比小伙伴高。
也许是我用法不对,我再研究研究,作为后续计划吧。
最后
截止到这篇文章发完,我还是没上大师,五一期间玩的有点多,影响了家庭与谐,后面就没再玩了,有点遗憾,没有圆大师梦。
希望这个插件能帮助到我们,让我们上升分。
作者:前端小付
链接:https://juejin.cn/post/7370244444282667034