node对接ChatGpt的流式输出的配置

node对接ChatGpt的流式输出的配置

首先看一下效果

将数据用流的方式返回给客户端,这种技术需求在传统的管理项目中不多见,但是在媒体或者有实时消息等功能上就会用到,这个知识点对于前端还是很重要的。

即时你不写服务端,但是服务端如果给你这样的接口,你也得知道怎么去使用联调。

1. nodejs实现简单的SSE服务,使用write返回流式

SSE服务(Server-Sent Events),是一种服务器向客户端推送实时更新的机制模式。

javascript 复制代码
const express = require('express');  
const app = express();  
const port = 8002;  
 
 
let strArr = [
    '你好',
    '吃饭了吗',
    'What are you doing?',
    'My name is yy',
    '8888',
    'hello'
]
let setTask = null
  
app.get('/events', (req, res) => {  
  res.setHeader('Content-Type', 'text/event-stream;charset=utf-8');  
  res.setHeader('Cache-Control', 'no-cache');  
  res.setHeader('Connection', 'keep-alive');  
  let num = 0
  setTask =  setInterval(()=>{
    res.write(`data:${strArr[num]}\n\n`)
    num++
    if(num > 5){
        res.write(`data:end\n\n`)
        res.end()
        // res.closed()
        clearInterval(setTask)
        setTask = null
    }
  },1000)
});  
  
app.listen(port, () => {  
  console.log(`${port}端口已启动`);  
});

2. 前端实现接收数据流

这里使用一个叫做EventSource的api去实现流式接口的调用和数据获取**。**

配置代理(重要)

如果我们用vue,react等等框架开发时,需要在代理处做一些配置,确保数据会以流式的返回。如果不做这层代理的配置,那么你获取的数据就会是执行完所有的res.write,一次性的全部返回给前端,就不是我们想要的效果。

效果如下,在配置代理中将compress设置为false

javascript 复制代码
    devServer:{
      client:{
        overlay:false
      },
      port:8080,
      open:true,
      compress:false,  //流式数据返回的关键配置
      proxy:{
        '/server1':{
          target:'http://localhost:3001',
          ws:false,
          changeOrigin:true,
          pathRewrite:{
            '^/server1':''
          }
        },
        '/server2':{
          target:'http://localhost:3002',
          ws:false,
          changeOrigin:true,
          pathRewrite:{
            '^/server2':''
          }
        },
        '/sse':{
          target:'http://localhost:8002',
          ws:false,
          changeOrigin:true,
          pathRewrite:{
            '^/sse':''
          }
        }
      }
    }
前端实现接口调用
javascript 复制代码
<template>
    <div>
        <el-button @click="sendMsg">发送消息</el-button>
        <p v-for="(item,index) in msgList" :key="index">{{ item }}</p>
    </div>
   
  </template>
  <script>
   export default{
      name:'admin',
      data(){
        return{
            msgList:[]
        }
      },
      methods:{
    sendMsg(){
        let vm = this
 
    //方案1:EventSource
      const eventSource = new EventSource('/sse/events');  
  //消息监听
  eventSource.onmessage = function(event) {  
    console.log(eventSource,vm,'状态')
    console.log(event.data); // 输出SSE发送的数据  
    if(event.data === 'end'){
      eventSource.close()
    }else{
      vm.msgList.push(event.data)
    }
 
  };  
  //连接成功
  eventSource.onopen = function(event){
 
  }
  //连接出错
  eventSource.onerror = function(error) {  
    if (eventSource.readyState === EventSource.CLOSED) {  
      // 连接已关闭,可能需要重新连接  
      console.error('SSE连接已关闭:', error);  
    }  
  }
 
  //方案2:xhr(不推荐)
  // const xhr = new XMLHttpRequest(); 
  // const url = '/sse/events'; 
  // xhr.open('GET', url,true); 
  // xhr.setRequestHeader('Accept', 'text/event-stream');
 
  // xhr.onload = (event)=>{
  //   if(xhr.status === 200){
  //     console.log(xhr.responseText,'onload',event)
  //   }
  // }
 
  // xhr.onreadystatechange = (event)=>{
  //   // if(xhr.status === 200){
  //   //   console.log(xhr.responseText,'onreadystatechange',event)
  //   // }
    
  // }
  // xhr.onprogress = (event)=>{
  //   if(xhr.status === 200){
  //     console.log(xhr.responseText,'onreadystatechange',event)
     
  //   }
  // }
  // xhr.send()
    }
      }
   }
  </script>
  <style lang="less">
  </style>

这样就大功告成了,如果以后要是做类似于chatgpt这种效果,就可以用到的。

3. 对接ai接口,使用write返回数据流格式

服务器端(node+express)与openai接口对接部分代码:

javascript 复制代码
// const md5 = require('md5')
import express from 'express';
import mysql from 'mysql';
import cors from 'cors';
import jwt from 'jsonwebtoken';
import bodyParser from 'body-parser';
import OpenAI from "openai";
const app = express()

app.use(bodyParser.json())              //解析json
app.use(bodyParser.urlencoded({ extended: true }));             //解析客户端传递过来的参数 

function query(sql, callback) {
  // 从连接池中获取一个连接
  pool.getConnection((err, connection) => {
    if (err) {
      callback(err, null);
    } else {
      // 执行查询
      connection.query(sql, (err, results) => {
        // 释放连接
        connection.release();
        callback(err, results);
      });
    }
  });
}

app.get('/open', (req, res) => {
  const openai = new OpenAI(
    {
      // 若没有配置环境变量,请用百炼API Key将下行替换为:apiKey: "sk-xxx",
      apiKey: "",
      baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1"
    }
  );

  async function main() {
    const completion = await openai.chat.completions.create({
      model: "qwen-plus",
      messages: [
        { "role": "system", "content": "You are a helpful assistant." },
        { "role": "user", "content": "你是谁?" }
      ],
      stream: true,
    });
    for await (const chunk of completion) {
      // console.log(JSON.stringify(chunk));
      // res.write(chunk);
      res.write(JSON.stringify(chunk));
      // res.end();
    }
    res.end();
  }

  main();
})

app.listen(3001, () => {
  console.log('serve is running at http://127.0.0.1:3001')
})

上面的apikey值需要你申请密钥

然后在浏览器访问地址http://192.168.3.105:3001/open,就会输出流式数据,在前端中调用就行

4. res.write、res.end及res.send 使用以及区别?

首先,Express响应中常用的四种API:res.write() | res.end() | res.send() | res.json(),这4个API方法,都可以发送HTTP响应,返回浏览器的请求数据

一. res.write()方法

javascript 复制代码
//引入express包
const express = require('express');
//创建路由器对象
const r = express.Router();
//往路由器中添加路由
//商品列表路由:get  /list
r.get('/list', (req, res) => {
  // 在响应头信息里设置响应返回的内容类型为html,编码为utf-8(在浏览器页面正常显示中文)
  // 设置内容解析的编码为utf-8,正确地告诉浏览器,服务器响应的内容是什么编码的,你浏览器应该按照我服务器设定的编码格式来解析给你的内容
  // res.writeHead(200, {
  //   'Content-Type': 'text/html;charset=utf-8'
  // });
  let show = "<h2>888</h2>";
  res.write(show);
  res.write('商品列表');
  res.end();
  //res.end('<div>该方法用于结束响应的浏览器请求</div>');
});
 
//导出路由器对象
module.exports = r;

打开接口地址

1. res.write()响应的数据"所见即所得"

res.write()的返回数据是没有经过处理的,原封不动的返回原数据,所见即所得

2. res.write()与res.end()总是且必须成对出现

如果要使用res.write()最后必须要有res.end,两者是成对出现的,缺一不可,也就是说使用res.write方法 向前端返回数据,必须调用res.end方法结束请求。否则浏览器会一直处于处于请求状态

3. res.write()方法在结束浏览器响应请求之前,允许多次调用

如果想要输出多条语句,使用的是res.write(),也就是说在res.end() 之前,res.write() 可以被执行多次),且返回的数据会被拼接到一起。

4.res.write()是可以结合HTML标签显示的

res.write()输出内容可以结合HTML标签进行使用。

5. res.write()只支持输出字符串类型或是Buffer对象两种内容类型的数据

如果此时我们输出一个数字就会报错,查看报错信息,提醒我们不能输出number类型

res.write(123);

二. res.end方法

javascript 复制代码
//引入express包
const express = require('express');
//创建路由器对象
const r = express.Router();
//往路由器中添加路由
//商品列表路由:get  /list
r.get('/list', (req, res) => {
  // 在响应头信息里设置响应返回的内容类型为html,编码为utf-8(在浏览器页面正常显示中文)
  // 设置内容解析的编码为utf-8,正确地告诉浏览器,服务器响应的内容是什么编码的,你浏览器应该按照我服务器设定的编码格式来解析给你的内容
  res.writeHead(200, {
     'Content-Type': 'text/html;charset=utf-8'
  });
  res.end('<div>该方法用于结束响应的浏览器请求</div>');
  //下面语句将不会输出
  res.end("Hello world");
});
 
//导出路由器对象
module.exports = r;

res.end()函数用于结束响应过程。该方法用于快速结束响应,而无需任何数据。也就是说用于在没有任何数据的情况下快速结束响应。如果有响应数据,就不能用 res.end,会报错,请使用res.send()和res.json()等方法。

1. res.end()响应的数据"所见即所得"

res.end()的返回数据同res.write()一样,也是没有经过处理的,原封不动的返回原数据,所见即所得

2. res.end()是不允许输出多行的

不同于res.write()方法,res.end()作为结束浏览器请求的方法,仅能调用一次

3. res.end()是可以结合HTML标签显示的

res.end()同res.write()一样,输出的内容可以是带HTML标签的内容

res.end(''
该方法用于结束响应的浏览器请求
'');

4. res.end()只支持输出字符串类型或是Buffer对象两种内容类型的数据

res.end()同res.write一样,不能输入除字符串类型或是Buffer对象类型外的其他内容类型的数据

三. res.send()方法

1. res.send()响应的数据是经过处理的

打开浏览器控制台,在响应头中被自动添加了context-type,也就是说,res.send()方法响应返回给页面数据时,在响应头信息里会被自动添加设置返回数据类型的context-type属性

2. res.send()只能被调用一次,因为它等同于res.write+res.end()

多个send输出只执行第一个send语句,后续send语句将不被执行

3.res.send()同res.write()、res.end()一样,可以结合HTML标签数据显示

*4. res.send**()支*持多种内容格式的输出

res.send()方法可以支持多种参数,比如可以传String、Array、Buffer对象、对象、json对象

当参数是Array或Object、json对象,Express以JSON表示响应

res.send({ user: 'tobi' });

res.send()只能被调用一次,因为它等同于res.write+res.end()**

多个send输出只执行第一个send语句,后续send语句将不被执行

3.res.send()同res.write()、res.end()一样,可以结合HTML标签数据显示

*4. res.send**()支*持多种内容格式的输出

res.send()方法可以支持多种参数,比如可以传String、Array、Buffer对象、对象、json对象

当参数是Array或Object、json对象,Express以JSON表示响应

res.send({ user: 'tobi' });

res.send([1,2,3]);

相关推荐
engchina13 分钟前
如何在 Python 中忽略烦人的警告?
开发语言·人工智能·python
ibrahim14 分钟前
Llama 3.2 900亿参数视觉多模态大模型本地部署及案例展示
ai·大模型·llama·提示词
paixiaoxin1 小时前
CV-OCR经典论文解读|An Empirical Study of Scaling Law for OCR/OCR 缩放定律的实证研究
人工智能·深度学习·机器学习·生成对抗网络·计算机视觉·ocr·.net
OpenCSG1 小时前
CSGHub开源版本v1.2.0更新
人工智能
weixin_515202491 小时前
第R3周:RNN-心脏病预测
人工智能·rnn·深度学习
Altair澳汰尔1 小时前
数据分析和AI丨知识图谱,AI革命中数据集成和模型构建的关键推动者
人工智能·算法·机器学习·数据分析·知识图谱
机器之心2 小时前
图学习新突破:一个统一框架连接空域和频域
人工智能·后端
AI视觉网奇2 小时前
人脸生成3d模型 Era3D
人工智能·计算机视觉
call me by ur name2 小时前
VLM--CLIP作分类任务的损失函数
人工智能·机器学习·分类
吃个糖糖2 小时前
34 Opencv 自定义角点检测
人工智能·opencv·计算机视觉