uniapp 开发微信小程序,对接 AI 讯飞星火大模型,websocket 实现打字效果,无限 token
...大约 4 分钟
uniapp 开发微信小程序,对接 AI 讯飞星火大模型,websocket 实现打字效果,无限 token
背景
使用 uniapp 开发的小程序,想对接 AI 大模型。目前这个时间点,2024 年 12 月,直接为前端提供 api 的大模型,有百度的文心一言和讯飞的星火大模型,这篇先记录下星火模型的对接方式。
1、申请星火模型所需 key
首先进入星火大模型 API,新用户要先注册。选择 Spark Lite,目前这个模型 token 是免费的。当然也可以选择其他更强大的模型。然后创建应用,获取到 APPID 和 APISecret、APIKey。
2、 在 uniapp 中进行配置
星火模型目前对前端,直接提供了 websocket 接口,可以查看文档。
下边是我自己封装的代码,可以满足我自己项目中对试题答案的解析需求,也可以满足类似微信那种直接对话框的需求。
//首先要安装crypto-js,再进行引入
import CryptoJS from '../src/static/crypto-js';
const httpUrl = 'https://spark-api.xf-yun.com/v1.1/chat'; // 如果是其他模型,需要修改
const modelDomain = 'lite'; // 如果是其他模型,需要修改
const APPID = ''; // 控制台获取填写
const APISecret = '';
const APIKey = '';
const sparkResult = '';
let socketTask = null;
// base64编码
function weBtoa(string) {
var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
string = String(string);
var bitmap,
a,
b,
c,
result = '',
i = 0,
rest = string.length % 3;
for (; i < string.length; ) {
if (
(a = string.charCodeAt(i++)) > 255 ||
(b = string.charCodeAt(i++)) > 255 ||
(c = string.charCodeAt(i++)) > 255
)
throw new TypeError(
"Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range."
);
bitmap = (a << 16) | (b << 8) | c;
result +=
b64.charAt((bitmap >> 18) & 63) +
b64.charAt((bitmap >> 12) & 63) +
b64.charAt((bitmap >> 6) & 63) +
b64.charAt(bitmap & 63);
}
return rest ? result.slice(0, rest - 3) + '==='.substring(rest) : result;
}
// 鉴权
async function getWebSocketUrl() {
// console.log(this.httpUrl);
var httpUrlHost = httpUrl.substring(8, 28);
var httpUrlPath = httpUrl.substring(28);
return new Promise((resolve, reject) => {
var url = 'wss://' + httpUrlHost + httpUrlPath;
var host = 'spark-api.xf-yun.com';
var apiKeyName = 'api_key';
var date = new Date().toGMTString();
var algorithm = 'hmac-sha256';
var headers = 'host date request-line';
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET ${httpUrlPath} HTTP/1.1`;
var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, APISecret);
var signature = CryptoJS.enc.Base64.stringify(signatureSha);
var authorizationOrigin = `${apiKeyName}="${APIKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
var authorization = weBtoa(authorizationOrigin);
url = `${url}?authorization=${authorization}&date=${encodeURI(
date
)}&host=${host}`;
resolve(url); // 主要是返回地址
});
}
// 调用ai接口
async function sendToSpark(
questionTitle,
callback,
temperature = 0.5, //核采样阈值,用于决定结果随机性,取值越高随机性越强,即相同的问题得到的不同答案的可能性越高。取值范围 (0,1],默认为0.5
historyTextList = []
) {
let myUrl = await getWebSocketUrl();
return new Promise((resolve, reject) => {
socketTask = uni.connectSocket({
url: myUrl,
method: 'GET',
success: (res) => {
// console.log(res, 'ws成功连接...', myUrl);
},
});
socketTask.onError((res) => {
// console.log('连接发生错误,请检查appid是否填写', res);
});
socketTask.onOpen((res) => {
historyTextList.push({
role: 'user',
content: questionTitle,
});
let params = {
header: {
app_id: APPID,
uid: 'aef9f963-7',
},
parameter: {
chat: {
domain: modelDomain,
temperature: temperature,
max_tokens: 4096,
},
},
payload: {
message: {
text: historyTextList,
},
},
};
// console.log(91, params)
socketTask.send({
data: JSON.stringify(params),
success() {
// console.log('第一帧发送成功');
},
});
});
// 接受到消息时
socketTask.onMessage((res) => {
// console.log('收到API返回的内容:', res.data);
let obj = JSON.parse(res.data);
let dataArray = obj.payload.choices.text;
for (let i = 0; i < dataArray.length; i++) {
callback(
dataArray[i].content,
obj.payload.choices.seq, //seq代表ai对同一个问题回答的第几帧,做ai对话可以用到
obj.payload.choices.status //status代表当前帧的返回状态,0代表正常,1代表需要继续等待,2代表已经返回最终结果。也是做ai对话的时候可以用的到
);
}
let temp = JSON.parse(res.data);
if (temp.header.code !== 0) {
// console.log(`${temp.header.code}:${temp.message}`);
socketTask.close({
success(res) {
console.log('关闭成功', res);
},
fail(err) {
console.log('关闭失败', err);
},
});
}
if (temp.header.code === 0) {
if (res.data && temp.header.status === 2) {
setTimeout(() => {
socketTask.close({
success(res) {
console.log('关闭成功', res);
},
fail(err) {
// console.log('关闭失败', err)
},
});
}, 1000);
}
}
});
});
}
export default sendToSpark;
调用的代码
// 调用ai接口
sendToSpark(
content, //用户提出的问题
(answer, seq, status) => {
if (seq == 0) {
// 第一帧
} else {
//中间帧
}
if (status == 2) {
//最后一帧
}
},
0.8, // 核采样阈值,用于决定结果随机性,取值越高随机性越强,即相同的问题得到的不同答案的可能性越高。取值范围 (0,1],默认为0.5。如果不是对话情景,可以不传
historyTextList //对话记录,如果不是对话情景,可以不传
);
注意
上边这个方法,就可以轻松实现打字效果,响应也很迅速。使用效果,跟咱们在浏览器上使用是一样的。而且他这个是完全不限 token 的。
但是缺点也很明显,星火这个 Spark Lite 模型,应对一些知识问答还不错,那种需要推理的情景可能就不太行了。比如问他今天星期几,可能就把他给难住了。但是问他计算机软考中级有什么类型,他又能轻松回答。具体使用什么模型,还是要根据自己业务场景来决定。