SSE(使用SSE完成大模型流式输出)
经常使用大模型AI的小伙伴应该不难发现,AI的回答都是一个字一个字蹦出来的,而不是整段文字直接出现。对大模型并不了解的小伙伴可能会误以为这是前端的动画效果。实际上,大模型的回复本身是逐词( 严格来说是逐token) 生成的,如果等全部生成完再返回,用户会明显感到卡顿。所以在实际部署和应用中,大模型问答应用通常采用,也就是所谓的“一个字一个字蹦出来”。既然流式文本输出并非前端特效,那么要实现这样的效
一.SSE的应用场景
场景:
经常使用大模型AI的小伙伴应该不难发现,AI的回答都是一个字一个字蹦出来的,而不是整段文字直接出现。对大模型并不了解的小伙伴可能会误以为这是前端的动画效果。
实际上,大模型的回复本身是逐词( 严格来说是逐token) 生成的,如果等全部生成完再返回,用户会明显感到卡顿。所以在实际部署和应用中,大模型问答应用通常采用流式(streaming)方式生成回答,也就是所谓的“一个字一个字蹦出来”。
解决方式:
既然流式文本输出并非前端特效,那么要实现这样的效果,自然就要用到实时通讯了。
在常见的实时通讯方式中,我们首先排除HTTP 轮询,用几十上百次请求得到一个回答,显然服务器压力巨大且效率极低。
那么WebSocket似乎是一个可选的方案?但大模型的回复主要还是单向的。服务器(AI模型)源源不断地向客户端(你的浏览器/App)推送生成出来的文本片段(tokens)。客户端只需要接收,不需要实时向服务器发送数据。那么WebSocket 提供的“双向实时发送”能力显然是过剩的。
那么有没有恰到好处的通讯方式呢?有的兄弟,有的。
SSE就非常适合文本的流式输出这样需要服务器单向的,持续不断推送数据的场景。
二.SSE是什么
SSE 是一种基于 HTTP 的“服务器单向广播”技术,允许服务器通过一次长连接,持续不断地向客户端实时推送数据。
🔘基于 HTTP:不是新协议,只是 HTTP 的一种特殊用法(Content-Type: text/event-stream)。
🔘单向推送:连接建立后,只有服务器能发,客户端只能收(类似“只读电台”)。
🔘长连接流式推送:一次连接,服务器可以源源不断发送消息,直到任务完成或连接中断。
核心特点(与轮询、WebSocket 对比)
✅ 比轮询更高效:不用客户端反复“问”,服务器有消息直接推,延迟低、浪费少。
✅ 比 WebSocket 更简单:不用双向通信,不涉及复杂协议切换,兼容性好。
✅ 适合场景:实时通知、消息流、大模型逐字生成等“服务器主动推,客户端被动看”的需求。
三.使用Spring boot提供的SseEmitter对象实现SSE接口
核心依赖,确保你的 pom.xml中包含了 Spring Web 依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
控制层代码:
@RestController
@RequestMapping("/api/sse")
public class SseController {
// 存储活跃的 SSE 连接(可选,用于广播等高级场景)
private final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();
/**
* 创建 SSE 连接,并开始流式生成内容
*/
@GetMapping("/stream")
public SseEmitter streamResponse(@RequestParam String question) {
// 创建一个 SseEmitter,可设置超时时间(默认 30 秒,设为 0 表示永不超时)
SseEmitter emitter = new SseEmitter(0L); // 0 表示不超时
// 可选:将 emitter 加入列表,方便后续管理
emitters.add(emitter);
// 模拟大模型逐字生成
simulateStreamingGeneration(emitter, question);
// 返回 emitter,Spring 会自动管理连接
return emitter;
}
/**
* 模拟流式生成:逐字发送回答
*/
private void simulateStreamingGeneration(SseEmitter emitter, String question) {
// 在子线程中执行,避免阻塞 HTTP 请求线程
CompletableFuture.runAsync(() -> {
try {
// 模拟生成回答
String answer = "你好,我是AI助手。你问的是:" + question + "。这是一个SSE流式响应的示例。";
// 逐字发送
for (int i = 0; i < answer.length(); i++) {
String token = String.valueOf(answer.charAt(i));
// 发送一个 SSE 事件
// 格式为:data: {内容}\n\n
emitter.send(SseEmitter.event()
.data(token) // 发送的内容
.name("token") // 事件名称(可选)
.id(String.valueOf(i)) // 事件 ID(可选,用于断线重连)
);
// 模拟生成延迟(每 100ms 发一个字)
Thread.sleep(100);
}
// 发送结束标记
emitter.send(SseEmitter.event()
.data("[DONE]")
.name("complete")
);
// 正常完成,关闭连接
emitter.complete();
} catch (Exception e) {
// 发生错误,关闭连接并发送错误信息
emitter.completeWithError(e);
} finally {
// 清理:从列表中移除 emitter
emitters.remove(emitter);
}
});
}
}
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)