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

news/2024/12/2 13:59:27/文章来源:https://www.cnblogs.com/westworldss/p/18581791

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

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

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

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

1         <dependency>
2             <groupId>org.springframework.bootgroupId>
3             <artifactId>spring-boot-starter-webartifactId>
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 2 3 4     SSE Example5 6 7     8     
32 
33 

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

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

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

本博客参考wgetcloud全球加速服务机场。转载请注明出处!

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

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

相关文章

高级语言程序设计课程第十次个人作业

2024高级语言程序设计:https://edu.cnblogs.com/campus/fzu/2024C 高级语言程序设计课程第十次个人作业:https://edu.cnblogs.com/campus/fzu/2024C/homework/13314 学号:102400226 姓名:石华波 本次作业所用到的"Source.txt"文本文件均为下图文件://1202.1 #include…

也许,这就是一个新的开始吧……

准备参加招警考试了,虽然我预测当前自己的各项指标什么的基本不合格,但是我愿意尝试一把,愿上岸成功。加油吧,青中骚年。 【敬畏能量 敬畏自然】

开源工具能让项目管理更高效?2024年开源管理软件大揭秘!

一、开源项目管理的重要性在当今的项目管理领域,开源项目管理具有至关重要的意义。它鼓励创新,为开发者提供了一个开放的平台,让不同背景的人能够共同参与,分享各自的想法和经验,从而推动项目不断创新发展。 开源项目管理保证透明,项目的代码和流程对所有人可见,这使得项…

Vaccine

Vaccine 1. 扫描 上来直接扫到ftp有一个backup.zip以及ssh和80端口将backup.zip下载下来然后发现压缩包有密码2. 访问 访问80端口看看还有一些信息,apache+PHP,Linux系统首先,对于登录界面,先排除弱口令和SQL注入,然后看目录扫描结果没什么内容,那只能回头看backup.zip,…

十、Spring Boot集成Spring Security之HTTP请求授权

Spring Security最新基于HTTP请求授权配置详解:工作原理,授权配置,异常处理,测试接口,案例源码目录前言一、HTTP请求授权工作原理二、HTTP请求授权配置1、添加用户权限2、配置ExceptionTranslationFilter自定义异常处理器3、HTTP请求授权配置三、测试接口1、测试类2、测试…

博客园主页皮肤设置 - 第三版

基本设置侧边栏 <script type="text/javascript">window.cnblogsConfig = {info: {name: A-刘晨阳, // 用户名startDate: 2021-10-14, // 入园时间,年-月-日。入园时间查看方法:鼠标停留园龄时间上,会显示入园时间avatar: https://img2024.cnblogs.com/blog…

11.27实验 25:访问者模式

[实验任务一]:打包员 在我们课堂上的“购物车”的例子中,增加一个新的访问者:打包员,负责对购物车中货物装包。 实验要求: 1. 画出对应的类图;2. 提交源代码; #include <iostream> #include <string> #include <list> using namespace std;class Prod…

11.29实验二:逻辑回归算法实现与测试

实验二:逻辑回归算法实现与测试 一、实验目的 深入理解对数几率回归(即逻辑回归的)的算法原理,能够使用 Python 语言实现对数 几率回归的训练与测试,并且使用五折交叉验证算法进行模型训练与评估。二、实验内容 (1)从 scikit-learn 库中加载 iris 数据集,使用留出法留出…

11.25实验 23:策略模式

[实验任务一]:旅行方式的选择 旅游的出行方式有乘坐飞机旅行、乘火车旅行和自行车游,不同的旅游方式有不同的实现过程,客户可以根据自己的需要选择一种合适的旅行方式。 实验要求: 1. 画出对应的类图;2. 提交源代码; package strategy;public class Person {private Tour…

vxe-table 设置斑马线条纹样式

vxe-table 设置斑马线条纹样式,通过设置 stripe 参数 官网:https://vxetable.cn 表格<template><div><vxe-grid v-bind="gridOptions"></vxe-grid></div> </template><script> export default {data () {const gridOpti…

Lsky Pro挂载alist

首先需要去修改lsky代码,如果是docker可以 docker exec -it lsky-pro /bin/bash 然后安装个vim apt install vim cd app cd Services/ ls vi ImageService.php 然后vim输入 :set nu 显示行号,然后找到345行,粘贴进去 authType => 1, lwebdav存储策略问题 Issue #497 ls…