From 8ba709aa3d91be53377a9c234fb7e41f8b49229b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B9=81=E6=98=9F=5F=E9=80=90=E6=A2=A6?= Date: Wed, 4 Dec 2024 15:13:31 +0800 Subject: [PATCH 1/2] =?UTF-8?q?modify:=20=E9=80=82=E9=85=8D=E8=AE=AF?= =?UTF-8?q?=E9=A3=9E=E6=98=9F=E7=81=AB=E6=A8=A1=E5=9E=8B4.0=E5=A4=9A?= =?UTF-8?q?=E4=B8=AA=E6=A8=A1=E5=9E=8B=EF=BC=8C=E6=96=B0=E5=A2=9E=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89prompt=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/xunfei/xunfei.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/xunfei/xunfei.js b/src/xunfei/xunfei.js index d9f343c..c00e589 100755 --- a/src/xunfei/xunfei.js +++ b/src/xunfei/xunfei.js @@ -10,7 +10,10 @@ const appID = env.XUNFEI_APP_ID const apiKey = env.XUNFEI_API_KEY const apiSecret = env.XUNFEI_API_SECRET // 地址必须填写,代表着大模型的版本号!!!!!!!!!!!!!!!! -const httpUrl = new URL('https://spark-api.xf-yun.com/v3.5/chat') +const httpUrl = new URL('https://spark-api.xf-yun.com/v4.0/chat') + +// 新增用户自定义prompt设定 +const prompt = env.XUNFEI_PROMPT let modelDomain // V1.1-V3.5动态获取,高于以上版本手动指定 function authenticate() { @@ -29,6 +32,15 @@ function authenticate() { case '/v3.5/chat': modelDomain = 'generalv3.5' break + case '/chat/pro-128k': + modelDomain = 'pro-128k' + break + case '/chat/max-32k': + modelDomain = 'max-32k' + break + case '/v4.0/chat': + modelDomain = '4.0Ultra' + break } return new Promise((resolve, reject) => { @@ -77,8 +89,9 @@ export async function xunfeiSendMsg(inputVal) { // 如果想获取结合上下文的回答,需要开发者每次将历史问答信息一起传给服务端,如下示例 // 注意:text里面的所有content内容加一起的tokens需要控制在8192以内,开发者如有较长对话需求,需要适当裁剪历史信息 text: [ - { role: 'user', content: '你是谁' }, //# 用户的历史问题 - { role: 'assistant', content: '你是一个专业的智能助手' }, //# AI的历史回答结果 + // { role: 'user', content: '你是谁' }, //# 用户的历史问题 + { role: 'system', content: prompt }, //# 自行设定的prompt + // { role: 'assistant', content: '你是一个专业的智能助手' }, //# AI的历史回答结果 // ....... 省略的历史对话 { role: 'user', content: inputVal }, //# 最新的一条问题,如无需上下文,可只传最新一条问题 ], From f759df936880276c11b9fa90ed20f13c19ea8b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B9=81=E6=98=9F=5F=E9=80=90=E6=A2=A6?= Date: Fri, 20 Dec 2024 09:49:20 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E8=AE=AF=E9=A3=9E=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?prompt=E5=85=BC=E5=AE=B9=E5=A4=84=E7=90=86,model=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E6=94=BE=E5=85=A5.env=E5=8A=A8=E6=80=81=E8=8E=B7?= =?UTF-8?q?=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 4 + src/xunfei/xunfei.js | 205 +++++++++++++++++++++---------------------- 2 files changed, 104 insertions(+), 105 deletions(-) diff --git a/.env.example b/.env.example index dcf3f54..7875c3a 100644 --- a/.env.example +++ b/.env.example @@ -13,6 +13,10 @@ KIMI_API_KEY='' XUNFEI_APP_ID='' XUNFEI_API_KEY='' XUNFEI_API_SECRET='' +# 使用的模型版本,默认填写 v4.0 或需要的版本号(如: v3.5, max-32k, pro-128k),参考src/xunfei.js中modelVersionMap +XUNFEI_MODEL_VERSION='v4.0' +# 系统角色描述,支持个性化定制 +XUNFEI_PROMPT='你是一个专业的智能助手,能够回答用户提出的各种问题。' # deepseek-free, model必须为deepseek-chat或deepseek-coder,去 https://platform.deepseek.com/usage或者https://github.com/LLM-Red-Team/deepseek-free-api # 在DEEPSEEK_SYSTEM_MESSAGE中设置系统提示词 diff --git a/src/xunfei/xunfei.js b/src/xunfei/xunfei.js index c00e589..41ea228 100755 --- a/src/xunfei/xunfei.js +++ b/src/xunfei/xunfei.js @@ -1,81 +1,90 @@ -import CryptoJS from 'crypto-js' -import dotenv from 'dotenv' -import WebSocket from 'ws' - -const env = dotenv.config().parsed // 环境参数 -// APPID,APISecret,APIKey在https://console.xfyun.cn/services/cbm这里获取 -// 星火认知大模型WebAPI文档:https://www.xfyun.cn/doc/spark/Web.html -// SDK&API错误码查询:https://www.xfyun.cn/document/error-code?code= -const appID = env.XUNFEI_APP_ID -const apiKey = env.XUNFEI_API_KEY -const apiSecret = env.XUNFEI_API_SECRET -// 地址必须填写,代表着大模型的版本号!!!!!!!!!!!!!!!! -const httpUrl = new URL('https://spark-api.xf-yun.com/v4.0/chat') - -// 新增用户自定义prompt设定 -const prompt = env.XUNFEI_PROMPT - -let modelDomain // V1.1-V3.5动态获取,高于以上版本手动指定 -function authenticate() { - // console.log(httpUrl.pathname) - // 动态获取domain信息 - switch (httpUrl.pathname) { - case '/v1.1/chat': - modelDomain = 'general' - break - case '/v2.1/chat': - modelDomain = 'generalv2' - break - case '/v3.1/chat': - modelDomain = 'generalv3' - break - case '/v3.5/chat': - modelDomain = 'generalv3.5' - break - case '/chat/pro-128k': - modelDomain = 'pro-128k' - break - case '/chat/max-32k': - modelDomain = 'max-32k' - break - case '/v4.0/chat': - modelDomain = '4.0Ultra' - break +import CryptoJS from "crypto-js"; +import dotenv from "dotenv"; +import WebSocket from "ws"; + +const env = dotenv.config().parsed; // 环境参数 + +// APPID,APISecret,APIKey在 https://console.xfyun.cn/services/cbm 获取 +const appID = env.XUNFEI_APP_ID; +const apiKey = env.XUNFEI_API_KEY; +const apiSecret = env.XUNFEI_API_SECRET; + +// 地址必须填写,代表着大模型的版本号 +const modelVersion = env.XUNFEI_MODEL_VERSION || "v4.0"; // 默认值 "v4.0" +const httpUrl = new URL(`https://spark-api.xf-yun.com/${modelVersion}/chat`); + +// 判断 prompt 是否存在,如果不存在则使用默认值 +const prompt = env.XUNFEI_PROMPT || "你是一个专业的智能助手"; + +// 动态映射模型版本到 domain 的逻辑 +const modelVersionMap = { + "v1.1": "general", + "v2.1": "generalv2", + "v3.1": "generalv3", + "v3.5": "generalv3.5", + "pro-128k": "pro-128k", + "max-32k": "max-32k", + "v4.0": "4.0Ultra", +}; + +// 获取模型域名 +function getModelDomain(httpUrl) { + try { + const modelPath = httpUrl.pathname.split("/")[1]; // 提取版本号或模型路径 + return modelVersionMap[modelPath] || "unknown"; // 如果没有匹配,返回 "unknown" + } catch (error) { + console.error("获取模型域名失败:", error); + return "unknown"; } +} + +let modelDomain = getModelDomain(httpUrl); +// 签名生成逻辑(可复用) +function generateSignature(httpUrl, apiKey, apiSecret) { + const host = "localhost:8080"; + const date = new Date().toGMTString(); + const algorithm = "hmac-sha256"; + const headers = "host date request-line"; + + const signatureOrigin = `host: ${host}\ndate: ${date}\nGET ${httpUrl.pathname} HTTP/1.1`; + const signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret); + const signature = CryptoJS.enc.Base64.stringify(signatureSha); + + const authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`; + const authorization = btoa(authorizationOrigin); + + const url = `wss://${httpUrl.host}${httpUrl.pathname}?authorization=${authorization}&date=${date}&host=${host}`; + return url; +} + +// 获取 WebSocket 地址 +function authenticate() { return new Promise((resolve, reject) => { - let url = 'wss://' + httpUrl.host + httpUrl.pathname - - let host = 'localhost:8080' - let date = new Date().toGMTString() - let algorithm = 'hmac-sha256' - let headers = 'host date request-line' - let signatureOrigin = `host: ${host}\ndate: ${date}\nGET ${httpUrl.pathname} HTTP/1.1` - let signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret) - let signature = CryptoJS.enc.Base64.stringify(signatureSha) - let authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"` - let authorization = btoa(authorizationOrigin) - url = `${url}?authorization=${authorization}&date=${date}&host=${host}` - resolve(url) - }) + try { + const url = generateSignature(httpUrl, apiKey, apiSecret); + resolve(url); + } catch (error) { + console.error("认证失败:", error); + reject(error); + } + }); } +// 发送消息并处理 WebSocket 逻辑 export async function xunfeiSendMsg(inputVal) { // 获取请求地址 - let myUrl = await authenticate() - let socket = new WebSocket(String(myUrl)) - let total_res = '' // 请空回答历史 + let myUrl = await authenticate(); + let socket = new WebSocket(String(myUrl)); + let total_res = ""; // 清空回答历史 // 创建一个Promise let messagePromise = new Promise((resolve, reject) => { - // 监听websocket的各阶段事件 并做相应处理 - socket.addEventListener('open', (event) => { - // console.log('socket开启连接', event); - // 发送消息 - let params = { + socket.addEventListener("open", () => { + const params = { header: { app_id: appID, - uid: 'fd3f47e4-d', + uid: "fd3f47e4-d", }, parameter: { chat: { @@ -86,53 +95,39 @@ export async function xunfeiSendMsg(inputVal) { }, payload: { message: { - // 如果想获取结合上下文的回答,需要开发者每次将历史问答信息一起传给服务端,如下示例 - // 注意:text里面的所有content内容加一起的tokens需要控制在8192以内,开发者如有较长对话需求,需要适当裁剪历史信息 text: [ - // { role: 'user', content: '你是谁' }, //# 用户的历史问题 - { role: 'system', content: prompt }, //# 自行设定的prompt - // { role: 'assistant', content: '你是一个专业的智能助手' }, //# AI的历史回答结果 - // ....... 省略的历史对话 - { role: 'user', content: inputVal }, //# 最新的一条问题,如无需上下文,可只传最新一条问题 + { role: "system", content: prompt }, + { role: "user", content: inputVal }, // 最新的问题 ], }, }, - } - socket.send(JSON.stringify(params)) - }) + }; + socket.send(JSON.stringify(params)); + }); - socket.addEventListener('message', (event) => { - let data = JSON.parse(String(event.data)) - total_res += data.payload.choices.text[0].content + socket.addEventListener("message", (event) => { + const data = JSON.parse(String(event.data)); if (data.header.code !== 0) { - console.log('socket出错了', data.header.code, ':', data.header.message) - // 出错了"手动关闭连接" - socket.close() - reject('') - } - if (data.header.code === 0) { - // 对话已经完成 - if (data.payload.choices.text && data.header.status === 2) { - total_res += data.payload.choices.text[0].content - setTimeout(() => { - // "对话完成,手动关闭连接" - socket.close() - }, 1000) - } + console.error("Socket 出错:", data.header.code, data.header.message); + socket.close(); + reject(""); + } else if (data.payload.choices.text && data.header.status === 2) { + total_res += data.payload.choices.text[0].content; + setTimeout(() => { + socket.close(); + }, 1000); } - }) + }); - socket.addEventListener('close', (event) => { - console.log('socket 连接关闭') - // 对话完成后socket会关闭,将聊天记录换行处理 - resolve(total_res) - }) + socket.addEventListener("close", () => { + resolve(total_res); + }); - socket.addEventListener('error', (event) => { - console.log('socket连接错误', event) - reject('') - }) - }) + socket.addEventListener("error", (event) => { + console.error("Socket 连接错误:", event); + reject(""); + }); + }); - return await messagePromise + return await messagePromise; }