工程对接大模型流式和非流式对话底层原理解析

文章目录

  • 前言
  • 一、非流式输出设计
  • 二、stream流式输出设计
  • 三、手撸一个流式输出项目
  • 总结


前言

之前对接过OpenAi大模型的官方API,可以看到它有一个Stream参数,设置成true的时候就是流式的对话输出,现象就是一段一段的往外崩。
官方手册的地址:https://platform.openai.com/docs/api-reference/chat
在这里插入图片描述
在这里插入图片描述
基本知识点是,在用户的提问以后,大模型底层的TransFromer架构能够根据自然语言理解逐词分析概率,继续预测下一部分的输出,这个我们理解为模型的推流过程。那么就会有两种方式进行输出:
(1)同步:等待这部分推流全部结束,通过HTTP响应一次性发送应答内容。
(2)异步:只要模型有输出,就针对推流的内容进行服务端的推送。
所以其实可以理解大模型本身就是异步推流的,区别在于是否等它全部生成完对话再推送。对于流式的服务端推送,整体上有两种实现方案:WebSocket和SSE
那么我们这个流式对话场景用的哪一种呢?
思考一下WebSocket和SSE的区别:
WebSocket全双工,适用于客户端和服务端双向交互,并且WebSocket是独立于HTTP协议之外的另一套协议,实现起来也相对复杂一些。SSE可以理解为单向的HTTP长连接,只需要前端或者浏览器发起一次HTTP get请求,后续服务端就可以向通道传输流式的数据了,SSE比较适合类似流式输出和股票信息实时推送这种场景。

顺便引入一下常见的几种实时通讯技术的对比
https://blog.csdn.net/xwh3165037789/article/details/128137023
在这里插入图片描述
另外其实官方说的比较直接了,设置之后如果数据推流的部分准备好了,就会通过SSE的方式进行服务端推送。
还有需要注意服务端对这个协议的实现要求,协议的描述参考下面地址
https://www.ruanyifeng.com/blog/2017/05/server-sent_events.html
https://www.ruanyifeng.com/blog/2017/05/websocket.html
在这里插入图片描述
通过PostMan请求返回如下,数据内容在Content里面,最后以DONE结尾。
在这里插入图片描述


一、非流式输出设计

stream参数为默认false,所以默认就是同步的。接入OpenAi的API以后,向对应地址发起HTTP请求,同步等待响应的结果就行了。所以本质上还是一次HTTP请求的过程。
但是一般为了进行后续的处理,比如对话做Summary、保存历史记录信息或者利用缓存的话,是可以把这次对话的内容入库的。

二、stream流式输出设计

理解一下整个流程设计:

假如做的是一个对话平台,项目的入口是一个Web页面,通过HTTP的POST请求发起对话,经过网关鉴权身份认证路由到接口,如果body携带的stream参数为true,进入streamChat的处理流程,走主服务转去请求OpenAi的对话接口地址,如果需要进行对话入库的话,这个时候我们的服务端作为大模型服务的客户端,Spring框架的WebFlux部分其实集成好了SseEmitter这个工具方便我们实现SSE功能,需要通过EventSource的factory建立sse连接,并且设置EventSourceListener监听器,重写它的一系列方法,包括onOpen、onEvent、onFailure、onClosed等。比如onEvent这个回调函数的作用就是当大模型输出事件触发的时候,将读取到的数据写入数据库,并且写入是可以通过请求内部的另外的写入HTTP的API触发的。在处理onClosed的时候需要考虑是否计算大模型的TokenSize占用作为附加信息入库和返回。

随后另外开启一个异步任务,线程池里面主要负责以下部分:
(1)心跳检测,SSE中断重连
(2)从数据库里面分页加载数据
(3)通过SSE通道推送数据给前端控制台

三、手撸一个流式输出项目

为了更好理解,我们可以手动实践,写一个这样的项目,做完了的话,估计大家也都能做GPT代理了,项目放在GitHub里面做个了记录。

后端(Spring Boot)
主应用代码

package com.example.ssedemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = {"com.example.*", "web"})
public class SsEdemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SsEdemoApplication.class, args);
    }

}

Controller层代码

package web;// 导入相关依赖
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@RestController
@RequestMapping("/api")
public class EventController {

    private final ExecutorService executor = Executors.newSingleThreadExecutor();

    private int i = 0;

    @GetMapping("/stream")
    public SseEmitter streamData() {
        SseEmitter sseEmitter = new SseEmitter();

        // 模拟大模型计算并分批次推送数据
        executor.execute(() -> {
            while (true) {
                try {
                    // 这里是模拟计算过程,实际项目中可能是从数据库、文件或其他来源获取数据
                    String dataChunk = "Data Chunk " + ++i;

                    // 发送一个事件给前端
                    sseEmitter.send(SseEmitter.event()
                            .id(Long.toString(i)) // 可选,用于标识事件ID
                            .name("data") // 事件类型名
                            .data(dataChunk, MediaType.TEXT_PLAIN));

                    // 模拟延迟,比如每次间隔1秒
                    Thread.sleep(1000);
                } catch (IOException | InterruptedException e) {
                    // 关闭 emitter 并处理异常
                    sseEmitter.completeWithError(e);
                }
            }
            // 计算结束或达到某个条件时关闭 emitter
//            sseEmitter.complete();
        });

        return sseEmitter;
    }
}

CROS的配置

package web;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://yourfrontenddomain.com") // 替换为您的前端域名
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

在使用SpringBoot3版本的时候可能会出现JDK版本报错,处理方式如下:
https://blog.csdn.net/giveupgivedown/article/details/134841844
https://blog.csdn.net/weixin_47889104/article/details/135289440
在这里插入图片描述
如果报错404了,记得添加包扫描路径,类似下面这种
@SpringBootApplication(scanBasePackages = {“com.example.demo”, “com.example.another.package”})
在这里插入图片描述
运行起来项目结构可能是下面这样
在这里插入图片描述
后端启动以后,直接浏览器访问80端口的API地址,
http://localhost:8080/api/stream
就能看到有流式的输出
在这里插入图片描述

其实不写下面的前端代码也行,利用SpringBoot内置的Tomcat直接查看浏览器页面。

前端(使用JavaScript,这里以原生Fetch API为例)

// 创建一个新的EventSource实例,连接到后端提供的SSE端点
var eventSource = new EventSource('http://localhost:8080/api/stream');

// 处理接收到的事件
eventSource.addEventListener('data', function(event) {
    var dataChunk = event.data; // 接收的数据
    console.log('Received chunk:', dataChunk);

    // 在这里你可以更新UI,例如将数据添加到页面上
    document.getElementById('output').innerText += dataChunk + '\n';
});

// 监听连接错误
eventSource.onerror = function(error) {
    console.error('Error occurred:', error);
};

// 如果需要,在适当的时候可以关闭连接
// eventSource.close();

上述提供的JavaScript代码片段可以在浏览器环境下执行,它创建了一个与服务器建立连接的EventSource对象,用于接收来自服务器的Server-Sent Events(SSE)。只需要确保你在一个支持SSE的现代浏览器环境中,并且服务器端(这里是Spring Boot后端)已经正确设置了SSE服务接口。将这段JavaScript代码嵌入到HTML文档中,例如在 <\script> 标签内部,以便在浏览器加载页面时执行:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SSE Streaming Demo</title>
    <style>
        #output {
            height: 200px; /* 设置高度以限制输出区域 */
            overflow-y: scroll; /* 添加滚动条 */
            word-wrap: break-word; /* 自动换行 */
            border: 1px solid #ccc; /* 边框仅用于区分展示区域 */
            padding: 10px;
        }
    </style>
</head>
<body>
<div id="output"></div>

<script>
    // JavaScript 代码放在这里
    var eventSource = new EventSource('http://localhost:8080/api/stream');

    eventSource.addEventListener('message', function(event) { // 修改为 'message' 事件,SSE 的标准事件类型
        var dataChunk = event.data;
        var outputElement = document.getElementById('output');
        var newLineNode = document.createElement('p'); // 使用段落元素以保证格式清晰
        newLineNode.textContent = dataChunk;
        outputElement.appendChild(newLineNode);
        outputElement.scrollTop = outputElement.scrollHeight; // 滚动到底部,展示最新内容
    });

    eventSource.onerror = function(error) {
        console.error('Error occurred:', error);
    };
</script>
</body>
</html>

其实IDEA支持直接打开或者转到浏览器打开的
在这里插入图片描述
如果出现这个CROS报错,需要给服务端加跨域支持的配置
在这里插入图片描述

关于如何在浏览器执行参考下面
https://www.runoob.com/js/js-chrome.html
https://jingyan.baidu.com/article/e4d08ffdc25e584ed3f60d48.html
https://www.yzktw.com.cn/post/644689.html

先启动服务端的SpringBoot工程,如果跨域配置好了,然后运行前端JS代码
就可以看到服务端推送的流式输出了。这个项目其实可以做得更复杂一点,后续有机会我们继续往里加功能加技术点进去。

其他参考地址
https://blog.csdn.net/fyk844645164/article/details/126680347
https://blog.csdn.net/xwh3165037789/article/details/128137023
https://juejin.cn/post/7209226686373609533
https://blog.csdn.net/weixin_40951507/article/details/135354852
http://wed.xjx100.cn/news/236173.html?action=onClick
https://zhuanlan.zhihu.com/p/674994371
https://blog.csdn.net/qq_45399396/article/details/130972849
https://www.cnblogs.com/1996-Chinese-Chen/p/17913287.html
https://blog.csdn.net/fengtaokelly/article/details/130702235
https://blog.csdn.net/Larry_Lee88/article/details/130995475


总结

以上就是对大模型流式和非流式输出的解析,做完这个真有点想基于GPT代理搞点额外东西了,大模型厂家也挺多的,有人喜欢chatGPT,最近感觉通义千问也挺好,问了很多人是如何利用大模型的,一个技术TL说他经常用大模型来写测试用例,也有人用它帮忙理清思路查Bug或者写Demo或者命令等,未来还是有比较广阔的应用场景的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/355841.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯训练|基础语言Day1 - STL pair vector list stack queue set map容器

学习目标&#xff1a; 博主介绍: 27dCnc 专题 : 算法题入门 &#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d;&#x1f44d; ☆*: .&#xff61;. o(≧▽≦)o .&#xff61…

Python爬虫案例展示:实现花猫壁纸数据采集

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 环境使用: Python 3.10 Pycharm 模块使用: import requests >>> pip install requests win R 输入cmd 输入安装命令 pip install requests 安装即…

Springboot各种请求参数详解

文章目录 请求Postman**为什么需要Postman****什么是Postman****Postman使用教程** 请求参数简单参数实体参数数组参数集合参数![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/eba0ca80e3724412ae4c79af72b859c3.png#pic_center)日期参数json参数路径参数总结 请求…

STM32CubeMX教程31 USB_DEVICE - HID外设_模拟键盘或鼠标

目录 1、准备材料 2、实验目标 3、模拟鼠标实验流程 3.0、前提知识 3.1、CubeMX相关配置 3.1.0、工程基本配置 3.1.1、时钟树配置 3.1.2、外设参数配置 3.1.3、外设中断配置 3.2、生成代码 3.2.0、配置Project Manager页面 3.2.1、设初始化调用流程 3.2.2、外设中…

ESP8266 控制之 : 使用 RingBuffer USART1 和 USART3互传

简介 使用Buffer来避免数据的丢失, 或许你自己在使用串口进行收发时会丢失数据, 现在我们就来简单使用一下RingBuffer创建Rx、Tx的Buffer来避免发送接收丢包或数据丢失问题。 扩展知识 RingBuffer的介绍, 看完大概也就知道了&#xff0c;实在不知道就看看下面的代码 线路连接…

详解操作系统各章大题汇总(死锁资源分配+银行家+进程的PV操作+实时调度+逻辑地址->物理地址+页面置换算法+磁盘调度算法)

文章目录 第三章&#xff1a;死锁资源分配图例一例二 第三章&#xff1a;银行家算法第四章&#xff1a;进程的同步与互斥做题步骤PV操作的代码小心容易和读者写者混 1.交通问题&#xff08;类似读者写者&#xff09;分析代码 2.缓冲区问题&#xff08;第二个缓冲区是复制缓冲区…

实现元素进入界面的平滑效果

先看效果&#xff1a; 实现思路&#xff1a;获取页面中需要加载动画的节点&#xff0c;用元素的animate()方法创建一个动画对象&#xff0c;并传入两个关键帧&#xff0c;接着使用IntersectionObserverAPI创建观察对象&#xff0c;用于观察元素进入页面。当元素进入界面时&…

【数据分享】1929-2023年全球站点的逐年平均气温数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;其中又以气温指标最为常用&#xff01;说到气温数据&#xff0c;最详细的气温数据是具体到气象监测站点的气温数据&#xff01;本次我们为大家带来的就是具体到气象监…

CSS--Emmet 语法

Emmet语法的前身是Zen coding,它使用缩写,来提高html/css的编写速度, Vscode内部已经集成该语法. 目录 1. 快速生成HTML结构语法 1.1 快速生成HTML结构语法 2. 快速生成CSS样式语法 2.1 快速生成CSS样式语法 1. 快速生成HTML结构语法 1.1 快速生成HTML结构语法 1. 生成标…

2.【Vue3】Vue 基本使用——局部使用Vue

文章目录 1. 快速入门2. 常用指令2.1 v-for2.2 v-bind2.3 v-if 与 v-show2.4 v-on2.5 v-model 3. 生命周期4. Ajax 函数库 Axios4.1 Axios 基本使用4.2 Axios 请求方式别名 1. 快速入门 现在需要将 “hello vue3” 这样一个字符串渲染到页面上进行展示。 这个需求并不陌生&…

JVM系列——对象管理

JVM对象分布 对象头 第一类是用于存储对象自身的运行时数据&#xff0c;如哈希码&#xff08;HashCode&#xff09;、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等 另外一部分是类型指针&#xff0c;即对象指向它的类型元数据的指针&#xff0c;Java 虚…

【ArcGIS微课1000例】0096:dem三维块状表达(层次地形模型)

文章目录 一、DEM表达方式二、层次模型表达三、注意事项一、DEM表达方式 DEM数字高程模型的表达方式通常有以下4种: 1. 规则格网 2. 不规则三角网 3. 等高线 4. 层次地形模型 作为栅格地理数据,DEM 数据具有2.5维的特征,能够以三维表面的形式进行三维空间表达。但受其数…

Web 开发 6:Redis 缓存(Flask项目使用Redis并同时部署到Docker详细流程 附项目源码)

大家好&#xff01;欢迎来到第六篇 Web 开发教程&#xff0c;今天我们将探讨一个非常重要的话题&#xff1a;Redis 缓存。作为一个互联网开发者&#xff0c;你一定知道在处理大量请求时&#xff0c;性能优化是至关重要的。而 Redis 缓存正是帮助我们提升系统性能的利器。Redis …

爬虫基础-计算机网络协议

一个数据的传输 这些设备的数据转发是通过协议来完成的&#xff0c;整个互联网可以说是完全由网络协议来维持的 不同的协议分工不同&#xff0c;比如ip协议确保了ip寻址&#xff0c;tcp协议确保了数据完整性 IP地址和URL ip地址 整个网络传输可以比作快递&#xff0c;数据就…

2023年度总结——忙忙碌碌,终有归章

思来想去&#xff0c;还是决定写一篇年终总结&#xff0c;一来算是对23年的一年的回顾&#xff0c;二来是对24年的展望。记得22年也写过一篇年度总结&#xff0c;题目是《2022年度总结——一切都在慢慢变好》。今年&#xff0c;我想起的题目是《2023年度总结——忙忙碌碌&#…

在Temu跨境电商平台上,如何快速出单?

随着越来越多的商家选择入驻Temu跨境电商平台&#xff0c;一旦入驻申请通过&#xff0c;商家就可以开始上架商品并等待订单的产生。然而&#xff0c;很多新手跨境电商卖家都面临一个共同的问题&#xff0c;那就是&#xff1a;Temu出单快吗&#xff1f;Temu上架多久能出单&#…

STM32CubeMX教程27 SDIO - 读写SD卡

目录 1、准备材料 2、实验目标 3、轮询方式读取SD卡流程 3.0、前提知识 3.1、CubeMX相关配置 3.1.0、工程基本配置 3.1.1、时钟树配置 3.1.2、外设参数配置 3.1.3、外设中断配置 3.2、生成代码 3.2.0、配置Project Manager页面 3.2.1、外设初始化调用流程 3.2.2、外设中断调用流…

激光雷达,角力「降本增效」

高工智能汽车研究院最新发布的数据显示&#xff0c;2023年1-11月&#xff0c;中国市场&#xff08;不含进出口&#xff09;乘用车前装标配激光雷达搭载量为46.48万颗&#xff0c;同比增长372.35%&#xff0c;继续保持高增长态势。 随着激光雷达在中国市场完成规模化上量的节点&…

Promethues是什么?

什么是Prometheus&#xff1f; Prometheus是一个开源的系统监控以及报警系统。整和zabbix的功能&#xff0c;系统&#xff0c;网络&#xff0c;设备。 promethues可以兼容网络、设备、容器监控、告警系统。因为和k8s是一个项目基金开发的产品&#xff0c;天生就匹配k8s的原生…

离线安装nginx_银河麒麟系统_nginx报错_503_500 Internal Server Error----nginx工作笔记007

如果报这个错误,意思就是,对于nginx.conf文件中指定的,文件夹没有权限 那么这个是去给对应的文件夹赋权限: chmod 777 /opt/module/test_web 就可以了,然后再去访问就不会报错了,还有 503的错误都可以这样解决 然后关于离线安装nginx,尝试了一下如果把之前安装过的nginx,直接…