d***@dufan.cool
d***@dufan.cool
  • 发布:2023-05-18 02:36
  • 更新:2023-05-18 02:36
  • 阅读:2063

uni-ai 实现一个简单的流式响应代码

分类:uni-app

这是云对象中的代码

// 流式输出的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 关注 分享

要回复文章请先登录注册