LLM流式方案解决方案和客户端解决方案

背景

接上一篇《LLM大模型统一封装接口解决方案》架构确定后,流式方案非常规请求,需要特殊处理。
本解决方案就是针对上一篇中所需要的流式(打字机效果进行编码)

什么是SSE

SSE(Server-Sent Events,服务器发送事件)是一种基于HTTP的服务器到客户端的单向通信技术,用于实现服务器向客户端推送数据的功能。SSE协议标准由HTML5规范定义,并且其定义被包含在HTML Living Standard中。
SSE允许服务器通过HTTP连接向客户端发送数据,而无需客户端发起请求。这使得SSE非常适合于实时通信或推送通知给客户端的应用程序,例如实时股票报价、即时通讯、实时监控等场景。
基本上,SSE由以下要素组成:

  1. 服务器:负责向客户端发送事件流的HTTP服务器。
  2. 客户端:通过浏览器中的EventSource API与服务器建立连接,接收服务器发送的事件。
  3. 事件流(Event Stream):服务器向客户端发送的数据流,格式为纯文本,使用一种特定的格式进行编码,例如MIME类型为"text/event-stream"。

SSE的优点包括简单易用、实现方便、跨浏览器支持良好等。然而,它也有一些限制,例如不能支持双向通信,与WebSocket相比,SSE的实时性稍逊一筹。

Java框架说明

pom 文件引入的核心依赖包

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>aip.com</groupId>
    <artifactId>aip-com</artifactId>
    <version>0.0.1</version>
    <name>aip-com</name>
    <description>aip com project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>io.reactivex.rxjava2</groupId>
            <artifactId>rxjava</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Java后端核心代码

本方法是标准的SSE协议标准

java 复制代码
    private final ExecutorService executorService = Executors.newFixedThreadPool(5);

    
    /**
     * 会话请求
     *
     * @return String
     */
    @PostMapping(value = "/completions", consumes = MediaType.APPLICATION_JSON_VALUE)
    @Operation(summary = "会话请求")
    public SseEmitter completions(@RequestBody CompletionRequest completionRequest) {
        response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE);
        SseEmitter emitter = new SseEmitter();

        executorService.execute(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    // 向客户端发送事件
                    emitter.send(
                            SseEmitter.event()
                                    .name("message")
                                    .data(JsonHelper.toJSONString(new StreamCompletionResult.Builder()
                                            .ended(false)
                                             .message(String.valueOf(i))
                                            .build()))
                    );
                    Thread.sleep(1000);
                }
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });
        return emitter;

    /**
     * 会话请求
     *
     * @return String
     */
    @GetMapping(value = "/stream")
    @Operation(summary = "会话请求")
    public SseEmitter stream() {
        response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE);
        SseEmitter emitter = new SseEmitter();

        executorService.execute(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    // 向客户端发送事件
                    emitter.send(
                            SseEmitter.event()
                                    .name("message")
                                    .data(JsonHelper.toJSONString(new StreamCompletionResult.Builder()
                                            .ended(false)
                                             .message(String.valueOf(i))
                                            .build()))
                    );
                    Thread.sleep(1000);
                }
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });
        return emitter;

Flux 和 Flowable 对比

Flux 和 Flowable 都是响应式编程库中的数据流类型,用于处理异步和基于事件的流式数据。它们分别来自于不同的库,Flux 是 Reactor 库的一部分,而 Flowable 则是 RxJava 库的一部分。以下是它们之间的一些区别:

  1. 库的来源:

    • Flux 来自于 Reactor 库,是 Reactor 的核心组件之一,React的核心模块用于基于反应式流规范处理数据流。
    • Flowable 来自于 RxJava 库,是 RxJava 的核心类之一,RxJava 是 Java 平台的反应式扩展库,用于处理异步和基于事件的编程。
  2. 背压策略:

    • Flux 默认采用背压策略为 BUFFER,可以通过 onBackpressureBuffer、onBackpressureDrop、onBackpressureLatest 等方法来指定不同的背压策略。
    • Flowable 默认也是支持背压的,但是相比 Flux,Flowable 提供了更多的背压策略,如 BUFFER、DROP、LATEST、ERROR、MISSING。
  3. 反应式规范:

    • Flux 遵循 Reactor 库的反应式流规范,使用 Mono 和 Flux 来表示异步流和单个结果。
    • Flowable 遵循 RxJava 库的反应式流规范,使用 Observable 和 Flowable 来表示异步流和单个结果。
  4. 生态系统:

    • Reactor 生态系统主要用于基于 Reactor 的应用程序。
    • RxJava 生态系统则更广泛,它是 ReactiveX 的一部分,支持多种语言和平台,并有许多衍生项目。

总的来说,Flux 和 Flowable 在概念上很相似,都用于处理异步和基于事件的流式数据,但它们来自于不同的库,并且有一些细微的区别,如背压策略和生态系统支持。您可以根据项目需求选择适合的库和数据流类型。

Java后端Flowable方式

本方法是Flowable方式,非标准流式规则

java 复制代码
    /**
     * 会话请求
     *
     * @return String
     */
    @GetMapping(value = "/stream")
    @Operation(summary = "会话请求")
    public Flowable<String> stream() {
        response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE);

        Flowable<String> typingFlow = Flowable.create(emitter -> {
            executorService.execute(() -> {
                try {
                    for (int i = 0; i < 10; i++) {

                        emitter.onNext(JsonHelper.toJSONString(new StreamCompletionResult.Builder()
                                .ended(false)
                                .message(String.valueOf(i))
                                .build()));

                        Thread.sleep(1000);
                    }
                    emitter.onComplete();
                } catch (Exception e) {

                }
            });
        }, BackpressureStrategy.BUFFER);

        return typingFlow;
    }

Java后端Flux方式

本方法是Flux方式,非标准流式规则

java 复制代码
    /**
     * 会话请求
     *
     * @return String
     */
    @GetMapping(value = "/stream")
    @Operation(summary = "会话请求")
    public Flux<String> stream() {
        response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE);

        Flux<String> typingFlow = Flux.create(emitter -> {
            executorService.execute(() -> {
                try {
                    for (int i = 0; i < 10; i++) {

                        emitter.next(JsonHelper.toJSONString(new StreamCompletionResult.Builder()
                                .ended(false)
                                .message(String.valueOf(i))
                                .build()));

                        Thread.sleep(1000);
                    }
                    emitter.complete();
                } catch (Exception e) {

                }
            });
        }, FluxSink.OverflowStrategy.BUFFER);

        return typingFlow;
    }
}

HTML 客户端接收示例程序

function EventSourceGetRequest() SSE 默认方法,只支持GET请求,适合演示用途以及后端包装好服务
function fetchPostRequest() fetch POST 请求实现SSE,支持所有请求(POST,GET等)以及传递参数
sse.html 内容

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SEE Example</title>
    <script>

        // SSE 默认方法,只支持GET请求
        function EventSourceGetRequest() {
            if(typeof(EventSource)!=="undefined")
            {
                var eventSource = new EventSource('http://127.0.0.1:8090/v1/chat/stream');
                eventSource.onmessage = function(event)
                {
                    document.getElementById('result').insertAdjacentHTML('beforeend', `${event.data}<br/><br/>`);
                    console.log(event)
                };
            }
            else
            {
                document.getElementById("result").innerHTML="抱歉,你的浏览器不支持 server-sent 事件...";
            }
        }

        // fetch POST 请求实现SSE
        function fetchPostRequest() {
            fetch('http://127.0.0.1:8090/v1/chat/completions', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({}),
            })
            .then(response => {
                // 检查响应是否成功
                if (!response.ok) {
                    throw new Error('Network response was not ok');
                }
                // 返回 ReadableStream 对象
                return response.body;
            })
            .then(stream => {
                // 创建一个新的文本解码器
                const decoder = new TextDecoder();
                
                // 获取一个 reader 对象
                const reader = stream.getReader();
                
                let chunk = ''
                
                // 逐块读取数据
                function read() {
                    reader.read().then(({ done, value }) => {
                        if (done) {
                            document.getElementById('result').insertAdjacentHTML('beforeend', `${chunk}<hr/>`);
                            console.log('Stream has ended');
                            return;
                        }
                        // 将数据块转换为字符串并显示
                        const tmp = decoder.decode(value, { stream: true });
                        if (tmp.startsWith('event:') && chunk!='') {
                            document.getElementById('result').insertAdjacentHTML('beforeend', `${chunk}<hr/>`);
                            chunk = tmp
                        }else{
                            chunk = chunk + tmp
                        }
                        // 继续读取下一块数据
                        read();
                    });
                }
                // 开始读取数据
                read();
            })
            .catch(error => {
                // 处理错误
                console.error('There was a problem with the fetch operation:', error);
            });
        }

        // EventSourceGetRequest();
        fetchPostRequest();
    </script>
</head>
<body>
	<h1>SEE result</h1>
    <div id="result"></div>
</body>
</html>
  • 标准SSE示例
  • 扩展SSE
相关推荐
spatial_coder6 分钟前
Kimi K2万亿参数开源模型原理介绍
llm
cainiao0806051 小时前
Java 大视界:基于 Java 的大数据可视化在智慧城市能源消耗动态监测与优化决策中的应用(2025 实战全景)
java
长风破浪会有时呀2 小时前
记一次接口优化历程 CountDownLatch
java
云朵大王2 小时前
SQL 视图与事务知识点详解及练习题
java·大数据·数据库
我爱Jack2 小时前
深入解析 LinkedList
java·开发语言
柠檬豆腐脑3 小时前
构建高效智能体(Building Effective Agents)
llm·agent
27669582924 小时前
tiktok 弹幕 逆向分析
java·python·tiktok·tiktok弹幕·tiktok弹幕逆向分析·a-bogus·x-gnarly
用户40315986396634 小时前
多窗口事件分发系统
java·算法
用户40315986396634 小时前
ARP 缓存与报文转发模拟
java·算法
小林ixn4 小时前
大一新手小白跟黑马学习的第一个图形化项目:拼图小游戏(java)
java