背景
前几天写了一篇文章,讲了我是如何使用coze实现云顶之弈助手的。不过后面留了一个需求,现在我们来实现一下,让插件功能更强大一点,交互性更强一点。
然而在上一篇说过表格模式下的知识库,回答用户问题不太稳定,所以这几天我一直在想怎么能让他稳定一点,后面我想到了一个方案,稳定率大大提升,下面和大家分享一下。
我的方案
上面说了coze自带的表格知识库回答的不稳定,是因为他生成的sql不太稳定,并且在工作流中使用知识库的时候,更不稳定。
我想了一下,既然coze默认生成的sql不稳定,那么我用大模型自己生成sql,然后动态执行sql查询结果。
大致流程如下
- 写一个服务从tactics网站抓取数据上传到自己数据库
- 写一个coze插件分析数据库表结构
- 在coze工作流中把数据库表结构信息和用户输入传给大模型,让大模型生成sql
- 写一个能够支持动态执行sql获取结果的coze插件
- 在coze工作流中把生成的sql传给sql执行器执行sql,获取结果
功能展示
查询棋子信息
查询装备信息
查询强化信息
查询棋子和装备
查询棋子和强化
问题
同一个问题,答案还是不太稳定,大家可以多提问几次。
提供的信息完善一点,准确率会更高一点
抓取数据上传到数据库
整理获取数据的接口
获取基本信息的接口
棋子信息: game.gtimg.cn/images/lol/…
羁绊信息: game.gtimg.cn/images/lol/…
职业信息: game.gtimg.cn/images/lol/…
装备信息: game.gtimg.cn/images/lol/…
强化信息: game.gtimg.cn/images/lol/…
奇遇信息: game.gtimg.cn/images/lol/…
获取实战数据的接口
返回的数据中units
字段里存放的是所有棋子的实战数据,包括(平均排名,前4率,登场次数,吃鸡率)
返回的数据中items
字段里存放的是所有装备的实战数据,包括(平均排名,前4率,登场次数,吃鸡率)
获取强化实战信息稍微有点麻烦
需要先从https://tactics.tools/augments
地址中获取html,然后使用cheerio
库解析html里的数据。
// 获取强化实战数据
export const getHexData = async () => {
const body = await fetch<string>('https://tactics.tools/augments');
const $ = load(body);
const data = $('#__NEXT_DATA__').text();
const formatData = JSON.parse(data) as any;
return (formatData.props.pageProps.augsData.singles || []).reduce(
(prev, cur) => {
prev[cur.id] = cur.base;
return prev;
},
{}
);
};
初始化本地项目
项目框架使用的是midway,数据库使用的是mysql,orm使用的是typeorm,使用corn起本地定时任务。
模型
棋子模型
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity({ comment: '棋子信息' })
export class Chess {
@PrimaryGeneratedColumn()
id: number;
@Column({ comment: '棋子图片' })
icon: string;
@Column({ comment: '棋子名称或棋子名字' })
chessName: string;
@Column({ comment: '棋子种族' })
race: string;
@Column({ comment: '棋子职业' })
occupation: string;
@Column({ comment: '棋子技能名称' })
skillName: string;
@Column({ comment: '棋子技能描述', type: 'text' })
skillDesc: string;
@Column({ comment: '棋子的价格' })
price: number;
@Column({ comment: '平均排名', type: 'float', nullable: true })
place: number;
@Column({ comment: '前4率', type: 'float', nullable: true })
top4: number;
@Column({ comment: '第一名率(吃鸡率)', type: 'float', nullable: true })
won: number;
@Column({ comment: '上场、登场、上场次数或登场次数', nullable: true })
count: number;
}
装备模型
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity({ comment: '装备信息' })
export class Item {
@PrimaryGeneratedColumn()
id: number;
@Column({ comment: '装备图片' })
icon: string;
@Column({ comment: '装备名称' })
name: string;
@Column({ comment: '装备描述', type: 'text' })
introduce: string;
@Column({ comment: '装备英语名称' })
enName: string;
@Column({ comment: '平均排名', type: 'float', nullable: true })
place: number;
@Column({ comment: '前4率', type: 'float', nullable: true })
top4: number;
@Column({ comment: '第一名率(吃鸡率)', type: 'float', nullable: true })
won: number;
@Column({ comment: '上场、登场、上场次数或登场次数', nullable: true })
count: number;
}
强化信息模型
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity({ comment: '强化信息' })
export class Hex {
@PrimaryGeneratedColumn()
id: number;
@Column({ comment: '强化图片' })
icon: string;
@Column({ comment: '强化名称' })
hexName: string;
@Column({ comment: '强化描述', type: 'text' })
introduce: string;
@Column({ comment: '强化英语名称' })
enName: string;
@Column({ comment: '平均排名', type: 'float', nullable: true })
place: number;
@Column({ comment: '前4率', type: 'float', nullable: true })
top4: number;
@Column({ comment: '第一名率(吃鸡率)', type: 'float', nullable: true })
won: number;
@Column({ comment: '上场、登场、上场次数或登场次数', nullable: true })
count: number;
}
棋子与装备的实战数据模型
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity({
comment: '棋子装备数据表',
})
export class ChessItem {
@PrimaryGeneratedColumn()
id: number;
@Column({ comment: '棋子名称' })
chessName: string;
@Column({ comment: '装备名称' })
itemName: string;
@Column({ comment: '平均排名', type: 'float', nullable: true })
place: number;
@Column({ comment: '前4率', type: 'float', nullable: true })
top4: number;
@Column({ comment: '第一名率(吃鸡率)', type: 'float', nullable: true })
won: number;
@Column({ comment: '登场次数', nullable: true })
count: number;
}
棋子与强化的实战数据模型
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity({
comment: '棋子强化数据表',
})
export class ChessHex {
@PrimaryGeneratedColumn()
id: number;
@Column({ comment: '棋子名称' })
chessName: string;
@Column({ comment: '强化名称' })
hexName: string;
@Column({ comment: '平均排名', type: 'float', nullable: true })
place: number;
@Column({ comment: '前4率', type: 'float', nullable: true })
top4: number;
@Column({ comment: '第一名率(吃鸡率)', type: 'float', nullable: true })
won: number;
@Column({ comment: '上场 登场 上场次数 登场次数', nullable: true })
count: number;
}
定时任务
使用corn起一个定时任务,每天晚上12点定时执行,把数据更新到数据库中,保证数据实时性。
编写获取数据库表结构信息插件
插件源码
使用mysql2连接数据库,然后执行sql查询表信息。
import { Args } from '@/runtime';
import { Input, Output } from "@/typings/database_table_info/database_table_info";
import mysql, {RowDataPacket} from 'mysql2/promise';
interface Table extends RowDataPacket {
tableName: string;
tableComment: string;
}
/**
* 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({ input }: Args<Input>): Promise<Output> {
// // Create the connection to database
const connection = await mysql.createConnection({
host: input.host,
user: input.user,
database: input.database,
password: input.password,
port: input.port,
});
// 查询数据库中有哪些表
const [results] = await connection.query<Table[]>(
`SELECT
table_name AS tableName,
table_comment as tableComment
FROM
information_schema.tables
WHERE
table_schema = '${input.database}';`
);
const tables = await Promise.all(results.map(item => getTableInfo(connection, item.tableName, item.tableComment)));
connection.destroy();
return {
data: {
tables,
} as any
}
};
async function getTableInfo(connection: mysql.Connection, tableName: string, tableComment: string) {
const [results] = await connection.query(
`SELECT COLUMN_NAME as name, COLUMN_COMMENT as comment
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '${tableName}'`
);
return {
tableName,
tableComment,
fields: results as any,
};
}
插件输入参数
输出参数
{
"data": {
"tables": [
{
"fields": [
{
"comment": "",
"name": "id"
},
{
"comment": "棋子种族",
"name": "race"
},
{
"comment": "棋子职业",
"name": "occupation"
},
{
"comment": "棋子技能名称",
"name": "skillName"
},
{
"comment": "棋子图片",
"name": "icon"
},
{
"comment": "棋子的价格",
"name": "price"
},
{
"comment": "棋子技能描述",
"name": "skillDesc"
},
{
"comment": "平均排名",
"name": "place"
},
{
"comment": "前4率",
"name": "top4"
},
{
"comment": "第一名率(吃鸡率)",
"name": "won"
},
{
"comment": "上场、登场、上场次数或登场次数",
"name": "count"
},
{
"comment": "棋子名称或棋子名字",
"name": "chessName"
}
],
"tableComment": "棋子信息",
"tableName": "chess"
},
{
"fields": [
{
"comment": "",
"name": "id"
},
{
"comment": "棋子名称,不是外键不用联合查询",
"name": "chessName"
},
{
"comment": "强化名称,不是外键不用联合查询",
"name": "hexName"
},
{
"comment": "平均排名",
"name": "place"
},
{
"comment": "前4率",
"name": "top4"
},
{
"comment": "第一名率(吃鸡率)",
"name": "won"
},
{
"comment": "上场 登场 上场次数 登场次数",
"name": "count"
}
],
"tableComment": "棋子强化数据表",
"tableName": "chess_hex"
},
{
"fields": [
{
"comment": "",
"name": "id"
},
{
"comment": "棋子名称,不是外键不用联合查询",
"name": "chessName"
},
{
"comment": "装备名称,不是外键不用联合查询",
"name": "itemName"
},
{
"comment": "平均排名",
"name": "place"
},
{
"comment": "前4率",
"name": "top4"
},
{
"comment": "第一名率(吃鸡率)",
"name": "won"
},
{
"comment": "登场次数",
"name": "count"
}
],
"tableComment": "棋子装备数据表",
"tableName": "chess_item"
},
{
"fields": [
{
"comment": "",
"name": "id"
},
{
"comment": "强化图片",
"name": "icon"
},
{
"comment": "强化描述",
"name": "introduce"
},
{
"comment": "强化英语名称",
"name": "enName"
},
{
"comment": "平均排名",
"name": "place"
},
{
"comment": "前4率",
"name": "top4"
},
{
"comment": "第一名率(吃鸡率)",
"name": "won"
},
{
"comment": "上场、登场、上场次数或登场次数",
"name": "count"
},
{
"comment": "强化名称",
"name": "hexName"
}
],
"tableComment": "强化信息",
"tableName": "hex"
},
{
"fields": [
{
"comment": "",
"name": "id"
},
{
"comment": "装备图片",
"name": "icon"
},
{
"comment": "装备名称",
"name": "name"
},
{
"comment": "装备描述",
"name": "introduce"
},
{
"comment": "装备英语名称",
"name": "enName"
},
{
"comment": "平均排名",
"name": "place"
},
{
"comment": "前4率",
"name": "top4"
},
{
"comment": "第一名率(吃鸡率)",
"name": "won"
},
{
"comment": "上场、登场、上场次数或登场次数",
"name": "count"
}
],
"tableComment": "装备信息",
"tableName": "item"
},
{
"fields": [
{
"comment": "",
"name": "id"
},
{
"comment": "职业图片",
"name": "icon"
},
{
"comment": "职业名称",
"name": "name"
},
{
"comment": "职业描述",
"name": "introduce"
},
{
"comment": "职业英语名称",
"name": "enName"
}
],
"tableComment": "棋子职业信息",
"tableName": "job"
},
{
"fields": [
{
"comment": "",
"name": "id"
},
{
"comment": "羁绊图片",
"name": "icon"
},
{
"comment": "羁绊名称",
"name": "name"
},
{
"comment": "羁绊英语名称",
"name": "enName"
},
{
"comment": "羁绊描述",
"name": "introduce"
}
],
"tableComment": "羁绊信息",
"tableName": "race"
}
]
}
}
插件已经上架到插件商店,可以使用数据库表结构分析
搜索使用该插件。
编写执行sql插件
插件源码
使用mysql2库动态执行sql
import { Args } from '@/runtime';
import { Input, Output } from "@/typings/sql_execute/sql_execute";
import mysql from 'mysql2/promise'
/**
* 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({ input }: Args<Input>): Promise<Output> {
if (!input.sql) {
return {
data: '' as any,
success: false,
message: 'sql不能为空',
}
}
const connection = await mysql.createConnection({
host: input.host,
user: input.user,
database: input.database,
password: input.password,
port: input.port,
});
try {
const [results] = await connection.query(
input.sql
);
connection.destroy();
return {
success: true,
data: results as any,
message: '成功'
};
} catch (err) {
return {
success: false,
data: [],
message: err.message,
};
}
};
输入参数
输出参数
插件已经上传到插件商店,可以使用sql执行器
搜索使用。
编写云顶数据搜索coze工作流
获取完表结构信息后,通过代码把用户输入和表结构信息处理一下,方便大模型识别。
这里加了一个分支,如果生成的sql,执行失败,把报错信息和sql传给大模型,让大模型根据报错信息自动修改或优化sql再执行。如果成功直接把结果输出出去。
bot配置
所有的逻辑都在工作流中,所以bot配置比较简单。
安全性
因为涉及到执行sql,如果用户输入删除棋子表或删除棋子表数据
,可能会把数据库中的表删除掉,我从两方面解决这个问题。
- 在生成sql的时候加限制
- 新建一个数据库用户,只给他分配select权限,delete,drop权限全部给限制了。
最后
大家如果对这个感兴趣,可以访问下面地址体验。
大家如果有建议,可以在评论区给我提需求,我后面会慢慢完善。
友情提醒:答案不一定完全正确,仅供参考。
Bot Id: 7370598857350348836