CoolFace
Apppublic

dakaca/linux

sourceHugging Facebsdupdated 3y agoView on Hugging Face
0likes
app.js294 linesDownload Raw Back to root
1const express = require('express')2const fetch = require('cross-fetch')3const app = express()4var multer = require('multer');5var forms = multer({limits: { fieldSize: 10*1024*1024 }});6app.use(forms.array()); 7const cors = require('cors');8app.use(cors());9 10const bodyParser = require('body-parser')11app.use(bodyParser.json({limit : '50mb' }));  12app.use(bodyParser.urlencoded({ extended: true }));13 14const tencentcloud = require("tencentcloud-sdk-nodejs");15const TmsClient = tencentcloud.tms.v20201229.Client;16const clientConfig = {17  credential: {18    secretId: process.env.TENCENT_CLOUD_SID,19    secretKey: process.env.TENCENT_CLOUD_SKEY,20  },21  region: process.env.TENCENT_CLOUD_AP||"ap-singapore",22  profile: {23    httpProfile: {24      endpoint: "tms.tencentcloudapi.com",25    },26  },27};28const mdClient = process.env.TENCENT_CLOUD_SID && process.env.TENCENT_CLOUD_SKEY ? new TmsClient(clientConfig) : false;29 30const controller = new AbortController();31 32app.all(`*`, async (req, res) => {33  34  if(req.originalUrl) req.url = req.originalUrl;35  let url = `https://api.openai.com${req.url}`;36  // 从 header 中取得 Authorization': 'Bearer 后的 token37  const token = req.headers.authorization?.split(' ')[1];38  if( !token ) return res.status(403).send('Forbidden');39 40  const openai_key = process.env.OPENAI_KEY||token.split(':')[0];41  if( !openai_key ) return res.status(403).send('Forbidden');42  if( openai_key.startsWith("fk") ) url = url.replaceAll( "api.openai.com", "openai.api2d.net" );43 44  const proxy_key = token.split(':')[1]||"";  45  if( process.env.PROXY_KEY && proxy_key !== process.env.PROXY_KEY ) 46    return res.status(403).send('Forbidden');47 48  // console.log( req );49  const { moderation, moderation_level, ...restBody } = req.body;50  let sentence = "";51  // 建立一个句子缓冲区52  let sentence_buffer = [];53  let processing = false;54  let processing_stop = false;55 56  async function process_buffer(res)57  {58    if( processing_stop )59    {60      console.log("processing_stop",processing_stop);61      return false;62    }63 64    console.log("句子缓冲区" + new Date(), sentence_buffer);65    66    // 处理句子缓冲区67    if( processing )68    {69      // 有正在处理的,1秒钟后重试70      console.log("有正在处理的,1秒钟后重试");71      setTimeout( () => process_buffer(res), 1000 );72      return false;73    }74    75    processing = true;76    const sentence = sentence_buffer.shift();77    console.log("取出句子", sentence);78    if( sentence )79    {80      if( sentence === '[DONE]' )81      {82        console.log("[DONE]", "结束输出");83        res.write("data: "+sentence+"\n\n" );84        processing = false;85        res.end();86        return true;87      }else88      {89        // 开始对句子进行审核90        let data_array = JSON.parse(sentence);91        console.log("解析句子数据为array",data_array);92 93        const sentence_content = data_array.choices[0]?.delta?.content;94        console.log("sentence_content", sentence_content);95        if( sentence_content )96        {97          const params = {"Content": Buffer.from(sentence_content).toString('base64')};  98          const md_result = await mdClient.TextModeration(params);99          // console.log("审核结果", md_result);100          let md_check = moderation_level == 'high' ? md_result.Suggestion != 'Pass' : md_result.Suggestion == 'Block';101          if( md_check )102          {103            // 终止输出104            console.log("审核不通过", sentence_content, md_result);105            let forbidden_array = data_array;106            forbidden_array.choices[0].delta.content = "这个话题不适合讨论,换个话题吧。";107            res.write("data: "+JSON.stringify(forbidden_array)+"\n\n" );108            res.write("data: [DONE]\n\n" );109            res.end();110            controller.abort();111            processing = false;112            processing_stop = true;113            return false;114          }else115          {116            console.log("审核通过", sentence_content);117            res.write("data: "+sentence+"\n\n" );118            processing = false;119            console.log("processing",processing);120            return true;121          }122        }123 124      }125    }else126    {127      // console.log("句子缓冲区为空");128    }129 130    processing = false;131  }132 133  134  const options = {135      method: req.method,136      timeout: process.env.TIMEOUT||30000,137      signal: controller.signal,138      headers: {139        'Content-Type': 'application/json; charset=utf-8',140        'Authorization': 'Bearer '+ openai_key,141      },142      onMessage: async (data) => {143        // console.log(data);144        if( data === '[DONE]' )145        {146          sentence_buffer.push(data);147          await process_buffer(res);  148        }else149        {150          if( moderation && mdClient )151          {152            try {153              let data_array = JSON.parse(data);154              const char = data_array.choices[0]?.delta?.content;155              if( char ) sentence += char;156              // console.log("sentence",sentence );157              if( char == '。' || char == '?' || char == '!' || char == "\n" )158              {159                // 将 sentence 送审160                console.log("遇到句号,将句子放入缓冲区", sentence);161                data_array.choices[0].delta.content = sentence;162                sentence = "";163                sentence_buffer.push(JSON.stringify(data_array));164                await process_buffer(res);165              }166            } catch (error) {167              // 因为开头已经处理的了 [DONE] 的情况,这里应该不会出现无法解析json的情况 168              console.log( "error", error );169            }   170          }else171          {172            // 如果没有文本审核参数或者设置,直接输出173            res.write("data: "+data+"\n\n" );  174          }175        }176      }177  };178  179  if( req.method.toLocaleLowerCase() === 'post' && req.body ) options.body = JSON.stringify(restBody);180  // console.log({url, options});181 182  try {183    184    // 如果是 chat completion 和 text completion,使用 SSE185    if( (req.url.startsWith('/v1/completions') || req.url.startsWith('/v1/chat/completions')) && req.body.stream ) {186      console.log("使用 SSE");187      const response = await myFetch(url, options);188      if( response.ok )189      {190        // write header191        res.writeHead(200, {192          'Content-Type': 'text/event-stream',193          'Cache-Control': 'no-cache',194          'Connection': 'keep-alive',195        });196        const  { createParser } = await import("eventsource-parser");197        const parser = createParser((event) => {198          // console.log(event);    199          if (event.type === "event") {200            options.onMessage(event.data);201          }202        });203        if (!response.body.getReader) {204          const body = response.body;205          if (!body.on || !body.read) {206            throw new error('unsupported "fetch" implementation');207          }208          body.on("readable", () => {209            let chunk;210            while (null !== (chunk = body.read())) {211              // console.log(chunk.toString());212              parser.feed(chunk.toString());213            }214          });215        } else {216          for await (const chunk of streamAsyncIterable(response.body)) {217            const str = new TextDecoder().decode(chunk);218            parser.feed(str);219          }220        }221      }else222      {223        const body = await response.text();224        res.status(response.status).send(body);225      }226      227    }else228    {229      console.log("使用 fetch");230      const response = await myFetch(url, options);231      // console.log(response);232      const data = await response.json();233      // 审核结果234      if( moderation && mdClient )235      {236        const params = {"Content": Buffer.from(data.choices[0].message.content).toString('base64')};  237        const md_result = await mdClient.TextModeration(params);238        // console.log("审核结果", md_result);239        let md_check = moderation_level == 'high' ? md_result.Suggestion != 'Pass' : md_result.Suggestion == 'Block';240        if( md_check )241        {242          // 终止输出243          console.log("审核不通过", data.choices[0].message.content, md_result);244          data.choices[0].message.content = "这个话题不适合讨论,换个话题吧。";245        }else246        {247          console.log("审核通过", data.choices[0].message.content);248        }249      }250 251      res.json(data);252    }253    254    255  } catch (error) {256    console.error(error);257    res.status(500).json({"error":error.toString()});  258  }259})260 261async function* streamAsyncIterable(stream) {262  const reader = stream.getReader();263  try {264    while (true) {265      const { done, value } = await reader.read();266      if (done) {267        return;268      }269      yield value;270    }271  } finally {272    reader.releaseLock();273  }274}275 276async function myFetch(url, options) {277  const {timeout, ...fetchOptions} = options;278  const controller = new AbortController();279  const timeoutId = setTimeout(() => controller.abort(), timeout||30000)280  const res = await fetch(url, {...fetchOptions,signal:controller.signal});281  clearTimeout(timeoutId);282  return res;283}284 285// Error handler286app.use(function(err, req, res, next) {287  console.error(err)288  res.status(500).send('Internal Serverless Error')289})290 291const port = process.env.PORT||7860;292app.listen(port, () => {293  console.log(`Server start on http://localhost:${port}`);294})