当前位置: 首页 > news >正文

第十篇:SpringAI 实战 10|全模型流式输出(Streaming)实战:实现打字机效果

导读:在上一章中,我们成功构建了多模型共存的底层架构。但在实际体验中,如果调用大模型生成一篇长文,传统的同步请求需要等待几十秒模型完全生成完毕后,才能一次性返回结果。这种“干等”的体验在 AI 应用中是灾难性的。
真正的 AI 应用(如 ChatGPT)都是“边思考边输出”,即流式响应(Streaming Response)。本章我们将基于上一章的多模型架构,引入 Spring WebFlux 的响应式编程,利用 SSE(Server-Sent Events)协议,用极少的代码为 OpenAI、通义千问、DeepSeek 和 Ollama 实现丝滑的“打字机效果”。

一、环境前置说明

运行前提:电脑安装 Ollama客户端,提前拉取开源模型文件

  1. JDK:21
  2. Gradle:8.8
  3. SpringBoot:3.5.14
  4. SpringAI:1.1.7
  5. IDEA:2023 社区版
    (本章代码是在上一篇的基础上新增/修改的)

二、 核心原理:SSE 与 Flux 数据流

要实现流式输出,我们需要理解两个核心技术点:

  1. SSE(Server-Sent Events)协议:这是一种基于 HTTP 的单向通信协议。服务端可以主动向客户端推送数据,非常适合大模型这种“服务端持续生成,客户端持续渲染”的场景。
  2. Reactor 的 Flux 类型:Spring WebFlux 提供了 Flux 响应式流类型。Spring AI 的 ChatClient 原生支持响应式编程,只需将同步的 .call() 替换为 .stream(),底层就会自动将大模型生成的增量 Token 封装为 SSE 数据流推送给前端。

三、 后端改造:一行代码开启流式输出

得益于 Spring AI 的高度抽象,我们无需修改上一章的 MultiModelConfig 配置类,只需在 Controller 层新增流式接口即可。

  1. 引入 WebFlux 依赖
    确保你的 build.gradle 中包含 WebFlux 依赖(Spring AI 的流式响应依赖它):
implementation'org.springframework.boot:spring-boot-starter-webflux'
  1. 新增流式 Controller
    我们在上一章的 MultiModelController 中新增一个流式接口。注意 produces 必须设置为 text/event-stream:
/** * 全模型流式输出接口 */@GetMapping(value="/stream/{provider}",produces=MediaType.TEXT_EVENT_STREAM_VALUE)publicFlux<String>streamChat(@PathVariableStringprovider,@RequestParam(defaultValue="你好,请介绍一下你自己")Stringmsg){ChatClientchatclient=getClientByProvider(provider);// 核心:使用 .stream() 替代 .call(),并调用 .content() 仅返回文本内容returnchatclient.prompt().user(msg).stream().content();}/** * 根据路径参数获取对应的 Client */privateChatClientgetClientByProvider(Stringprovider){returnswitch(provider.toLowerCase()){case"openai"->openaiClient;case"ollama"->ollamaClient;case"qwen"->qwenClient;case"deepseek"->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文件,代码如下:

<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><title>Spring AI 流式输出测试</title><style>#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></head><body><h2>多模型流式对话测试</h2><inputtype="text"id="msgInput"value="用五句话聊一聊苏轼"style="width:300px;"><buttononclick="startStream('openai')">OpenAI</button><buttononclick="startStream('qwen')">通义千问</button><buttononclick="startStream('deepseek')">DeepSeek</button><buttononclick="startStream('ollama')">Ollama</button><divid="output">等待输入...</div><script>letcurrentEventSource=null;functionstartStream(provider){constmsg=document.getElementById('msgInput').value;constoutputDiv=document.getElementById('output');// 1. 关闭上一次的连接,防止流冲突if(currentEventSource)currentEventSource.close();outputDiv.innerHTML='';// 2. 建立 SSE 连接consturl=`/ai/stream/${provider}?msg=${encodeURIComponent(msg)}`;currentEventSource=newEventSource(url);// 3. 监听消息,实现打字机追加效果currentEventSource.onmessage=(event)=>{outputDiv.innerHTML+=event.data;// 自动滚动到底部outputDiv.scrollTop=outputDiv.scrollHeight;};// 4. 监听完成或错误currentEventSource.onerror=()=>{currentEventSource.close();};}</script></body></html>

五、 运行与验证

  1. 启动 Spring Boot 应用。
  2. 使用浏览器访问 http://localhost:8080/stream-test.html
  3. 点击不同的模型按钮,你会看到文字像真人打字一样逐字出现在屏幕上。

六、 本章总结

通过本章的实战,我们仅用 .stream().content() 这一行核心代码,就打通了从大模型到前端的流式数据链路。
对后端而言:响应式编程避免了长文本生成时的线程阻塞,单台服务器即可支撑成千上万个并发流式连接。
对前端而言:浏览器原生的 EventSource API 完美契合 SSE 协议,无需引入任何第三方 WebSocket 库。
至此,我们的 AI 应用已经具备了“多模型路由”与“丝滑流式输出”两大核心能力。

六、 参考文献

  1. SpringAI官方文档
http://www.jsqmd.com/news/1002412/

相关文章:

  • 植物大战僵尸杂交版重制版下载v0.22 2026最新版
  • 2026年恒温恒湿机选购指南:从实验室到工业车间,如何精准匹配场景需求? - 优质品牌商家
  • 私域团购55亿年流水背后:40万人自愿卖货的隐秘玩法?
  • 2026年石灰供应商实力评估:从产能、案例到服务,哪些厂家值得关注? - 优质品牌商家
  • 新手组员看过来:5分钟上手!用TortoiseGit(小乌龟)从Gitee拉取代码到提交PR的全流程图解
  • 别再卡了!用大白话拆解YouTube的“自适应码率”技术,看它如何偷偷帮你选画质
  • 手把手教你用USB转TTL给STM32F103C8T6最小系统板烧程序(附FlyMcu软件配置)
  • 从LPRNet到CRNN:我在RK3588上部署车牌识别的模型选型踩坑实录
  • 虚幻引擎新手开箱即用工程模板,含标准目录与可运行场景
  • 2026甄选:常州新娘跟妆专业品牌机构,RENA芮娜婚纱以高审美与匠心服务诠释婚礼妆容美学 - 品牌发掘
  • WechatDecrypt终极指南:3步轻松解密微信加密数据库
  • 全志TWI/I2C驱动实战:从设备树配置到用户态读写(Linux 4.9/5.4)
  • 别再只会调频率了!用运放搭波形发生器,占空比和幅值调节的坑我都帮你踩完了
  • CodeCombat容器化部署实践指南:游戏化编程学习平台的最佳方案
  • 别再手动改文献了!用Better BibTex插件5分钟搞定Zotero与Google Scholar格式同步
  • Go爬虫实战:用Chromedp绕过网站自动化检测的3个关键Flag(附完整代码)
  • Android虚拟摄像头终极指南:5分钟掌握隐私保护与创意特效
  • 如何用AB Download Manager提升3倍下载效率:免费开源解决方案完全指南
  • 在成都想买ECO棉床垫,到底哪家才靠谱? - 深圳市民HLL
  • 3步解锁Honey Select 2完整中文体验:新手必看汉化增强补丁配置指南
  • Robix系统的20项底层裸数据参数和源码实现,涉及硬件、通信、控制等多个技术领域。主要内容包括:地址总线时序参数剥离、触控信号原始配置、电源并联均流破除、逻辑门阵列直控、SPI闪存极限读写等核心技术
  • 2026年绵阳虫害防治公司选择指南:从白蚁灭治到四害消杀,这些机构实测有效! - 优质品牌商家
  • 2026年湖南中职学校择校观察:长沙医卫、技工及综合类院校多维对比与趋势分析 - 优质品牌商家
  • 移动端实时语义分割的救星?深入剖析DeepLabv3+中的深度可分离卷积与Xception
  • 保姆级教程:用Ubiqua Protocol Analyzer抓取并解密Zigbee网络数据(附CC2531嗅探器配置)
  • IPO前夜OpenAI收购Ona:为Codex补上安全地基,加速迈向企业级AI平台
  • 讲真的2026年天津地道天津菜 这5家值得推荐 - 本地品牌推荐
  • 20823个汉字结构化数据包:含拼音、五笔、部首、笔画、笔顺、释义及说文引文
  • 避坑指南:CGAL泊松表面重建效果不好?可能是这6个参数没调对
  • 2026年近期唐山信誉好的野营帐篷厂商选择与推荐指南 - 品牌鉴赏官2026