当前位置: 首页 > article >正文

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

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

首先看一下效果

image-20241115161821543

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

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

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

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

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

    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':''
          }
        }
      }
    }
前端实现接口调用

<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接口对接部分代码:

// 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()方法

//引入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;

打开接口地址

image-20241115163302145

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方法

//引入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]);


http://www.kler.cn/a/395766.html

相关文章:

  • 【安全科普】NUMA防火墙诞生记
  • UNIX网络编程-TCP套接字编程(实战)
  • 鸿蒙next ui安全区域适配(刘海屏、摄像头挖空等)
  • centos rich 美观打印日志
  • androidstudio入门到放弃配置
  • react+hook+vite项目使用eletron打包成桌面应用+可以热更新
  • Apache Doris:深度优化与最佳实践
  • Dev C++ 无法使用to_string方法的解决
  • shell编程(2)永久环境变量和字符串显位
  • 利用云计算实现高效的数据备份与恢复策略
  • 使用 DBSCAN(基于密度的聚类算法) 对二维数据进行聚类分析
  • Spring基础之——控制反转(IOC)、依赖注入(DI)与切面编程(AOP)概念详解(适合小白,初学者必看)
  • 问:数据库的六种锁机制实践总结?
  • C语言,用最小二乘法实现一个回归模型
  • (附项目源码)Java开发语言,211 springboot 在线问诊系统的设计与实现,计算机毕设程序开发+文案(LW+PPT)
  • 谷歌Gemini发布iOS版App,live语音聊天免费用!
  • 基于微信小程序的乡村研学游平台设计与实现,LW+源码+讲解
  • 科锐国际,蓝禾,汤臣倍健,三七互娱,GE医疗,得物,顺丰,快手,途游游戏25秋招内推
  • 14天Java基础学习——第6天:面向对象编程(类与对象)
  • 实验1-1 顺序表的基本操作
  • ceph的集群管理
  • 计算机的错误计算(一百五十五)
  • HTML5实现俄罗斯方块小游戏
  • jenkins用户在执行scp的时候如何做免密登录
  • 【RabbitMQ】08-延迟消息
  • POD-Transformer多变量回归预测(Matlab)