目录
[一、实测现象 & 核心代码展示](#一、实测现象 & 核心代码展示)
[1. 方案一:Spring AI 实现(浏览器无明显流式)](#1. 方案一:Spring AI 实现(浏览器无明显流式))
[二、核心原理:同为 text/html,流式效果为何差异巨大?](#二、核心原理:同为 text/html,流式效果为何差异巨大?)
[1. LangChain4j:原生细粒度实时推送](#1. LangChain4j:原生细粒度实时推送)
[2. Spring AI:框架批量聚合推送](#2. Spring AI:框架批量聚合推送)
[3. 关键补充:delayElements 作用](#3. 关键补充:delayElements 作用)
[三、Spring AI 与 LangChain4j 全方位核心对比](#三、Spring AI 与 LangChain4j 全方位核心对比)
[四、两种响应类型对比:text/html VS text/event-stream](#四、两种响应类型对比:text/html VS text/event-stream)
[1. 浏览器地址栏](#1. 浏览器地址栏)
[2. Apifox](#2. Apifox)
在基于 Spring Boot + Spring WebFlux 对接大模型实现逐字流式对话 的开发过程中,我遇到了一个非常典型且容易困惑的问题: 两套接口均使用 text/html;charset=UTF-8 作为响应类型,且都在浏览器地址栏进行测试,但流式表现截然不同:
- 基于 Spring AI ChatClient 实现:内容一次性全部输出,看不到逐字流式效果;
- 基于 LangChain4j 原生回调 实现:逐段输出,流式观感十分明显。
除此之外,开发中还陆续碰到 SSE 接口中文乱码、浏览器与 Apifox 测试表现不一致、添加延迟后流式效果恢复 等问题。
本文结合真实业务代码、框架底层推送机制、HTTP 分块传输、客户端解析规则,新增 Spring AI 与 LangChain4j 核心对比,全方位拆解所有现象背后的原理,同时给出不同场景下的代码选型与生产最佳实践。
一、实测现象 & 核心代码展示
两套接口响应类型完全一致 、均基于 WebFlux Flux 实现流式,仅框架与编码方式不同。
1. 方案一:Spring AI 实现(浏览器无明显流式)
java
package org.example.springaixushu.controller;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.time.Duration;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/ai")
public class AiController {
@Autowired
private ChatClient chatClient;
@GetMapping(value = "/stream", produces = "text/html;charset=UTF-8")
public Flux<String> streamChatResponse(@RequestParam String question) {
return chatClient.prompt(question)
.stream()
.content();
// .delayElements(Duration.ofMillis(10));
}
}
- 方案二:LangChain4j 实现(浏览器流式效果明显)
java
package org.example.langchain4j_springboot.controller;
import dev.langchain4j.community.model.dashscope.QwenChatModel;
import dev.langchain4j.community.model.dashscope.QwenStreamingChatModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/ai")
public class ChatControler {
@Autowired
QwenChatModel qwenChatModel;
@Autowired
QwenStreamingChatModel streamingChatModel;
@RequestMapping(value = "/stream_chat",produces ="text/html;charset=UTF-8")
public Flux<String> test3(@RequestParam(defaultValue="你是谁") String message) {
return Flux.create(sink -> {
streamingChatModel.chat(message, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
sink.next(partialResponse); // 逐次返回部分响应
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
sink.complete(); // 完成整个响应流
}
@Override
public void onError(Throwable error) {
sink.error(error); // 异常处理
}
});
});
}
}
二、核心原理:同为 text/html,流式效果为何差异巨大?
根本原因:框架的推送粒度、缓冲策略、数据下发速度完全不同 ,浏览器text/html增量渲染依赖分段 + 间隔推送。
1. LangChain4j:原生细粒度实时推送
- 纯底层大模型 SDK,无缓冲、无聚合;
- 大模型每生成一个字 / 词,立刻触发
onPartialResponse回调; - 数据实时推送给浏览器,搭配大模型天然生成延迟;
- 浏览器增量渲染生效 → 肉眼可见逐字流式。
2. Spring AI:框架批量聚合推送
- Spring 官方一体化框架,为性能做了数据缓冲聚合;
- 不会逐字下发,而是攒成一段文本后批量高速推送;
- HTTP 协议自动合并分块,浏览器只收到一个完整数据块;
- 增量渲染失效 → 内容一次性输出。
3. 关键补充:delayElements 作用
仅测试用 ,强制拆分数据间隔,让 Spring AI 模拟出流式效果,生产环境必须删除。
三、Spring AI 与 LangChain4j 全方位核心对比
这是本文新增核心章节,从定位、设计、流式实现、生态等维度,彻底厘清两个框架的本质区别:
表格
| 对比维度 | Spring AI | LangChain4j |
|---|---|---|
| 框架定位 | Spring 官方出品一体化 AI 集成框架,深度绑定 Spring 生态 | 独立开源底层大模型 SDK,无框架绑定,跨平台通用 |
| 设计理念 | 约定大于配置,极简 API,屏蔽底层细节,开箱即用 | 灵活可扩展,暴露底层回调,支持高度自定义 |
| 流式实现方式 | 封装ChatClient,自动处理流转换、数据聚合 |
原生StreamingChatModel+ 回调接口,手动桥接 WebFlux |
| 数据推送策略 | 批量聚合推送(性能优先) | 逐字 / 逐片段实时推送(原生实时性优先) |
| 代码风格 | 链式调用,极简优雅,代码量极少 | 命令式编程,需手动写回调、流管理、异常处理 |
| 生态适配 | 完美兼容 Spring Boot/WebFlux/Security 等全家桶 | 可适配 Spring、Quarkus、无框架等任意 Java 环境 |
| 学习成本 | 极低,Spring 开发者直接上手 | 中等,需理解异步回调、响应式流桥接 |
| 自定义能力 | 中低,适合标准化 AI 场景 | 极高,可自定义每一步流式、prompt、模型调用 |
| 浏览器 text/html 流式 | 无天然效果,需加延迟 | 天然逐字流式,无需任何配置 |
| 生产适用场景 | 标准化 AI 接口、前后端分离、企业级 Spring 项目 | 深度自定义 AI、老项目改造、需要细粒度控制流式 |
四、两种响应类型对比:text/html VS text/event-stream
这是乱码、工具适配问题的核心根源:
表格
| 响应类型 | 协议标准 | 浏览器表现 | 专业工具 (Apifox) | 适用场景 |
|---|---|---|---|---|
text/html;charset=UTF-8 |
标准 HTML 类型 | 增量渲染,LangChain4j 天然流式 | 等待全量响应,无实时流 | 浏览器直访、内部简易接口 |
text/event-stream |
标准 SSE 协议 | 地址栏解析异常,中文乱码 | 完美逐字流,无乱码 | 前后端分离、生产级接口 |
五、不同测试工具表现差异
1. 浏览器地址栏
text/html:LangChain4j 有流式,Spring AI 无流式,不乱码;text/event-stream:解析 SSE 协议失败,必乱码,不推荐使用。
2. Apifox
text/html:默认无流式,需手动开 Raw 视图;text/event-stream:最优选择,完美支持 SSE 流式,无乱码。
六、全文总结
-
流式差异根源 LangChain4j 原生逐字实时推送,Spring AI 批量聚合高速推送,是浏览器流式效果天差地别的核心原因。
-
两大框架核心区别 Spring AI 极简、高性能、绑定 Spring 生态,适合标准化开发; LangChain4j 灵活、底层、无侵入,适合自定义流式场景。
-
响应类型选型 浏览器直访用
text/html,生产前后端分离用text/event-stream(SSE)。 -
测试工具规范 浏览器测
text/html,Apifox 测 SSE,拒绝用地址栏测试标准流式接口。 -
关键技巧
delayElements仅用于测试可视化,生产环境必须删除。