const express = require('express') const fetch = require('cross-fetch') const app = express() var multer = require('multer'); var forms = multer({limits: { fieldSize: 10*1024*1024 }}); app.use(forms.array()); const cors = require('cors'); app.use(cors()); const bodyParser = require('body-parser') app.use(bodyParser.json({limit : '50mb' })); app.use(bodyParser.urlencoded({ extended: true })); const tencentcloud = require("tencentcloud-sdk-nodejs"); const TmsClient = tencentcloud.tms.v20201229.Client; const clientConfig = { credential: { secretId: process.env.TENCENT_CLOUD_SID, secretKey: process.env.TENCENT_CLOUD_SKEY, }, region: process.env.TENCENT_CLOUD_AP||"ap-singapore", profile: { httpProfile: { endpoint: "tms.tencentcloudapi.com", }, }, }; const mdClient = process.env.TENCENT_CLOUD_SID && process.env.TENCENT_CLOUD_SKEY ? new TmsClient(clientConfig) : false; const controller = new AbortController(); app.all(`*`, async (req, res) => { if(req.originalUrl) req.url = req.originalUrl; let url = `https://api.openai.com${req.url}`; // 从 header 中取得 Authorization': 'Bearer 后的 token const token = req.headers.authorization?.split(' ')[1]; if( !token ) return res.status(403).send('Forbidden'); const openai_key = process.env.OPENAI_KEY||token.split(':')[0]; if( !openai_key ) return res.status(403).send('Forbidden'); if( openai_key.startsWith("fk") ) url = url.replaceAll( "api.openai.com", "openai.api2d.net" ); const proxy_key = token.split(':')[1]||""; if( process.env.PROXY_KEY && proxy_key !== process.env.PROXY_KEY ) return res.status(403).send('Forbidden'); // console.log( req ); const { moderation, moderation_level, ...restBody } = req.body; let sentence = ""; // 建立一个句子缓冲区 let sentence_buffer = []; let processing = false; let processing_stop = false; async function process_buffer(res) { if( processing_stop ) { console.log("processing_stop",processing_stop); return false; } console.log("句子缓冲区" + new Date(), sentence_buffer); // 处理句子缓冲区 if( processing ) { // 有正在处理的,1秒钟后重试 console.log("有正在处理的,1秒钟后重试"); setTimeout( () => process_buffer(res), 1000 ); return false; } processing = true; const sentence = sentence_buffer.shift(); console.log("取出句子", sentence); if( sentence ) { if( sentence === '[DONE]' ) { console.log("[DONE]", "结束输出"); res.write("data: "+sentence+"\n\n" ); processing = false; res.end(); return true; }else { // 开始对句子进行审核 let data_array = JSON.parse(sentence); console.log("解析句子数据为array",data_array); const sentence_content = data_array.choices[0]?.delta?.content; console.log("sentence_content", sentence_content); if( sentence_content ) { const params = {"Content": Buffer.from(sentence_content).toString('base64')}; const md_result = await mdClient.TextModeration(params); // console.log("审核结果", md_result); let md_check = moderation_level == 'high' ? md_result.Suggestion != 'Pass' : md_result.Suggestion == 'Block'; if( md_check ) { // 终止输出 console.log("审核不通过", sentence_content, md_result); let forbidden_array = data_array; forbidden_array.choices[0].delta.content = "这个话题不适合讨论,换个话题吧。"; res.write("data: "+JSON.stringify(forbidden_array)+"\n\n" ); res.write("data: [DONE]\n\n" ); res.end(); controller.abort(); processing = false; processing_stop = true; return false; }else { console.log("审核通过", sentence_content); res.write("data: "+sentence+"\n\n" ); processing = false; console.log("processing",processing); return true; } } } }else { // console.log("句子缓冲区为空"); } processing = false; } const options = { method: req.method, timeout: process.env.TIMEOUT||30000, signal: controller.signal, headers: { 'Content-Type': 'application/json; charset=utf-8', 'Authorization': 'Bearer '+ openai_key, }, onMessage: async (data) => { // console.log(data); if( data === '[DONE]' ) { sentence_buffer.push(data); await process_buffer(res); }else { if( moderation && mdClient ) { try { let data_array = JSON.parse(data); const char = data_array.choices[0]?.delta?.content; if( char ) sentence += char; // console.log("sentence",sentence ); if( char == '。' || char == '?' || char == '!' || char == "\n" ) { // 将 sentence 送审 console.log("遇到句号,将句子放入缓冲区", sentence); data_array.choices[0].delta.content = sentence; sentence = ""; sentence_buffer.push(JSON.stringify(data_array)); await process_buffer(res); } } catch (error) { // 因为开头已经处理的了 [DONE] 的情况,这里应该不会出现无法解析json的情况 console.log( "error", error ); } }else { // 如果没有文本审核参数或者设置,直接输出 res.write("data: "+data+"\n\n" ); } } } }; if( req.method.toLocaleLowerCase() === 'post' && req.body ) options.body = JSON.stringify(restBody); // console.log({url, options}); try { // 如果是 chat completion 和 text completion,使用 SSE if( (req.url.startsWith('/v1/completions') || req.url.startsWith('/v1/chat/completions')) && req.body.stream ) { console.log("使用 SSE"); const response = await myFetch(url, options); if( response.ok ) { // write header res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', }); const { createParser } = await import("eventsource-parser"); const parser = createParser((event) => { // console.log(event); if (event.type === "event") { options.onMessage(event.data); } }); if (!response.body.getReader) { const body = response.body; if (!body.on || !body.read) { throw new error('unsupported "fetch" implementation'); } body.on("readable", () => { let chunk; while (null !== (chunk = body.read())) { // console.log(chunk.toString()); parser.feed(chunk.toString()); } }); } else { for await (const chunk of streamAsyncIterable(response.body)) { const str = new TextDecoder().decode(chunk); parser.feed(str); } } }else { const body = await response.text(); res.status(response.status).send(body); } }else { console.log("使用 fetch"); const response = await myFetch(url, options); // console.log(response); const data = await response.json(); // 审核结果 if( moderation && mdClient ) { const params = {"Content": Buffer.from(data.choices[0].message.content).toString('base64')}; const md_result = await mdClient.TextModeration(params); // console.log("审核结果", md_result); let md_check = moderation_level == 'high' ? md_result.Suggestion != 'Pass' : md_result.Suggestion == 'Block'; if( md_check ) { // 终止输出 console.log("审核不通过", data.choices[0].message.content, md_result); data.choices[0].message.content = "这个话题不适合讨论,换个话题吧。"; }else { console.log("审核通过", data.choices[0].message.content); } } res.json(data); } } catch (error) { console.error(error); res.status(500).json({"error":error.toString()}); } }) async function* streamAsyncIterable(stream) { const reader = stream.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) { return; } yield value; } } finally { reader.releaseLock(); } } async function myFetch(url, options) { const {timeout, ...fetchOptions} = options; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout||30000) const res = await fetch(url, {...fetchOptions,signal:controller.signal}); clearTimeout(timeoutId); return res; } // Error handler app.use(function(err, req, res, next) { console.error(err) res.status(500).send('Internal Serverless Error') }) const port = process.env.PORT||7860; app.listen(port, () => { console.log(`Server start on http://localhost:${port}`); })