第十篇:SpringAI 实战 10|全模型流式输出(Streaming)实战:实现打字机效果 导读在上一章中我们成功构建了多模型共存的底层架构。但在实际体验中如果调用大模型生成一篇长文传统的同步请求需要等待几十秒模型完全生成完毕后才能一次性返回结果。这种“干等”的体验在 AI 应用中是灾难性的。真正的 AI 应用如 ChatGPT都是“边思考边输出”即流式响应Streaming Response。本章我们将基于上一章的多模型架构引入 Spring WebFlux 的响应式编程利用 SSEServer-Sent Events协议用极少的代码为 OpenAI、通义千问、DeepSeek 和 Ollama 实现丝滑的“打字机效果”。一、环境前置说明运行前提电脑安装 Ollama客户端提前拉取开源模型文件JDK21Gradle8.8SpringBoot3.5.14SpringAI1.1.7IDEA2023 社区版本章代码是在上一篇的基础上新增/修改的二、 核心原理SSE 与 Flux 数据流要实现流式输出我们需要理解两个核心技术点SSEServer-Sent Events协议这是一种基于 HTTP 的单向通信协议。服务端可以主动向客户端推送数据非常适合大模型这种“服务端持续生成客户端持续渲染”的场景。Reactor 的 Flux 类型Spring WebFlux 提供了 Flux 响应式流类型。Spring AI 的 ChatClient 原生支持响应式编程只需将同步的 .call() 替换为 .stream()底层就会自动将大模型生成的增量 Token 封装为 SSE 数据流推送给前端。三、 后端改造一行代码开启流式输出得益于 Spring AI 的高度抽象我们无需修改上一章的 MultiModelConfig 配置类只需在 Controller 层新增流式接口即可。引入 WebFlux 依赖确保你的 build.gradle 中包含 WebFlux 依赖Spring AI 的流式响应依赖它implementationorg.springframework.boot:spring-boot-starter-webflux新增流式 Controller我们在上一章的 MultiModelController 中新增一个流式接口。注意 produces 必须设置为 text/event-stream/** * 全模型流式输出接口 */GetMapping(value/stream/{provider},producesMediaType.TEXT_EVENT_STREAM_VALUE)publicFluxStringstreamChat(PathVariableStringprovider,RequestParam(defaultValue你好请介绍一下你自己)Stringmsg){ChatClientchatclientgetClientByProvider(provider);// 核心使用 .stream() 替代 .call()并调用 .content() 仅返回文本内容returnchatclient.prompt().user(msg).stream().content();}/** * 根据路径参数获取对应的 Client */privateChatClientgetClientByProvider(Stringprovider){returnswitch(provider.toLowerCase()){caseopenai-openaiClient;caseollama-ollamaClient;caseqwen-qwenClient;casedeepseek-deepseekClient;default-thrownewIllegalArgumentException(Unsupported provider: provider);};}代码解析Spring WebFlux 检测到返回值是 Flux 且 produces text/event-stream 时会自动启用 ServerSentEventHttpMessageWriter。每当大模型生成一个词Spring 就会自动将其包装成 data: 词语\n\n 的 SSE 格式推送到前端四、 前端实战极简 HTML 实现打字机在 resources/static 目录下新建 stream-test.html文件代码如下!DOCTYPEhtmlhtmllangzh-CNheadmetacharsetUTF-8titleSpring AI 流式输出测试/titlestyle#output{border:1px solid #ccc;padding:15px;min-height:150px;white-space:pre-wrap;font-family:monospace;}button{padding:8px 16px;margin:5px;cursor:pointer;}/style/headbodyh2多模型流式对话测试/h2inputtypetextidmsgInputvalue用五句话聊一聊苏轼stylewidth:300px;buttononclickstartStream(openai)OpenAI/buttonbuttononclickstartStream(qwen)通义千问/buttonbuttononclickstartStream(deepseek)DeepSeek/buttonbuttononclickstartStream(ollama)Ollama/buttondividoutput等待输入.../divscriptletcurrentEventSourcenull;functionstartStream(provider){constmsgdocument.getElementById(msgInput).value;constoutputDivdocument.getElementById(output);// 1. 关闭上一次的连接防止流冲突if(currentEventSource)currentEventSource.close();outputDiv.innerHTML;// 2. 建立 SSE 连接consturl/ai/stream/${provider}?msg${encodeURIComponent(msg)};currentEventSourcenewEventSource(url);// 3. 监听消息实现打字机追加效果currentEventSource.onmessage(event){outputDiv.innerHTMLevent.data;// 自动滚动到底部outputDiv.scrollTopoutputDiv.scrollHeight;};// 4. 监听完成或错误currentEventSource.onerror(){currentEventSource.close();};}/script/body/html五、 运行与验证启动 Spring Boot 应用。使用浏览器访问 http://localhost:8080/stream-test.html点击不同的模型按钮你会看到文字像真人打字一样逐字出现在屏幕上。六、 本章总结通过本章的实战我们仅用 .stream().content() 这一行核心代码就打通了从大模型到前端的流式数据链路。对后端而言响应式编程避免了长文本生成时的线程阻塞单台服务器即可支撑成千上万个并发流式连接。对前端而言浏览器原生的 EventSource API 完美契合 SSE 协议无需引入任何第三方 WebSocket 库。至此我们的 AI 应用已经具备了“多模型路由”与“丝滑流式输出”两大核心能力。六、 参考文献SpringAI官方文档