ai大模型流式输出------基于SSE协议的长连接实现

news/2024/12/2 11:33:47/文章来源:https://www.cnblogs.com/jilodream/p/18581368

传统的http1.0请求开发,已经满足了我们日常的web开发。
一般请求就像下图这样子,客服端发起一个请求(触发),服务端做出一个响应(动作):

有时会有诸如实时刷新,实时显示的场景,我们往往是客户端定时发起请求,不断的尝试获取最新的数据。
但是每次请求都会创建并释放一个新的连接,这样对于需要频繁请求的场景,性能损耗太大,此外对于实时性响应的场景也很难评估轮询周期。轮询的周期短,很多查询结果其实并没有变化,增加了成本开销。轮询周期长,又不能实时的展示数据,周期值变成了一个经验值,而且不同场景都需要不断的调整。这属实不够友好。
于是http1.1协议对此进行了扩展,允许长连接的存在。今天要介绍的SSE协议,就属于http1.1下的新协议。
SSE全称为 Sever-Sent Event
指服务器端事件发送。当客户端请求成功后,服务端会依次将事件(其实就是响应信息),分多次发送到客户端。客户端只要接收事件(响应信息),做出相应的处理即可。
就像下图的样子:

比如K线增长图,实时热力图,各种增长曲线等等,都可以实时的,由后端主动将事件推送到前端,不再需要前端每次建立一个新的连接来请求。这种方式也称之为长连接。
除了SSE,像websocket 、TCP等都属于长连接的类型。依次连接可以多次交互。
SSE其实最初并不受重视,甚至很多人都不知道这个协议。如果是简单一点的话,通常直接多轮询几遍就解决问题了,如果是复杂一点的话,直接就使用websocket这样的重协议来处理了,功能也相对来说比较强大。但是自从交互大模型问世以后,大模型的流式对话往往能更高效的输出,这种流式输出的用户体验也更好。这种主要是侧重大模型响应的交互模式,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )反而使得SSE的优势又体现出来了。
下面我们看下如何在springboot中使用sse来开发:
由于springboot的封装,我们使用SSE开发变得异常简单,
核心思路是:
创建一个 SseEmitter 对象,返回给前端
这个SseEmitter类似于一个socket,我们只管向里边塞数据即可,
而前端在收到SseEmitter对象后,则只管从sseEmitter中取数据即可。(注意此处一般采用注册响应方式)

后端代码如下:
pom文件新增依赖:

1         <dependency>
2             <groupId>org.springframework.boot</groupId>
3             <artifactId>spring-boot-starter-web</artifactId>
4         </dependency>

controller类:

 1 package com.example.demo.learnsse;
 2 
 3 import lombok.extern.slf4j.Slf4j;
 4 import org.springframework.http.MediaType;
 5 import org.springframework.web.bind.annotation.CrossOrigin;
 6 import org.springframework.web.bind.annotation.GetMapping;
 7 import org.springframework.web.bind.annotation.RequestParam;
 8 import org.springframework.web.bind.annotation.RestController;
 9 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
10 
11 import java.io.IOException;
12 import java.util.concurrent.TimeUnit;
13 
14 /**
15  * @discription
16  */
17 @Slf4j
18 @RestController
19 @CrossOrigin(origins = "*")
20 public class SseController {
21 
22 
23     @GetMapping(value = "/learn/sseChat" , produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
24     public SseEmitter chat(@RequestParam String name) throws IOException {
25         SseEmitter sseEmitter = new SseEmitter(360000L);
26         sseEmitter.onCompletion(() -> log.warn("sse complete!!!" + Thread.currentThread().getName()));
27         sseEmitter.onError(throwable -> {
28             log.warn("sse error  " + Thread.currentThread().getName(), throwable);
29         });
30         sseEmitter.send("start");
31         Runnable r = () -> {
32             int i = 1;
33             try {
34                 while (i <= 10) {
35                     sseEmitter.send(Thread.currentThread().getName()+": the next index:" + i);
36                     log.warn(Thread.currentThread().getName() + ":" + i);
37                     i++;
38                     TimeUnit.SECONDS.sleep(3);
39                 }
40                 sseEmitter.complete();
41             } catch (Exception e) {
42                 log.warn("catch a ex", e);
43                 sseEmitter.completeWithError(e);
44             }
45         };
46         Thread t = new Thread(r);
47         t.start();
48         log.warn("start return sse");
49         return sseEmitter;
50     }
51 }

我们可以不写前端,直接用浏览器或者命令行访问,

浏览器效果如下:

真实效果是一行行输出的

data:startdata:Thread-2: the next index:1data:Thread-2: the next index:2data:Thread-2: the next index:3data:Thread-2: the next index:4data:Thread-2: the next index:5data:Thread-2: the next index:6data:Thread-2: the next index:7data:Thread-2: the next index:8data:Thread-2: the next index:9data:Thread-2: the next index:10

日志输出如下:

2024-12-02 11:06:36.267  WARN 2032 --- [nio-8081-exec-4] com.example.demo.learnsse.SseController  : sse complete!!!http-nio-8081-exec-4
2024-12-02 11:06:38.440  WARN 2032 --- [       Thread-2] com.example.demo.learnsse.SseController  : Thread-2:2
2024-12-02 11:06:41.442  WARN 2032 --- [       Thread-2] com.example.demo.learnsse.SseController  : Thread-2:3
2024-12-02 11:06:44.450  WARN 2032 --- [       Thread-2] com.example.demo.learnsse.SseController  : Thread-2:4
2024-12-02 11:06:47.458  WARN 2032 --- [       Thread-2] com.example.demo.learnsse.SseController  : Thread-2:5
2024-12-02 11:06:50.468  WARN 2032 --- [       Thread-2] com.example.demo.learnsse.SseController  : Thread-2:6
2024-12-02 11:06:53.471  WARN 2032 --- [       Thread-2] com.example.demo.learnsse.SseController  : Thread-2:7
2024-12-02 11:06:56.475  WARN 2032 --- [       Thread-2] com.example.demo.learnsse.SseController  : Thread-2:8
2024-12-02 11:06:59.483  WARN 2032 --- [       Thread-2] com.example.demo.learnsse.SseController  : Thread-2:9
2024-12-02 11:07:02.495  WARN 2032 --- [       Thread-2] com.example.demo.learnsse.SseController  : Thread-2:10
2024-12-02 11:07:05.508  WARN 2032 --- [nio-8081-exec-5] com.example.demo.learnsse.SseController  : sse complete!!!http-nio-8081-exec-5

这样一个简单的单次连接,服务器多次推送的示例就写完了。

当然你也可以写一个简短的前端代码,查看效果,注意此时涉及到跨域了,因此我们的java代码要使用注解@CrossOrigin(origins = "*") 来解决跨域,请看controller代码中红色字体

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <title>SSE Example</title>
 5 </head>
 6 <body>
 7     <div id="events"></div>
 8     <script>
 9         const eventSource = new EventSource('http://127.0.0.1:8081/learn/sseChat?name=xx');
10 
11         eventSource.onmessage = function(event) {
12             const newElement = document.createElement("div");
13             newElement.textContent = "New message: " + event.data;
14             document.getElementById("events").appendChild(newElement);
15         };
16 
17         eventSource.onerror = function(error) {
18             console.error("Error:", error);
19             const newElement = document.createElement("div");
20             newElement.textContent = "error message: " + error;
21             document.getElementById("events").appendChild(newElement);
22             eventSource.close();
23         };
24         
25         eventSource.onclose = function(event) {
26             const newElement = document.createElement("div");
27             newElement.textContent = "close message: " + event.data;
28             document.getElementById("events").appendChild(newElement);
29             eventSource.close();
30         };
31     </script>
32 </body>
33 </html>

我们在创建好SSE示例时,一般会设置以下几个回调方法:

onCompletion(Runnable callback):当异步请求完成时,我们会调用此方法注册的回调函数。
onError(Consumer<Throwable> callback) 当异步处理期间发生错误时,会调用该方法设置的回调函数

服务端发现任务结束时,主动知会客户端关闭连接:
complete():表示已经完成推送,通知客户端不再有新的事件发送。
completeWithError(Throwable ex) 表示由于发生了某个异常而结束推送。springmvc将通过异常处理机制传递该异常。
一般在对接大模型时,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )我们除了完成SSE相关的注册,还会设置与大模型的连接,
一般的思路是这样的:
1、当前端发送请求提问来后端时,
2、我们首先创建一个SseEmitter,作为未来发送的套接字,
3、接着启动一个http连接,来请求大模型,
4、此时我们会使用Reactor-Mono之类的响应式编程框架,来回调处理大模型推送回来的数据。(其中Reactor部分的代码实现,由于篇幅有限,我会在后边的文章中讲解)
5、在Mono的每次回调到大模型推送回来的数据时,我们通过SseEmitter发送给前端
6、将第二步创建好的SseEmitter,返回给前端。
注意3/4/5步都是作为异步回调注册到mono中的。整体的结构图如下:

 

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

子卡设计原理图:232-基于FMC的2收2发TLK2711子卡

基于FMC的2收2发TLK2711子卡 一、板卡概述TLK2711 是千兆位收发器,专用于超高速双向点对点数据传输系统。TLK2711与时钟芯片SI5338相结合支持1.6Gbps至2.5Gbps的有效串行接口速度,可提供高达 2Gbps的数据带宽。板卡包含2路TLK2711,实现2收2发 。二、技术规格 ● 电源供电:F…

Python 存储验证码至redis

存储时考虑原子性 import redis# 实例化redis对象r = redis.Redis(host=localhost, port=6379, db=5,decode_responses=True)# 随机码的存储 def save_code(email,lifetime,code):# 原子操作,确保原子性 都执行或都不执行# 原子性保存验证码return r.setex(email,lifetime,cod…

【终端】终端配置、Vim使用相关

终端设置 MobaXterm主题 GithubVim 设置相关设置显示行号/etc/vimrc 是系统范围的初始化配置 ~/.vimrc 个人的vim初始化配置如果没有个人的vim初始化配置:touch ~/.vimrc vim ~/.vimrc设置好保存退出 wq使用相关跳转跳转指定行 :100跳转到100行 跳转到最开头 gg 跳转到最末…

idea的如何git代码进行合并

案例,将dev-wsg 的部分代码提交,合并到develop中1、先使用命令,将分支切换到develop分支 git checkout develop2、在idea 底部,选中这个标签页 3、选中要合并的提交,选择优选。这样本地的代码,会合并到本地develop分支中 4、优选后,如果分支右侧显示还要推送,则要进行…

11.30学习日记

11.30学习日记 oj 跑项目报错如下查问题,改成java8这个路径也要改掉这里用的是木马程序测试路径 改成我们正常测试的main.java就行运行成功

携程酒店库存查询接口(接单被骗直接放出接口)

本来是接单的,做了一个查询库存自动到他们系统里下库存的单子,做好部署好,老板可能也有点技术,拿了跑路了,那就放出来,给大家用 接口请求格式:https://down.ychengsnsm.com/jd/api.php?id=48565681&checkinDate=2024-12-30 测试图:id就是酒店id,随便到携程到个酒…

jQuery和CSS3超酷3D翻转式模态对话框插件

jquery-awesomodals是一款JQUERY和CSS3超酷3D翻转式模态对话框插件。该对话框特效通过jquery和CSS3 transitions和transforms来在对话框打开时制作3D翻转的效果,效果非常的酷。在线演示 下载安装 可以通过bower来安装jquery-awesomodals插件。$ bower install jquery-awesomo…

软件工程课堂测试九

软件需求与分析课堂测试九—结构化建模分析II(100分) (45分钟)1、 需求描述: 请设计一个仓储管理系统原型系统,该系统支持多个仓库的设立。统一 设立物资台账,物资台账需包含物资编码、物资名称、规格、材质、供应商、 品牌、物资分类,用户可以自定义物资的物资分类。…

Zabbix7 乱码处理

Zabbix7 乱码处理 Zabbix安装好后,查看图形时下面的文字往往显示不出来从windows主机 C:\Windows\Fonts拷贝字体文件双击打开,拷贝文件到桌面上传到 Zabbix主机以下目录(可以使用lrzsz 上传,安装方式 dnf install lrzsz -y) /usr/share/zabbix/assets/fonts 修改配置文件vim…

PDPS的虚拟调试整流程简单版

1:和PLC虚拟连接【选项------PLC------外部连接------添加Advance】2:PDPS插入cojt/jt文件,【建模-插入组件】【这样可以直接把机械端的设计文件导入】 3:定义机械设备的动作设计,比如气缸的打开/关闭动作【建模-运动学设备-姿态编辑器】 4:定义设备的动作逻辑流程【控件…

经济下行,利润却翻倍!AI救了这些企业的命

只有紧跟AI潮流,企业才能在变革中不断成长,开辟新的增长点,从而增强核心竞争力。大家好,我是陈哥,今天想和大家聊聊企业落地AI~ 自2022年底ChatGPT问世以来,AI热度高居不下,这场科技革命正以不可阻挡之势改变着世界。 SpaceX和特斯拉的董事会成员史蒂夫贾维森曾说:“机…