LangChain4j全集-15-Response Streaming
Response Streaming梳理
下面我按这篇文档的结构,结合你是 Spring Boot 开发者的视角,把 LangChain4j Response Streaming 讲清楚。
这篇文档讲的是:如何让大模型的回答像 ChatGPT 那样一个字、一段一段地流式返回,而不是等整段回答生成完再一次性返回。
1. Response Streaming 是什么?
大模型生成文本时,本质上不是一次性生成完整答案,而是:
一个 token 一个 token 地生成。
这里的 token 可以简单理解为:
- 一个字
- 一个词
- 一个词的一部分
- 一个标点
- 一小段文本
不同模型、不同服务商对 token 的切分方式不同。
普通非流式调用是这样:
用户提问
↓
等待模型完整生成
↓
一次性返回完整答案
流式调用是这样:
用户提问
↓
模型生成一点,就返回一点
↓
前端马上显示
↓
继续生成,继续显示
类似 ChatGPT 的打字机效果。
2. 为什么要用 Response Streaming?
文档里说它可以显著改善用户体验。
原因很简单:
如果用户问:
请帮我写一篇 1000 字的文章
非流式模式下,用户可能要等 5 秒、10 秒甚至更久,页面上什么都没有。
而流式模式下,可能 0.5 秒后就开始看到内容:
当然可以,下面是一篇关于...
这样用户会觉得:
- 系统响应更快
- 页面没有卡住
- 更像真实 AI 助手
- 适合聊天机器人、客服、写作助手等场景
3. 这篇文档讲的是 Low-level LLM API
文档开头有一句说明:
This page describes response streaming with a low-level LLM API.
See AI Services for a high-level LLM API.
意思是:
这篇文档讲的是 底层 API 的流式响应。
LangChain4j 里大致有两种使用方式:
方式一:Low-level API
你直接操作模型对象,比如:
StreamingChatModel
ChatModel
LanguageModel
这种方式比较底层、灵活,但是需要自己处理:
- 消息
- 回调
- token
- 错误
- 流式事件
- 工具调用等
方式二:AI Services
这是更高级的封装。
类似这样:
interface Assistant {
String chat(String userMessage);
}
或者流式:
interface Assistant {
TokenStream chat(String userMessage);
}
AI Services 更适合业务开发,因为它可以帮你自动处理很多东西,比如:
- Prompt 模板
- Chat Memory
- Tools
- RAG
- Structured Output
如果你是 Spring Boot 开发者,刚开始学习,我建议:
先理解 Low-level API 的原理,再在项目里优先考虑 AI Services。
4. ChatModel 和 StreamingChatModel 的关系
文档里提到:
For the ChatModel and LanguageModel interfaces, there are corresponding StreamingChatModel and StreamingLanguageModel interfaces.
意思是 LangChain4j 有普通模型接口,也有对应的流式模型接口。
4.1 ChatModel
ChatModel 是普通的聊天模型接口。
它的特点是:
等模型完整生成后,一次性返回完整结果。
大概类似这样:
ChatModel model = ...;
ChatResponse response = model.chat("你好,介绍一下 LangChain4j");
System.out.println(response.aiMessage().text());
结果是完整返回:
LangChain4j 是一个用于 Java 应用集成大语言模型的框架...
4.2 StreamingChatModel
StreamingChatModel 是流式聊天模型接口。
它的特点是:
模型生成一部分,就回调一部分。
类似这样:
StreamingChatModel model = ...;
model.chat("你好,介绍一下 LangChain4j", new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
System.out.print(partialResponse);
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
System.out.println("\n生成完成");
}
@Override
public void onError(Throwable error) {
error.printStackTrace();
}
});
这时候控制台可能会逐步输出:
LangChain4j 是
一个用于 Java
应用集成大语言模型
的框架...
5. LanguageModel 和 StreamingLanguageModel
文档还提到:
LanguageModel
StreamingLanguageModel
这两个和 ChatModel 类似,但概念上有些区别。
5.1 LanguageModel
更偏向传统文本生成。
例如:
输入一个 prompt,输出一段文本
适合这类场景:
请续写下面这段话:
从前有一座山...
5.2 ChatModel
更偏向聊天场景。
它通常支持多轮消息,例如:
UserMessage
AiMessage
SystemMessage
ToolExecutionResultMessage
也就是说它知道:
- 哪些话是用户说的
- 哪些话是 AI 说的
- 哪些话是系统指令
- 哪些话是工具调用结果
现在大多数大模型应用,尤其是聊天机器人、AI 助手,都会优先使用 ChatModel 或 StreamingChatModel。
6. StreamingChatResponseHandler 是核心
文档中的核心接口是:
public interface StreamingChatResponseHandler {
default void onPartialResponse(String partialResponse) {}
default void onPartialResponse(
PartialResponse partialResponse,
PartialResponseContext context
) {}
default void onPartialThinking(PartialThinking partialThinking) {}
default void onPartialThinking(
PartialThinking partialThinking,
PartialThinkingContext context
) {}
default void onPartialToolCall(PartialToolCall partialToolCall) {}
default void onPartialToolCall(
PartialToolCall partialToolCall,
PartialToolCallContext context
) {}
default void onCompleteToolCall(CompleteToolCall completeToolCall) {}
default void onUnmappedRawEvent(Object rawEvent) {}
void onCompleteResponse(ChatResponse completeResponse);
void onError(Throwable error);
}
这个接口的作用是:
你告诉 LangChain4j:当模型流式返回不同类型的数据时,应该怎么处理。
它类似 Spring 里的回调接口,或者事件监听器。
你可以理解成:
模型开始生成
↓
每生成一点文本,调用 onPartialResponse
↓
如果生成思考过程,调用 onPartialThinking
↓
如果生成工具调用,调用 onPartialToolCall
↓
如果工具调用完整了,调用 onCompleteToolCall
↓
最终完整回答生成完,调用 onCompleteResponse
↓
如果出错,调用 onError
7. onPartialResponse(String partialResponse)
这是最常用的方法。
default void onPartialResponse(String partialResponse) {}
它表示:
当模型生成了一小段文本时,就会调用这个方法。
例如用户问:
介绍一下 Spring Boot
模型可能分多次返回:
Spring
Boot
是一个
用于简化
Spring 应用开发
每次返回一小段,就调用一次:
@Override
public void onPartialResponse(String partialResponse) {
System.out.print(partialResponse);
}
在 Spring Boot 里,这个方法通常用来:
- 推送给前端 SSE
- 推送给 WebSocket
- 写入响应流
- 实现打字机效果
作用总结
| 方法 | 作用 |
|---|---|
onPartialResponse(String partialResponse) |
接收模型生成的文本片段 |
| 常见用途 | 实时推送到前端 |
| 类似场景 | ChatGPT 边生成边显示 |
8. onPartialResponse(PartialResponse, PartialResponseContext)
文档里还提到另一个重载方法:
default void onPartialResponse(
PartialResponse partialResponse,
PartialResponseContext context
) {}
它和 onPartialResponse(String) 的区别是:
String 版本只给你文本内容。
而这个版本给你的信息更多。
可以简单理解为:
PartialResponse partialResponse
代表这次流式返回的内容对象。
PartialResponseContext context
代表这次返回时的一些上下文信息。
可能包括服务商相关信息、响应上下文、事件信息等。
如果你只是做普通聊天页面,通常用这个就够了:
onPartialResponse(String partialResponse)
如果你需要更精细控制,比如:
- 区分模型响应的不同事件
- 获取更完整的上下文
- 兼容不同模型服务商的特殊返回
- 做日志、监控、调试
可以使用带 context 的版本。
9. partial response 不一定是一个 token
文档强调:
Depending on the LLM provider, partial response text can consist of a single or more tokens.
意思是:
每次回调返回的内容,不一定刚好是一个 token。
有些服务商一次返回一个 token:
你
好
,
我
是
AI
有些服务商一次返回一小段:
你好,
我是
AI 助手。
所以开发时不要假设:
- 一次回调就是一个字
- 一次回调就是一个完整词
- 一次回调就是一句话
正确做法是:
收到什么就追加什么。
例如:
StringBuilder builder = new StringBuilder();
@Override
public void onPartialResponse(String partialResponse) {
builder.append(partialResponse);
}
10. onPartialThinking:接收模型的思考过程
文档里提到:
default void onPartialThinking(PartialThinking partialThinking) {}
default void onPartialThinking(
PartialThinking partialThinking,
PartialThinkingContext context
) {}
这个方法表示:
当模型流式输出 reasoning / thinking 内容时,会调用这个方法。
现在有些模型支持“推理过程”或者“思考过程”。
比如用户问:
小明有 3 个苹果,又买了 5 个,一共有几个?
模型可能内部会有思考:
用户问的是加法问题,3 + 5 = 8...
然后最终回答:
一共有 8 个苹果。
部分模型可能把这类“思考过程”也作为流式事件返回。
普通回答 vs Thinking
可以这样理解:
| 类型 | 说明 | 是否展示给用户 |
|---|---|---|
| PartialResponse | 最终回答的一部分 | 一般展示 |
| PartialThinking | 模型思考/推理过程 | 看业务决定 |
是否应该展示 Thinking?
这个要看你的业务。
如果你做的是:
- 编程助手
- 数学解题
- 推理演示
- AI 调试工具
可以考虑展示部分 reasoning。
如果你做的是:
- 客服机器人
- 普通问答助手
- 企业内部助手
一般不建议直接展示模型思考过程。
因为它可能:
- 让用户困惑
- 暴露不必要的中间内容
- 包含不稳定推理
- 不同模型支持程度不同
11. onPartialToolCall:接收工具调用片段
文档里提到:
default void onPartialToolCall(PartialToolCall partialToolCall) {}
default void onPartialToolCall(
PartialToolCall partialToolCall,
PartialToolCallContext context
) {}
这个和 LangChain4j 的 Tools / Function Calling 有关。
11.1 什么是 Tool Call?
大模型本身不会真正查数据库、查天气、调用接口。
但是它可以决定:
我需要调用某个工具来完成任务。
比如你定义了一个工具:
public class WeatherTool {
@Tool
public String getWeather(String city) {
return "北京今天晴,25 度";
}
}
用户问:
北京今天天气怎么样?
模型可能不会直接回答,而是生成一个工具调用:
{
"name": "getWeather",
"arguments": {
"city": "北京"
}
}
这就是 tool call。
11.2 为什么 Tool Call 也需要流式?
因为工具调用的参数可能也是一点一点生成的。
比如模型生成这个 JSON:
{
"name": "getWeather",
"arguments": {
"city": "北京"
}
}
流式过程中可能分几段返回:
{
"name": "get
Weather",
"arguments":
{
"city": "北京"
}
所以 LangChain4j 提供了:
onPartialToolCall(...)
用来接收尚未完整的工具调用片段。
11.3 实际开发中怎么用?
如果你是初学者,大多数情况下不用自己处理 onPartialToolCall。
因为:
- 使用 AI Services 时,LangChain4j 可以帮你处理 tools
- 你更常关心最终结果
- 手动处理 tool call 复杂度较高
但如果你在做底层框架、调试工具调用、实现自定义 agent,那么这个方法就很重要。
12. onCompleteToolCall:工具调用完整生成
文档里提到:
default void onCompleteToolCall(CompleteToolCall completeToolCall) {}
它表示:
当模型完整生成了一个工具调用时,会调用这个方法。
也就是说:
onPartialToolCall 是片段。
onCompleteToolCall 是完整结果。
比如最终完整的工具调用是:
{
"name": "getWeather",
"arguments": {
"city": "北京"
}
}
这时你就可以在 onCompleteToolCall 里拿到完整工具名和参数。
简单理解
| 方法 | 时机 | 用途 |
|---|---|---|
onPartialToolCall |
工具调用生成中 | 看中间片段、做调试 |
onCompleteToolCall |
工具调用生成完 | 可以执行工具调用 |
13. onUnmappedRawEvent:未映射的原始事件
文档里提到:
default void onUnmappedRawEvent(Object rawEvent) {}
这个方法的意思是:
如果模型服务商返回了某些 LangChain4j 还没有标准化映射的原始流式事件,会调用这个方法。
不同大模型服务商的流式协议不完全一样。
比如:
- OpenAI
- Anthropic Claude
- Google Gemini
- Ollama
- DashScope
- Azure OpenAI
它们返回的流式事件格式可能不同。
LangChain4j 会尽量把它们统一抽象成:
- PartialResponse
- PartialThinking
- PartialToolCall
- CompleteToolCall
- CompleteResponse
但是有时候服务商会返回一些特殊事件,LangChain4j 没有对应的统一对象。
这时就会进入:
onUnmappedRawEvent(Object rawEvent)
这个方法有什么用?
主要用于:
- 调试
- 日志记录
- 兼容某个服务商的特殊功能
- 排查为什么某些事件没有被标准处理
例如:
@Override
public void onUnmappedRawEvent(Object rawEvent) {
log.info("收到未映射的原始事件: {}", rawEvent);
}
对于普通业务开发,这个不是必须实现。
14. onCompleteResponse:完整响应结束
文档中的必实现方法之一:
void onCompleteResponse(ChatResponse completeResponse);
它表示:
模型本次完整回答已经生成结束。
这个方法很重要。
因为在流式过程中,你会不断收到片段:
onPartialResponse("Spring")
onPartialResponse(" Boot")
onPartialResponse(" 是一个")
onPartialResponse("框架")
等全部结束后,会调用:
onCompleteResponse(...)
这里可以拿到完整的 ChatResponse。
completeResponse 里面通常有什么?
一般可以包含:
- AI 最终消息
- 完整文本
- token 使用情况
- finish reason
- metadata
- 工具调用信息
具体字段取决于 LangChain4j 版本和模型服务商。
常见用途
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
log.info("AI 完整响应: {}", completeResponse.aiMessage().text());
}
可以在这里做:
- 保存聊天记录
- 统计 token 用量
- 记录日志
- 关闭 SSE 连接
- 通知前端回答结束
- 做后续业务处理
15. onError:异常处理
另一个必实现方法:
void onError(Throwable error);
它表示:
流式调用过程中发生异常。
可能的原因包括:
- API Key 错误
- 网络超时
- 模型服务不可用
- 请求参数错误
- 触发模型服务商限流
- 响应解析失败
- 用户中途断开连接
实际开发中一定要实现这个方法。
例如:
@Override
public void onError(Throwable error) {
log.error("AI 流式响应失败", error);
}
如果你使用 SSE,需要在这里通知前端:
emitter.completeWithError(error);
16. 完整执行流程
你可以把整个流式响应过程理解成这样:
用户发送问题
↓
调用 StreamingChatModel.chat(...)
↓
LangChain4j 请求大模型
↓
大模型开始生成
↓
onPartialResponse:收到文本片段
↓
onPartialThinking:收到思考片段,可选
↓
onPartialToolCall:收到工具调用片段,可选
↓
onCompleteToolCall:工具调用完整,可选
↓
onCompleteResponse:完整响应结束
↓
结束
如果中途失败:
用户发送问题
↓
调用模型
↓
发生异常
↓
onError
17. Spring Boot 中怎么理解这个东西?
如果你是 Spring Boot 开发者,可以把它类比成:
普通接口
@GetMapping("/chat")
public String chat(String message) {
return aiService.chat(message);
}
特点:
- 后端等 AI 完整生成
- 一次性返回给前端
- 简单,但用户体验一般
流式接口
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter chatStream(String message) {
SseEmitter emitter = new SseEmitter();
streamingChatModel.chat(message, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
try {
emitter.send(partialResponse);
} catch (IOException e) {
emitter.completeWithError(e);
}
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
emitter.complete();
}
@Override
public void onError(Throwable error) {
emitter.completeWithError(error);
}
});
return emitter;
}
这样前端就能不断收到后端推过来的内容。
18. 一个更贴近 Spring Boot 的示例
假设你用 SseEmitter 实现流式返回:
@RestController
@RequestMapping("/ai")
public class AiController {
private final StreamingChatModel streamingChatModel;
public AiController(StreamingChatModel streamingChatModel) {
this.streamingChatModel = streamingChatModel;
}
@GetMapping(value = "/chat-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter chatStream(@RequestParam String message) {
SseEmitter emitter = new SseEmitter(0L);
streamingChatModel.chat(message, new StreamingChatResponseHandler() {
@Override
public void onPartialResponse(String partialResponse) {
try {
emitter.send(SseEmitter.event()
.name("message")
.data(partialResponse));
} catch (IOException e) {
emitter.completeWithError(e);
}
}
@Override
public void onCompleteResponse(ChatResponse completeResponse) {
try {
emitter.send(SseEmitter.event()
.name("done")
.data("[DONE]"));
} catch (IOException e) {
emitter.completeWithError(e);
return;
}
emitter.complete();
}
@Override
public void onError(Throwable error) {
emitter.completeWithError(error);
}
});
return emitter;
}
}
前端可以用:
const eventSource = new EventSource("/ai/chat-stream?message=介绍一下Spring Boot");
eventSource.addEventListener("message", event => {
console.log("收到片段:", event.data);
});
eventSource.addEventListener("done", event => {
console.log("完成");
eventSource.close();
});
19. 什么时候用 Response Streaming?
推荐使用流式响应的场景:
| 场景 | 是否推荐 |
|---|---|
| AI 聊天机器人 | 强烈推荐 |
| AI 客服 | 强烈推荐 |
| 文章生成 | 推荐 |
| 代码生成 | 推荐 |
| 长文本总结 | 推荐 |
| 简短分类任务 | 不一定需要 |
| 后台批处理 | 不一定需要 |
| JSON 结构化输出 | 视情况而定 |
20. 流式响应和普通响应的对比
| 对比项 | 普通响应 | 流式响应 |
|---|---|---|
| 返回方式 | 一次性返回 | 分段返回 |
| 用户等待时间 | 较长 | 较短 |
| 体验 | 像普通接口 | 像 ChatGPT |
| 开发复杂度 | 低 | 稍高 |
| 前端处理 | 简单 | 需要 SSE/WebSocket |
| 后端处理 | 普通 Controller | 需要流式推送 |
| 适合场景 | 短任务 | 长文本、聊天、生成类任务 |
21. 初学者最该掌握哪些点?
如果你刚开始学 LangChain4j 的流式响应,优先掌握这些:
第一,知道为什么要流式
因为用户不想一直等。
第二,知道核心接口
StreamingChatModel
负责发起流式调用。
StreamingChatResponseHandler
负责接收流式事件。
第三,重点掌握三个方法
onPartialResponse
onCompleteResponse
onError
这三个最常用。
第四,Spring Boot 中一般配合 SSE 或 WebSocket
常见方式:
- SSE:简单,适合服务端单向推送
- WebSocket:适合双向实时通信
AI 聊天场景下,SSE 用得非常多。
22. 这篇文档的知识点总表
| 知识点 | 是干什么的 | 作用 |
|---|---|---|
| Response Streaming | 流式响应 | 边生成边返回 |
| token | 模型生成的最小片段 | 流式输出的基础 |
| Low-level LLM API | 底层模型 API | 灵活,但需要自己处理回调 |
| AI Services | 高级封装 API | 更适合业务开发 |
| ChatModel | 普通聊天模型 | 一次性返回完整回答 |
| StreamingChatModel | 流式聊天模型 | 分段返回回答 |
| LanguageModel | 普通文本模型 | 文本生成 |
| StreamingLanguageModel | 流式文本模型 | 流式文本生成 |
| StreamingChatResponseHandler | 流式响应处理器 | 接收各种流式事件 |
| onPartialResponse | 文本片段回调 | 实时展示给用户 |
| PartialResponseContext | 文本片段上下文 | 获取更详细信息 |
| onPartialThinking | 思考过程回调 | 接收 reasoning 内容 |
| onPartialToolCall | 工具调用片段 | 处理 function calling 流式片段 |
| onCompleteToolCall | 完整工具调用 | 工具调用生成完成 |
| onUnmappedRawEvent | 未映射原始事件 | 调试服务商特殊事件 |
| onCompleteResponse | 完整响应结束 | 保存结果、关闭流 |
| onError | 异常回调 | 错误处理 |
23. 你可以这样记
最简单的理解方式:
StreamingChatModel 是流式模型
StreamingChatResponseHandler 是监听器
onPartialResponse 是每次收到一点内容
onCompleteResponse 是全部结束
onError 是出错
如果你只是做一个 Spring Boot + LangChain4j 的 AI 聊天接口,最开始只需要关心:
onPartialResponse(String partialResponse)
onCompleteResponse(ChatResponse completeResponse)
onError(Throwable error)
其他的:
onPartialThinking
onPartialToolCall
onCompleteToolCall
onUnmappedRawEvent
可以等你学习 Tools、Agent、Reasoning 模型时再深入。
更多推荐




所有评论(0)