引言
在使用Spring AI Alibaba等框架时,理解其流式输出是成功开发的必须项。在大模型应用中,由于生成完整回复可能需要数秒甚至更久,流式输出 (Streaming) 是提升用户体验的关键------它允许后端像"打字机"一样,生成一个 Token 就立即推送到前端,而不需要等待整个响应完成。
在 Spring AI Alibaba 中,流式输出主要依赖于 Project Reactor 的 Flux 异步流和 Server-Sent Events (SSE) 协议。
下面我将逐层深入解析流式输出。
什么是流式输出
在DeepSeek这样具有深度思考的LLM出世后,大家在使用其深度思考模式,可以看到其思考过程,这样并没有一下子全部给出答案的输出方式,则是流式输出。
在传统的同步调用中,客户端发送请求后需等待模型完整生成所有文本,然后一次性接收全部响应。而流式输出则是在模型逐步生成 token 的过程中,将每个新生成的 token 或片段实时推送给客户端。
例如:
- 用户提问:"介绍一下 Spring AI Alibaba。"
- 模型一边思考一边输出:"Spring AI Alibaba 是......","由阿里云与 Spring 团队合作......","支持通义千问......"
- 客户端每收到一段就立即显示,无需等待整段完成
统一抽象 + 类型区分: StreamingOutput
ok,上面讲了基础概念,现在来讲一下涉及到Spring AI Alibaba这个框架是怎么样处理流式输出的。
统一输出类型
在 Agent 工作流中,所有的输出都被封装在 NodeOutput 的子类 StreamingOutput 中。
- 统一抽象:无论当前 Agent 是在"思考"、"说话"还是"调工具",你拿到的都是同一个对象。
- 状态机思维 :你需要通过
OutputType状态来决定如何"分流"处理这些数据
好处:
- 简化客户端处理逻辑(只需订阅一个
Flux<NodeOutput>) - 支持混合流(模型输出 + 工具调用 + Hook 事件交错出现)
类型区分
虽然输出类型统一,但通过 OutputType 枚举明确标识当前片段的 来源 和 状态:
- 来源:模型(MODEL)、工具(TOOL)、Hook(HOOK)
- 状态:流式增量(STREAMING) vs 完成(FINISHED)
关键设计原则 : "统一载体,差异化语义" ------ 用同一类型承载不同语义,靠元数据区分
| OutputType | 说明 |
|---|---|
AGENT_MODEL_STREAMING |
模型推理的流式增量内容 |
AGENT_MODEL_FINISHED |
模型推理完成,可获取全量内容 |
AGENT_TOOL_STREAMING |
工具调用的流式增量内容 |
AGENT_TOOL_FINISHED |
工具调用完成 |
AGENT_HOOK_STREAMING |
Hook 节点的流式增量内容 |
AGENT_HOOK_FINISHED |
Hook 节点完成 |
提示:
- 对于 Hook 等通常非流式的节点,直接使用
AGENT_HOOK_FINISHED读取内容即可 - 并非所有节点输出都有意义,尤其是 Hook 节点可能产生无效输出,建议通过
OutputType进行过滤
核心机制解析
消息类型的多维判断
即使同是 AGENT_MODEL_FINISHED,也可能代表:
- 模型的最终回答(普通
AssistantMessage) - 模型发起的工具调用请求(
hasToolCalls() == true) - 模型的思考过程(
metadata.reasoningContent != null)
因此,必须结合两个维度判断:
OutputType→ 确定阶段和来源Message子类型 + 元数据 → 确定具体内容语义
这体现了 分层解耦 的设计:
- 第一层:Agent 执行流程(谁在输出?是否完成?)
- 第二层:消息语义(是回答?是工具请求?是思考?)
StreamingOutput.message() 返回的消息有多种类型,需要结合 OutputType 和消息元数据进行区分处理:
| OutputType | 消息类型 | 判断条件 | 说明 |
|---|---|---|---|
AGENT_MODEL_STREAMING / AGENT_MODEL_FINISHED |
模型普通响应 | AssistantMessage 且 metadata.reasoningContent 为空 |
模型的实际回复内容,通过 getText() 获取 |
AGENT_MODEL_STREAMING / AGENT_MODEL_FINISHED |
模型 Thinking | AssistantMessage 且 metadata.reasoningContent 不为空 |
模型的思考过程(如 DeepSeek 等支持 Thinking 的模型) |
AGENT_MODEL_FINISHED |
工具调用请求 | AssistantMessage 且 hasToolCalls() 为 true |
模型请求调用工具,包含工具名称和参数 |
AGENT_TOOL_FINISHED |
工具响应结果 | ToolResponseMessage |
工具执行后的返回结果 |
代码示例
这是一段消息代码识别范例。
- 先判断 OutputType :通过
OutputType先确定是模型输出、工具输出还是 Hook 输出,再处理具体消息 - Thinking 消息 :部分模型(如 DeepSeek-R1)支持输出思考过程,通过
metadata.reasoningContent获取 - 工具调用 :工具调用请求通常在
AGENT_MODEL_FINISHED阶段出现,此时hasToolCalls()返回true - 流式 vs 完成 :
STREAMING阶段内容是增量的,FINISHED阶段可获取完整消息或执行结果
java
// 结合 OutputType 和消息类型进行处理
if (output instanceof StreamingOutput streamingOutput) {
OutputType type = streamingOutput.getOutputType();
Message message = streamingOutput.message();
// 处理模型流式输出
if (type == OutputType.AGENT_MODEL_STREAMING) {
if (message instanceof AssistantMessage assistantMessage) {
// 检查是否为 Thinking 消息
Object reasoningContent = assistantMessage.getMetadata().get("reasoningContent");
if (reasoningContent != null && !reasoningContent.toString().isEmpty()) {
System.out.print("[Thinking] " + reasoningContent);
} else {
// 普通模型响应(增量内容)
System.out.print(assistantMessage.getText());
}
}
}
// 处理模型输出完成
else if (type == OutputType.AGENT_MODEL_FINISHED) {
if (message instanceof AssistantMessage assistantMessage) {
if (assistantMessage.hasToolCalls()) {
// 工具调用请求
assistantMessage.getToolCalls().forEach(toolCall -> {
System.out.println("[Tool Call] " + toolCall.name() + ": " + toolCall.arguments());
});
} else {
// 模型完整响应
System.out.println("\n[Model Finished]");
}
}
}
// 处理工具执行结果
else if (type == OutputType.AGENT_TOOL_FINISHED) {
if (message instanceof ToolResponseMessage toolResponse) {
toolResponse.getResponses().forEach(response -> {
System.out.println("[Tool Result] " + response.name() + ": " + response.responseData());
});
}
}
}
关键事项
1. 并非所有 STREAMING 都有文本内容
- 只有
AGENT_MODEL_STREAMING会持续输出用户可见的文本。 AGENT_TOOL_STREAMING/AGENT_HOOK_STREAMING目前无实际意义,可忽略。
2. 工具调用发生在 MODEL_FINISHED 阶段
- 模型不会在流式过程中发出工具调用;
- 工具调用是一次性决策 ,出现在
AGENT_MODEL_FINISHED的AssistantMessage中。 - 工具执行结果则通过
AGENT_TOOL_FINISHED返回。
执行流程:
用户输入 → 模型流式生成(可能含 Thinking)→ 模型完成(可能含 ToolCall)→ Agent 调用工具 → 工具完成(返回结果)→ (可能再次进入模型推理)
3. Thinking 是可选能力
- 仅部分模型(如 DeepSeek-R1)支持
reasoningContent; - 普通模型该字段为空,应 fallback 到
getText()。
4. Hook 节点输出需谨慎处理
- Hook 通常是中间逻辑(如日志、权限校验),不一定产生有意义的消息;
- 文档明确建议:"建议通过 OutputType 进行过滤",避免误处理空/无效内容。
5. 完成事件 ≠ 最终答案
AGENT_MODEL_FINISHED可能触发工具调用,此时不是最终用户答案;- 最终答案通常出现在最后一次
AGENT_MODEL_FINISHED且!hasToolCalls()时
流式输出的核心逻辑图
这里给出两份示例图,喜欢看哪份都可以。 

总结
这就是Spring AI Alibaba的流式输出全部内容。
理解这里对使用相关框架非常重要,特别是Spring AI和Langchain基本都是基于这样的流式输出思想来的。