m***@163.com
m***@163.com
  • 发布:2023-06-08 20:46
  • 更新:2023-06-12 11:50
  • 阅读:492

sseChannel没有对完话就结束

分类:uniCloud
// 云对象教程: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj  
// jsdoc语法提示教程:https://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/129  
// 引入utils模块中的safeRequire和checkContentSecurityEnable函数  
const {safeRequire} = require('./utils')  
// 引入uni-config-center模块,并创建config对象  
const createConfig = safeRequire('uni-config-center')  
const config = createConfig({  
    pluginId: 'uni-ai-chat'  
}).config()  
// 引入uni-id-common模块  
const uniIdCommon = require('uni-id-common')  

module.exports = {  
    _before:async function() {  
        // 这里是云函数的前置方法,你可以在这里加入你需要逻辑  

        // 判断否调用量本云对象的send方法  
        if(this.getMethodName() == 'send'){           
            // 从配置中心获取内容安全配置  
            // console.log('config.contentSecurity',config.contentSecurity);  
            if (config.contentSecurity) {  
                // 引入uni-sec-check模块  
                const UniSecCheck = safeRequire('uni-sec-check')  
                // 创建uniSecCheck对象  
                const uniSecCheck = new UniSecCheck({  
                    provider: 'mp-weixin',  
                    requestId: this.getUniCloudRequestId()  
                })  
                // 定义文本安全检测函数  
                this.textSecCheck = async (content)=>{  
                    // 获取sseChannel  
                    let {sseChannel} = this.getParams()[0]||{}  
                    // 如果存在sseChannel,则抛出错误  
                    if(sseChannel){  
                        throw {  
                            errSubject: 'uni-ai-chat',  
                            errCode: "sec-check",  
                            errMsg: "流式响应模式,内容安全识别功能无效"  
                        }  
                    }  
                    // 检测文本  
                    const checkRes = await uniSecCheck.textSecCheck({  
                        // 文本内容,不可超过500KB  
                        content,  
                        // 微信小程序端 开放的唯一用户标识符  
                        // openid,  
                        // 场景值(1 资料;2 评论;3 论坛;4 社交日志)  
                        scene:1,  
                        // 接口版本号,可选1或2,但1的检测能力很弱  支持微信登录的项目,微信小程序端 可改用模式2 详情:https://uniapp.dcloud.net.cn/uniCloud/uni-sec-check.html#%E4%BD%BF%E7%94%A8%E5%89%8D%E5%BF%85%E7%9C%8B  
                        version:2   
                    })  
                    console.log('checkRes检测文本',checkRes);  
                    // 如果检测到风险内容,则抛出错误  
                    if (checkRes.errCode === uniSecCheck.ErrorCode.RISK_CONTENT) {  
                        console.error({  
                            errCode: checkRes.errCode,  
                            errMsg: '文字存在风险',  
                            result: checkRes.result  
                        });  
                        throw "uni-sec-check:illegalData"  
                        // 如果检测出错,则抛出错误  
                    } else if (checkRes.errCode) {   
                        console.log(`其他原因导致此文件未完成自动审核(错误码:${checkRes.errCode},错误信息:${checkRes.errMsg}),需要人工审核`);  
                        console.error({  
                            errCode: checkRes.errCode,  
                            errMsg: checkRes.errMsg,  
                            result: checkRes.result  
                        });  
                        throw "uni-sec-check:illegalData"  
                    }  
                }  

                // 获取messages参数  
                let {messages} = this.getParams()[0]||{"messages":[]}  
                // 将messages中的content拼接成字符串  
                let contentString = messages.map(i=>i.content).join(' ')  
                console.log('contentString',contentString);  
                // 对contentString进行文本安全检测  
                await this.textSecCheck(contentString)  
            }  
        }  
    },  
    async _after(error, result) {  
        // 打印错误和结果  
        // console.log('_after',{error,result});   
        // 如果有错误  
        if(error){   
            // 如果是内容安全检测错误  
            if(error.errCode == 60004 || error == "uni-sec-check:illegalData" ) {   
                // 返回一个包含敏感内容提示和标记的响应体  
                return {   
                    "data": {  
                        "reply": "内容涉及敏感",  
                        "illegal":true  
                    },  
                    "errCode": 0  
                }  
            }  
            // 其他符合响应体规范的错误,直接返回  
            else if(error.errCode && error.errMsg) {   
                return error  
            }else{   
                // 如果是其他错误  
                throw error  // 直接抛出异常  
            }  
        }  

        // 如果是send方法且开启了内容安全检测  
        if (this.getMethodName() == 'send' && config.contentSecurity) {   
            try{  
                // 对回复内容进行文本安全检测  
                await this.textSecCheck(result.data.reply)   
            }catch(e){   
                // 如果检测到敏感内容 返回一个包含敏感内容提示和标记的响应体  
                return {  
                    "data": {  
                        "reply": "内容涉及敏感",  
                        "illegal":true  
                    },  
                    "errCode": 0  
                }  
            }  
        }  
        // 返回处理后的结果  
        return result  
    },  

    // 发送消息  
    async send({  
        // 消息内容  
        messages,   
        // sse渠道对象  
        sseChannel   
    }) {  

        // 校验客户端提交的参数  
        // 检查消息是否符合规范  
        let res = checkMessages(messages)  
        if (res.errCode) {  
            throw new Error(res.errMsg)  
        }  

        // 向uni-ai发送消息  
        // 调用chatCompletion函数,传入messages、sseChannel、llm参数  
        let {llm,chatCompletionOptions} = config  
        return await chatCompletion({  
            messages, //消息内容  
            sseChannel, //sse渠道对象  
            llm  
        })  

        // chatCompletion函数:对话完成  
        async function chatCompletion({  
            // 消息列表  
            messages,   
            // 是否需要总结  
            summarize = false,   
            // sse渠道对象  
            sseChannel = false,   
            // 语言模型  
            llm   
        }) {  
            // 获取语言模型管理器  
            const llmManager = uniCloud.ai.getLLMManager(llm)  
            // 调用chatCompletion方法,传入参数  
            let res = await llmManager.chatCompletion({  
                ...chatCompletionOptions,  
                messages,  
                stream: sseChannel !== false  
            })  
            // 如果存在sseChannel  
            if (sseChannel) {  
                let reply = ""   
                return new Promise((resolve, reject) => {  
                    // 反序列化sseChannel  
                    const channel = uniCloud.deserializeSSEChannel(sseChannel)  
                    // 按字返回  
                    res.on('message', async (message) => {  
                        // console.log('---message----', message)  
                        reply += message  
                        await channel.write(message)  

                    })  
                    // 结束返回  
                    res.on('end', async () => {  
                        console.log('---end----结束返回',reply)  
                        // 将回复内容添加到消息列表中  
                        messages.push({  
                            "content": reply,  
                            "role": "assistant"  
                        })  

                        await channel.end()  
                        // 返回处理结果  
                        resolve({  
                            errCode: 0  
                        })  
                    })  
                    // 返回错误  
                    res.on('error',async (error) => {  
                        console.error('---error----', error)  
                        // 特殊处理 uni-ai默认服务商检测到内容涉及敏感的错误  
                        if(error.errCode == "60004"){  
                            await channel.write("内容涉及敏感")  
                            // 结束sseChannel并返回 illegal:true 表示内容涉及敏感  
                            await channel.end({  
                                illegal: true  
                            })  
                            return resolve({  
                                errCode: 0  
                            })  
                        }  
                        reject(error)  
                    })  
                })  
            } else {  
                console.log('不存在sseChannel');  
                // 如果不需要总结  
                if (summarize == false) {  
                    // 将回复内容添加到消息列表中  
                    messages.push({  
                        "content": res.reply,  
                        "role": "assistant"  
                    })  
                }  
                // 如果存在错误  
                if(res.errCode){  
                    // 抛出错误  
                    throw res  
                }  
                // 返回处理结果  
                return {  
                    data:res,  
                    errCode: 0  
                }  
            }  
        }  

        /**  
         * 校验消息内容是否符合规范  
         * @param {Array} messages - 消息列表  
         * @returns {Object} - 返回校验结果  
         */  
        function checkMessages(messages) {  
            try {  
                // 如果messages未定义  
                if (messages === undefined) {   
                    // 抛出异常  
                    throw "messages为必传参数"   
                // 如果messages不是数组  
                } else if (!Array.isArray(messages)) {   
                    // 抛出异常  
                    throw "参数messages的值类型必须是[object,object...]"   
                } else {   
                    // 否则 遍历messages  
                    messages.forEach(item => {   
                        // 如果item不是对象  
                        if (typeof item != 'object') {   
                            // 抛出异常  
                            throw "参数messages的值类型必须是[object,object...]"   
                        }  
                        // 定义itemRoleArr数组  
                        let itemRoleArr = ["assistant", "user", "system"]   
                        // 如果item的role属性不在itemRoleArr数组中  
                        if (!itemRoleArr.includes(item.role)) {   
                            // 抛出异常  
                            throw "参数messages[{role}]的值只能是:" + itemRoleArr.join('或')   
                        }  
                        // 如果item的content属性不是字符串  
                        if (typeof item.content != 'string') {   
                            // 抛出异常  
                            throw "参数messages[{content}]的值类型必须是字符串"   
                        }  
                    })  
                }  
                // 返回校验结果  
                return {   
                    errCode: 0,  
                }  
            // 捕获异常  
            } catch (errMsg) {   
                // 返回异常信息  
                return {   
                    errSubject: 'ai-demo',  
                    errCode: 'param-error',  
                    errMsg  
                }  
            }  
        }  
    }  
}
// 初始化sse通道  
let sseChannel = false  
    // 引入markdown-it库  
    import MarkdownIt from '@/lib/markdown-it.min.js';  

    // hljs是由 Highlight.js 经兼容性修改后的文件,请勿直接升级。否则会造成uni-app-vue3-Android下有兼容问题  
    import hljs from "@/lib/highlight/highlight-uni.min.js";  

    const md = new MarkdownIt({  
      breaks: true, // 设置为true,即可实现冒号、分号、句号换行的效果  
      highlight: function (str, lang) {  
        if (lang && hljs.getLanguage(lang)) {  
          try {  
            return '<pre class="hljs"><code class="hljs '+lang+'">' +  
                   hljs.highlight(lang, str, true).value +  
                   '</code></pre>';  
          } catch (__) {}  
        }  

        return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>';  
      },  
      html: true  
    })  

export default {  
    data(){  
        return{  
            // 记录流式响应次数  
            sseIndex: 0,  
            lastMsg:{  
                content:'',  
                markdownHtml:null  
            }  
        }  
    },  
    methods:{  
        async setGPT(msg){  
            sseChannel = new uniCloud.SSEChannel() // 创建消息通道  
            sseChannel.on('message', message => {  
                // 监听message事件  
                console.log('on message', message)  
                this.lastMsg.content += message  
                let html = md.render(this.lastMsg.content)  
                this.lastMsg.markdownHtml = html  
                // console.log(this.lastMsg.markdownHtml);  
                // 让流式响应计数值递增  
                this.sseIndex++  
            })  
            sseChannel.on('end', message => {  
                // 监听end事件,如果云端执行end时传了message,会在客户端end事件内收到传递的消息  
                console.log('on end', message)  
                // 结束流式响应 将流式响应计数值 设置为 0  
                this.sseIndex = 0  
            })  
            await sseChannel.open() // 等待通道开启  
            const chatgpt = uniCloud.importObject('hszy-chatgpt', {  
                customUI: true  
            })  
            chatgpt  
                .send({  
                    messages: msg,  
                    sseChannel  
                })  
                .then(res => {  
                    console.log(res)  
                    if (!sseChannel) {  
                        if (!res.data) {  
                            return  
                        }  
                        console.log(res, res.reply)  
                    } else {  
                        // 处理 sseChannel没结束 云函数提前结束的情况  
                        sseChannel.close()  
                        this.sseIndex = 0  
                    }  
                })  
                .catch(() => {  
                    sseChannel.close()  
                    this.sseIndex = 0  
                })  
        }  
    }  
}

从uni-ai的基础上,写了一个minxins,用来实现一问一答,SSEChannel总是没有对完话就执行
res.on('end', async () => {
console.log('---end----结束返回',reply),
两次会复现一次,百分之五十的概率

2023-06-08 20:46 负责人:无 分享
已邀请:

最佳回复

DCloud_uniCloud_WYQ

DCloud_uniCloud_WYQ

经排查,此问题是由于ai总结耗时导致了云函数执行超时未返回。uni-ai-chat已发布新版(1.1.1)优化此问题

m***@163.com

m***@163.com (作者)

响应一半直接中断执行res.on('end'),和请求时,system里面填充的提示词太多有关联吗?

DCloud_uniCloud_WYQ

DCloud_uniCloud_WYQ

AI响应多少个字的时候触发的end,只有云端调用了end,客户端才会触发end事件

  • m***@163.com (作者)

    之前这个问题“响应一半直接中断执行res.on('end')”,改了下tokensToGenerate,已经解决了,但又遇到了新的问题,在云端服务器,日志里面可以看到,响应的全文chatgpt都已经全部展示了,但向客户端推送了一半就停止了,直接去执行了函数的res,可看下图

    2023-06-09 14:36

m***@163.com

m***@163.com (作者)

给客户端返回了一半数据

要回复问题请先登录注册