这是云对象中的代码
// 流式输出的ai调用
async streamAi(obj, sseChannel) {
// 根据类型拼接提示词
let messages
switch (obj.type) {
case 'grammar':
messages = grammarMsg(obj.msg)
break
case '问答':
messages = qaMsg(obj.question, obj.msg)
break
case '翻译':
messages = translatorMsg(obj.question, obj.msg)
break;
case 'word':
messages = wordMsg(obj.msg)
break;
}
// AI对象
const LLMManager = uniCloud.ai.getLLMManager({
provider: 'openai',
apiKey: '*****',
proxy: '*****'
})
// 调用 ai 开通 stream
let res = await LLMManager.chatCompletion({
messages, // 传给 openai api 的信息
sseChannel, // 从前端传过来的 sseChannel 通道
stream: true // 开通流式数据
})
if (res) {
let reply = "" // 这个是用来拼接 openai 返回的数据,跟返回客户端的没有关系,用来保存到数据库
return new Promise((resolve, reject) => { //流式给客户端返回数据
const channel = uniCloud.deserializeSSEChannel(sseChannel) // 这个不知道干啥,反正通过这个得到 channel 的通道
// 注意这里,openai 是一个字一个字返回的,有些ai是一句一句返回的,还有一人 on('line') 的事件,是用来接收一句一句返回的。我用的是openai所有没有写那个 line 事件。
res.on('message', async (message) => { // openai 返回数据时 会触发 这个 message 回调 在这里向客户端一点点发送数据。
reply += message
await channel.write(message) // 这个 write 会向客户端发送数据 openai 是一个字一个字发送
// console.log('---message----', message)
})
res.on('end', async () => { // 发送完毕后 会触发这个事件 这里可以 做一些其他操作
// 保存用户内容 这个不用看,是把上面 reply 拼接的数据保存到数据库
if (obj.type == "问答" || obj.type == "翻译") {
// 把我的回答保存到数据库
let res = await dbJql.collection('bl_answer').where(
`userId=="${obj.id}"&&contentId=="${obj.contentId}"`).get()
if (res.data.length == 1) {
dbJql.collection('bl_answer').where(
`userId=="${obj.id}"&&contentId=="${obj.contentId}"`)
.update({
myanswer: obj.msg,
})
} else {
dbJql.collection('bl_answer').add({
myanswer: obj.msg,
contentId: obj.contentId,
userId: obj.id
})
}
}
// 输出结束后
await channel.end(points) // 这个 end 是告诉前端客户端数据发送结束了,也可以带一个参数,跟 return 差不多。
resolve({
errCode: 0
})
})
res.on('error', (err) => {
console.error('---error----', err)
reject(err)
})
})
}
}
这是前端代码
async onAi() {
// 这是提交给后端的数据
let contentObj = {
msg: this.msg, //这个是关键数据,就是你要发送给 openai 的内容
id: this.$props.user._id,
type: this.$props.data.type,
points: this.$props.data.points,
contentId: this.$props.data._id._value,
question: this.$props.data.question,
}
// ai 调用 这个是正常的云对象调用
// const aiReplyDB = uniCloud.importObject('aiReplyObj')
// let res = await aiReplyDB.ai(contentObj)
// 流式 ai 调用
let sseChannel = new uniCloud.SSEChannel() // 这个就是提交给云对象的通道,通过这个接收数据。
sseChannel.on('message', (message) => { // 这个跟后端的一样,后面 write 的时候 会触发这个 message 事件 用来接收后端发送过来的数据
// console.log(message)
this.aiReply += message // 这里把数据显示到界面上,就会出现打字机的效果(一点一点输出内容)
})
sseChannel.on('end', (points) => { //后端的 end() 会触发这个 end 做一些其他的操作。
// uni.hideLoading()
// console.log(points)
let resData = {
points: points,
answer: this.msg
}
this.curMsg = this.msg
//回调父组件函数e
this.$emit('onAi', resData) // 这个是组件回调给父组件的函数,这个onAi是父组件的函数,这里和上面的 onAi名字写成一样的了,跟上面的没关系了。
})
await sseChannel.open() // 这个是打开通道(上面两个 sseChannel.on() 是绑定事件,是在返回数据的时候执行。并不是执行了上面的sseChannel.on()再来执行这个 sseChannel.open() )
// 上面的都是准备,这里才开始调用云对象
const streamAi = uniCloud.importObject('aiReplyObj')
streamAi.streamAi(contentObj, sseChannel).then(res => {
// console.log("then", res)
sseChannel.close() // 这个代码是我自己加的,感觉还是要把之前的 open 给关闭掉。
})
}
这些是前后端实现一个简单的流式数据请求的代码。在这之前,还要开通 uniPush 2.0 这个内容的文档写的还可以,能看明白。
https://zh.uniapp.dcloud.io/unipush-v2.html#%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B
对了,有一个很重要的事情,我写完代码的时候有一个错误提示:没有这张表:opendb-tempdata。 所以数据库里要新建一张叫:opendb-tempdata 的数据表。保存uniPush 的ID 好像是。
0 个评论
要回复文章请先登录或注册