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

news2024/12/22 23:04:36

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.coloradmin.cn/o/2241900.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

聊聊Flink:Flink的运行时架构

一、运行时架构 上一篇我们可以看到Flink的核心组件的Deploy层&#xff0c;该层主要涉及了Flink的部署模式&#xff0c;Flink支持多种部署模式&#xff1a;本地、集群&#xff08;Standalone/YARN&#xff09;、云&#xff08;GCE/EC2&#xff09;。 Local&#xff08;本地&am…

【动手学电机驱动】 STM32-FOC(7)MCSDK Pilot 上位机控制与调试

STM32-FOC&#xff08;1&#xff09;STM32 电机控制的软件开发环境 STM32-FOC&#xff08;2&#xff09;STM32 导入和创建项目 STM32-FOC&#xff08;3&#xff09;STM32 三路互补 PWM 输出 STM32-FOC&#xff08;4&#xff09;IHM03 电机控制套件介绍 STM32-FOC&#xff08;5&…

华为云前台用户可挂载数据盘和系统盘是怎么做到的?

用户可以选择磁盘类型和容量&#xff0c;其后台是管理员对接存储设备 1.管理员如何在后台对接存储设备&#xff08;特指业务存储&#xff09; 1.1FusionSphere CPS&#xff08;Cloud Provisionivice&#xff09;云装配服务 它是first node https://10.200.4.159:8890 对接存…

Python爬虫知识体系-----requests-----持续更新

数据科学、数据分析、人工智能必备知识汇总-----Python爬虫-----持续更新&#xff1a;https://blog.csdn.net/grd_java/article/details/140574349 文章目录 一、安装和基本使用1. 安装2. 基本使用3. response常用属性 二、get请求三、post请求四、代理 一、安装和基本使用 1.…

区块链技术在数据安全中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 区块链技术在数据安全中的应用 区块链技术在数据安全中的应用 区块链技术在数据安全中的应用 引言 区块链技术基础 1.1 区块链的…

RK3568平台开发系列讲解(GPIO篇)GPIO的sysfs调试手段

🚀返回专栏总目录 文章目录 一、内核配置二、GPIO sysfs节点介绍三、命令行控制GPIO3.1、sd导出GPIO3.2、设置GPIO方向3.3、GPIO输入电平读取3.4、GPIO输出电平设置四、Linux 应用控制GPIO4.1、控制输出4.2、输入检测4.3、使用 GPIO 中断沉淀、分享、成长,让自己和他人都能有…

电商系统开发:Spring Boot框架实战

3 系统分析 当用户确定开发一款程序时&#xff0c;是需要遵循下面的顺序进行工作&#xff0c;概括为&#xff1a;系统分析–>系统设计–>系统开发–>系统测试&#xff0c;无论这个过程是否有变更或者迭代&#xff0c;都是按照这样的顺序开展工作的。系统分析就是分析系…

从电动汽车到车载充电器:LM317LBDR2G 线性稳压器在汽车中的多场景应用

附上LM317系列选型&#xff1a; LM317BD2TG-TO-263 LM317BTG-TO-220 LM317BD2TR4G-TO-263 LM317D2TG-TO-263 LM317D2TR4G-TO-263 LM317TG-TO-220 LM317LBDR2G-SOP-8 LM317LDR2G-SOP-8 LM317MABDTG-TO-252 LM317MABDTRKG-TO-252 LM317MA…

Linux下MySQL的简单使用

Linux下MySQL的简单使用 导语MySQL安装与配置MySQL安装密码设置 MySQL管理命令myisamchkmysql其他 常见操作 C语言访问MYSQL连接例程错误处理使用SQL 总结参考文献 导语 这一章是MySQL的使用&#xff0c;一些常用的MySQL语句属于本科阶段内容&#xff0c;然后是C语言和MySQl之…

前端 JS 实用操作总结

目录 1、重构解构 1、数组解构 2、对象解构 3、...展开 2、箭头函数 1、简写 2、this指向 3、没有arguments 4、普通函数this的指向 3、数组实用方法 1、map和filter 2、find 3、reduce 1、重构解构 1、数组解构 const arr ["唐僧", "孙悟空&quo…

力扣 LeetCode 541. 反转字符串II(Day4:字符串)

解题思路&#xff1a; i可以成段成段的跳&#xff0c;而不是简单的i class Solution {public String reverseStr(String s, int k) {char[] ch s.toCharArray();// 1. 每隔 2k 个字符的前 k 个字符进行反转for (int i 0; i < ch.length; i 2 * k) {// 2. 剩余字符小于 …

鸿蒙版APP-图书购物商城案例

鸿蒙版-小麦图书APP是基于鸿蒙ArkTS-API12环境进行开发&#xff0c;不包含后台管理系统&#xff0c;只有APP端&#xff0c;页面图书数据是从第三方平台(聚合数据)获取进行展示的&#xff0c;包含登录&#xff0c;图书类别切换&#xff0c;图书列表展示&#xff0c;图书详情查看…

卡尔曼滤波:从理论到应用的简介

卡尔曼滤波&#xff08;Kalman Filter&#xff09;是一种递归算法&#xff0c;用于对一系列噪声观测数据进行动态系统状态估计。它广泛应用于导航、控制系统、信号处理、金融预测等多个领域。本文将介绍卡尔曼滤波的基本原理、核心公式和应用案例。 1. 什么是卡尔曼滤波&#x…

学习日志011--模块,迭代器与生成器,正则表达式

一、python模块 在之前学习c语言时&#xff0c;我们学了分文件编辑&#xff0c;那么在python中是否存在类似的编写方式&#xff1f;答案是肯定的。python中同样可以实现分文件编辑。甚至还有更多的好处&#xff1a; ‌提高代码的可维护性‌&#xff1a;当代码被分成多个文件时…

CSS 语法规范

基本语法结构 CSS 的基本语法结构包含 选择器 和 声明块,两者共同组成 规则集。规则集可以为 HTML 元素设置样式,使页面结构和样式实现分离,便于网页的美化和布局调整。 CSS 规则集的结构如下: selector {property: value; }选择器(Selector) 选择器用于指定需要应用…

Bag Graph: Multiple Instance Learning Using Bayesian Graph Neural Networks文献笔记

基本信息 原文链接&#xff1a;[2202.11132] Bag Graph: Multiple Instance Learning using Bayesian Graph Neural Networks 方法概括&#xff1a;用图&#xff08;贝叶斯GNN框架&#xff09;来建模袋之间的相互作用&#xff0c;并使用图神经网络&#xff08;gnn&#xff09…

Spark 共享变量:广播变量与累加器解析

Spark 的介绍与搭建&#xff1a;从理论到实践_spark环境搭建-CSDN博客 Spark 的Standalone集群环境安装与测试-CSDN博客 PySpark 本地开发环境搭建与实践-CSDN博客 Spark 程序开发与提交&#xff1a;本地与集群模式全解析-CSDN博客 Spark on YARN&#xff1a;Spark集群模式…

前海华海金融创新中心的工地餐点探寻

​前海的工地餐大部分都是13元一份的哈。我在前海华海金融创新中心的工地餐点吃过一份猪杂饭&#xff0c;现做13元一份。我一般打包后回公司吃或直接桂湾公园找个环境优美的地方吃饭。 ​我点的这份猪杂汤粉主要是瘦肉、猪肝、肉饼片、豆芽和生菜&#xff0c;老板依旧贴心问需要…

借助Excel实现Word表格快速排序

实例需求&#xff1a;Word中的表格如下图所示&#xff0c;为了强化记忆&#xff0c;希望能够将表格内容随机排序&#xff0c;表格第一列仍然按照顺序编号&#xff0c;即编号不跟随表格行内容调整。 乱序之后的效果如下图所示&#xff08;每次运行代码的结果都不一定相同&#x…

【C语言指南】C语言内存管理 深度解析

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《C语言指南》 期待您的关注 引言 C语言是一种强大而灵活的编程语言&#xff0c;为程序员提供了对内存的直接控制能力。这种对内存…