前言
coze出来后,我就一直在关注,做了一些小应用,参加了一些活动,得了一些小奖,算是积累了一些使用心得。
有天在某个群里看一个官方发布的活动,奖励挺吸引人的,并且当时我也有一些想法,所以就报着试试的心态参加了。
那段时间,每天下班都搞的很晚,有两天3点多睡的,第二天还继续上班,不过功夫不负有心人,最终得奖了。
体验地址:智能分析助手
得奖后有不少人私聊我,希望能把使用的技术分享一下,所以下面就和大家分享一下智能分析助手技术实现。
阅读本文需要有coze基础,建议大家可以先看下官方文档入一下门。
功能演示
excel数据分析
- 先准备一个excel
- 到智能分析助手里上传excel
- 上传成功后,大模型会根据你的数据,为你生成三条推荐问题
- 点击其中一个问题,大模型会先回答你的问题
- 然后根据你的问题答案,智能生成图表和数据解析。
- 根据上一个问题,再为你推荐三个问题。
- 如果你想自己问问题,也是可以的
不知道大家有没有发现,我准备的excel表里数据是没有利润字段的,但是这里给动态计算出来了,拿销售额减去成本。
我在excel里写了一个公式试了一下,计算结果都是对的。
- 在浏览器中查看大图
这里的图表渲染成svg格式,所以无论放大还是缩小不会模糊。
数据库数据分析
- 准备数据库数据,这里我使用的是从云顶之弈大数据网站爬下来的棋子数据
- 输入数据库信息,连接数据库
- 连接数据库成功后和excel分析流程一样会生成三个推荐问题
- 如果不想使用推荐问题,也可以输入自己的问题
为什么做智能分析
为什么要做智能分析呢,因为我们公司就是做分析的,我对分析的流程比较熟悉。
不过我们公司做的分析还是靠人工拖字段的方式生成图表,对不熟悉系统的人来说,还是有一点上手成本的。
我做的智能分析助手,更偏向于大众使用,每个人只要上传excel,通过问问题的方式,就能得到自己的答案,基本没有啥上手成本。
整体实现思路
第一版
- 写一个插件根据用户上传的excel,解析出excel里的内容,转换为二维数组格式。
- 把excel数据和用户问题传给大模型,让大模型根据问题返回数据,并使用大模型解析问题中维度字段和指标字段并返回。
- 根据用户问题以及上一步返回的数据,让大模型生成答案返回给用户
- 根据维度字段和指标字段以及数据,使用代码组装成图表渲染插件的参数渲染图表。
- 把excel数据传给大模型让大模型生成三个推荐问题
这是我最开始的实现思路,但是在实战中发现2个问题没办法解决,所以放弃了这个方案
- 如果excel数据量太大,把整个excel数据传给大模型,会超出大模型的字数限制。
- 大模型不适合做计算,假如我要算汇总数据,大模型可以把式子准确的列出来,但是最后计算结果是错的,试了好几个大模型计算结果都是错的。
第二版
- 写一个插件根据用户上传的excel,解析出excel里的内容,转换为二维数组格式。
- 根据二维数组动态生成表,第一行是表字段,后面行都是表数据。
- 根据用户问题和表字段,让大模型生成查询sql。
- 执行查询sql,查询数据
- 根据用户问题和查询出来的数据,让大模型生成答案返回给用户。
- 根据用户问题和查询出来的数据,生成echarts图表渲染参数。(这个真是惊喜了,没想到大模型这么强,生成的参数准确率很高,可以直接使用。本来我都打算自己拼接参数的。)
- 根据表字段让大模型为用户生成三个推荐问题
技术实现
提示词
提示词很简单,就是根据用户不同输入调用不同的工作流
数据库
因为上传完excel后,还要支持持续对话,所以需要定义一个数据库存放配置信息。
工作流
创建了三个工作流,这三个工作流是这个应用的核心。
解析excel数据,生成推荐问题
工作流整体设计
开始节点
这里有不少人疑惑,不知道怎么获取用户上传的excel,其实很简单。coze调用工作流的时候,会把上传的excel连接,传给工作流。
只需要在开始节点定义一个参数接连接就行了
下载excel并把数据转换为数据库表
这一步本来打算使用coze里的插件实现的,虽然coze插件功能很强大,基本可以实现需求。可惜coze里的数据库不支持动态创建表,我只能自己写一个后端服务,然后在工作流里调用自己的服务。
这里的服务框架使用的是node中的midway框架,数据库orm使用的typeorm。
创建一个接口,通过excel url下载excel,读取excel数据转换为二维数组,校验数据,动态创建表,插入数据。
校验数据
调用typeorm里的方法动态创建表,中间有段数据校验代码省略没截出来
往新建的表里插入数据,最后把刚创建的表名和字段列表返回出去
下载excel方法实现
/**
* 下载Excel文件
*
* @param downloadPath 下载路径
* @returns 返回包含Excel文件内容的Buffer对象,若下载失败则返回Promise的rejected状态
*/
export function downloadExcel(downloadPath: string) {
return new Promise((resolve, reject) => {
let dataBuffer = Buffer.from([]);
https
.get(downloadPath, response => {
response.on('data', chunk => {
dataBuffer = Buffer.concat([dataBuffer, chunk]);
});
response.on('end', () => {
resolve(dataBuffer);
});
})
.on('error', err => {
reject(err);
});
});
}
把excel数据转换为二维数组方式实现,使用xlsx库解析excel数据。
/**
* 将 Excel 表格数据转换为 JSON 格式
*
* @param buffer Excel 文件的二进制数据
* @param type Excel 文件类型,可选值为 'csv' 或其他 xlsx 支持的文件类型
* @returns 转换后的 JSON 数据,以二维数组形式返回
*/
export function excelToJson(buffer: any, type: string): string[][] {
let workbook;
if (type === 'csv') {
const decodedData = iconv.decode(buffer, 'utf8');
workbook = xlsx.read(decodedData, { type: 'string' });
} else {
workbook = xlsx.read(buffer, { type: 'buffer' });
}
// 获取 Excel 第一个工作表的名称
const sheetName = workbook.SheetNames[0];
// 通过工作表名称获取数据
const sheet = workbook.Sheets[sheetName];
if (sheet['!merges']) {
return;
}
// 将 Excel 表格数据转换为 JSON 格式
const excelData = xlsx.utils.sheet_to_json(sheet, { header: 1 }) as any;
return excelData;
}
回到coze工作流中,在开始节点后添加一个代码节点,调用刚写好的接口
往coze数据库中插入配置信息
后面添加一个选择器,判断excel是否解析成功,成功后删除上一次的配置,把当前表名插入进去。如果失败,则输出失败消息。
生成3个推荐问题
接着使用大模型根据表字段生成三个推荐问题,因为返回的表字段是数组,所以先用代码处理成字符串。
大模型的系统提示词
在用一个节点,把大模型返回的数据格式化一下。
结束节点配置
最后处理错误信息,以及结束节点配置,把错误信息和三个推荐问题变量返回出去
使用卡片展示结果
创建卡片,拖三个按钮和一个文字节点进画布,给三个按钮分别绑定发送消息动作,消息内容就是显示的文字,文字节点绑定消息变量。
结束节点绑定卡片
配置快捷指令
选择刚才新建的工作流,自动生成模板
最后效果
连接数据库,生成推荐问题
总览
解析数据库连接信息
使用大模型把用户输出的数据库连接信息转换为json数据
校验输入的连接信息,是否有些字段为空
写一个coze插件,测试连接信息是否准确,并查询当前表有哪些字段。
使用mysql2库
import { Args } from '@/runtime';
import { Input, Output } from "@/typings/check_database_link/check_database_link";
import mysql, { createConnection, 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> {
try {
const connection = await createConnection(input);
// 查询数据库中有哪些表
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 table = results.find(o => o.tableName === input.table);
if (!table) {
return {
success: false,
msg: `当前表(${input.table})不存在,请检查是否输入错误。`,
}
}
const tableInfo = await getTableInfo(connection, input.table, table.tableComment);
connection.destroy();
return {
success: true,
msg: '测试成功',
tableComment: tableInfo.tableComment,
tableName: tableInfo.tableName,
fields: tableInfo.fields,
}
} catch (error) {
return {
success: false,
msg: error.message,
}
}
};
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,
};
}
测试成功后,把用户输入的数据库信息存入数据库
根据表名和表字段让大模型生成推荐问题
大模型的系统提示词
处理错误消息和配置结束节点
绑定卡片
这一步和excel一样,省略了。
快捷指令
把变量插入到指令内容中
最终效果
回答用户问题,智能生成图表
工作流总览
查询数据库,获取当前配置信息
根据不同的配置类型,返回数据库配置信息
如果类型是excel,数据库信息就是我服务器里的数据库信息,写死的。
如果类型是database,数据库信息就是用户自己输入的数据库信息。
数据库节点有个地方要注意一下,查询出来的字段需要在输出那里配置一下,不然后面取不到值。
添加选择器,如果没查询到配置信息,提示用户上传excel或连接数据库
写一个插件,根据数据库连接信息获取表结构
插件代码
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,
};
}
根据数据库表结构和用户输入的问题,使用大模型生成sql。
先使用代码节点格式化一个表结构信息,然后传给大模型。
大模型提示词
这里限制只能生成查询语句,防止误删数据。
写一个coze插件,动态执行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, logger }: 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,
});
// A simple SELECT query
try {
await connection.execute("SET SESSION sql_mode = ''");
const [results] = await connection.query(
input.sql
);
connection.destroy();
return {
success: true,
data: JSON.stringify(results),
message: '成功'
};
} catch (err) {
return {
success: false,
data: "[]",
message: err.message,
};
}
};
当sql执行失败,把错误信息和sql传给大模型修复一下,然后再次执行。
在测试的时候,大模型生成的sql,有时候会出错,这里为了保险起见,支持再次生成sql,重新执行。
把sql查询出来的数据和用户的问题传给大模型,让大模型生成答案
大模型提示词
让大模型生成图表渲染参数和对数据进行分析
调用接口生成图表和大图
coze官方出了一个渲染图表插件,我没使用这个,是因为它生成的图表很模糊,所以我自己写一个生成svg格式的图表接口,原理是echarts服务端渲染。
生成大图使用的是svgdom库,使用node渲染svg,把图片和文本还是解析的内容合并到一个svg中,然后返回链接,用户点击链接可以在浏览器中查看。
服务端接口代码
格式化输出内容
使用代码节点格式化输出内容,因为coze的输出支持markdown格式,所以我这里就给格式化成markdown格式。
生成推荐问题
回答完用户当前问题,使用大模型根据当前问题和表格再生成三个推荐问题
绑定卡片
和前面两个工作流一样,给结束节点绑定推荐问题卡片。
效果预览
大图
这个工作流里还有一些校验和错误信息处理,因为不太重要,又比较多,我给省略了,所以大家看的可能觉得有点不连贯,大家见谅。
总结
到此整个应用使用到的所有技术都讲完了,如果对你有帮助,帮忙点个赞鼓励一下吧😊。
关于coze,说一下我的感想,我觉得我现在前端技术已经陷入了瓶颈,并且整个大环境对前端也不太友好,所以我想换个赛道试试。
开发coze应用算是我的一个尝试,个人感觉coze最后不一定能大火,但是这种模式未来肯定会成为热门,让普通人也可以做出AI应用,这次参赛获奖作品,有不少人都不是程序员,但是借助coze平台也做出好用的AI应用。
最后举个例子:
昨天和老婆孩子还有老婆的妹妹一起去吃饭,吃过饭服务员说在大众点评上写点评,会送甜品。
我老婆的妹妹和我们说,你们不知道怎么写点评,可以使用文心一言帮忙写。
我当时很诧异,她竟然也知道文心一言,她的职业是幼师,我一直以为这些大模型只有一些技术人员知道,没想到普通人都已经开始使用了,有点孤陋寡闻了。
通过这个例子我想说明,AI已经进入普通人的视野了,AI应用未来可能使用的人会越来越多,因为它真的能帮助人们解决一些问题,而AI应用开发会不会是下一个风口,这个我不知道,但还是先做一些准备吧。