Spring AI 进阶之路02:集成SSE实现AI对话的流式响应

引子

上一篇文章中,我们虽然用三步快速实现了 Spring Boot 集成 LLM,但这种同步响应的方式会让用户体验大打折扣。尤其当问题过于复杂时,大模型需要更多的时间来处理,这会导致用户不得不长时间面对空白屏幕,这种体验显然无法与逐字浮现的“打字机效果”相媲美。这种实时反馈的交互体验,正是流式响应的独特魅力,也已成为AI应用的标配。

在本篇文章中,我们将对项目进行升级改造,通过使用 Spring AI 的流式API与 SSE(Server-Sent Events) 技术,让 AI 响应如“打字机”般自然呈现。

认识SSE

在动手编码之前,我们有必要先花点时间了解一下本次实现的关键技术——SSE,全称 Server-Sent Events,即“服务器发送事件”。你可以把它想象成你关注了一个新闻App的“突发新闻”推送。你只需要在App里点击一次“允许通知”(这就是建立连接),之后只要有新的大新闻发生,App服务器就会主动把消息推送到你的手机上,你不用一遍遍地去刷新App。

你的浏览器(客户端)和我们的服务器(服务端)建立一个连接后,服务器就能随时把新数据(AI生成的新词语)主动“推送”给浏览器,而浏览器只管接收就行。这是一个从服务器到客户端的单行道

这里我们引申一下,可能有的读者会问为啥不用 WebSocket?这里我们对比下:

  • WebSocket:像一个微信电话。你和服务器都能随时说话,是双向的。它功能强大,但对于我们这个场景来说,有点“杀鸡用牛刀”。
  • SSE:就是我们上面说的新闻推送。只有服务器能“说话”,你只管听。是单向的。

在AI对话的场景里,我们问完问题后,只需要静静地听AI把答案一个字一个字“说”出来就行了。AI并不需要中途再听我们说什么。所以,更轻量、更简单的SSE,就是我们这个场景下的完美选择。

项目迭代

理论知识已经储备完毕,现在让我们开始动手改造项目。

1.搭建SSE通信管道

首先,我们需要创建一个SSE服务管理器,它负责管理所有客户端的连接。可以把它想象成一个"调度中心",负责记录哪些用户连接了进来,并向指定用户推送消息。

package com.cc.utils;

import com.cc.enums.SSEMsgType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

@Slf4j
public class SSEServer {

     // 存放所有用户的SseEmitter连接
    private static final Map<String, SseEmitter> sseClients = new ConcurrentHashMap<>();

    // 建立连接
    public static SseEmitter connect(String userId) {
        // 设置超时时间为0,即不超时,默认是30秒,超时未完成任务则会抛出异常
        SseEmitter sseEmitter = new SseEmitter(0L);
        // 注册连接完成、超时、异常时的回调函数
        sseEmitter.onTimeout(timeoutCallback(userId));
        sseEmitter.onCompletion(completionCallback(userId));
        sseEmitter.onError(errorCallback(userId));

        sseClients.put(userId, sseEmitter);
        log.info("SSE connect, userId: {}", userId);
        return sseEmitter;
    }


    // 发送消息
    public static void sendMsg(String userId, String message, SSEMsgType msgType) {

        if (CollectionUtils.isEmpty(sseClients)) {
            return;
        }

        if (sseClients.containsKey(userId)) {
            SseEmitter sseEmitter = sseClients.get(userId);
            sendEmitterMessage(sseEmitter, userId, message, msgType);
        }
    }

    public static void sendMsgToAllUsers(String message) {
        if (CollectionUtils.isEmpty(sseClients)) {
            return;
        }

        sseClients.forEach((userId, sseEmitter) -> {
            sendEmitterMessage(sseEmitter, userId, message, SSEMsgType.MESSAGE);
        });
    }


    private static void sendEmitterMessage(SseEmitter sseEmitter,
                                          String userId,
                                          String message,
                          
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值