一站式了解Spring AI Alibaba的流式输出

引言

在使用Spring AI Alibaba等框架时,理解其流式输出是成功开发的必须项。在大模型应用中,由于生成完整回复可能需要数秒甚至更久,流式输出 (Streaming) 是提升用户体验的关键------它允许后端像"打字机"一样,生成一个 Token 就立即推送到前端,而不需要等待整个响应完成。

Spring AI Alibaba 中,流式输出主要依赖于 Project ReactorFlux 异步流和 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

因此,必须结合两个维度判断

  1. OutputType → 确定阶段和来源
  2. Message 子类型 + 元数据 → 确定具体内容语义

这体现了 分层解耦 的设计:

  • 第一层:Agent 执行流程(谁在输出?是否完成?)
  • 第二层:消息语义(是回答?是工具请求?是思考?)

StreamingOutput.message() 返回的消息有多种类型,需要结合 OutputType 和消息元数据进行区分处理:

OutputType 消息类型 判断条件 说明
AGENT_MODEL_STREAMING / AGENT_MODEL_FINISHED 模型普通响应 AssistantMessagemetadata.reasoningContent 为空 模型的实际回复内容,通过 getText() 获取
AGENT_MODEL_STREAMING / AGENT_MODEL_FINISHED 模型 Thinking AssistantMessagemetadata.reasoningContent 不为空 模型的思考过程(如 DeepSeek 等支持 Thinking 的模型)
AGENT_MODEL_FINISHED 工具调用请求 AssistantMessagehasToolCalls()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_FINISHEDAssistantMessage 中。
  • 工具执行结果则通过 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基本都是基于这样的流式输出思想来的。

相关推荐
Lonely丶墨轩15 小时前
从登录入口窥见架构:一个企业级双Token认证系统的深度拆解
java·数据库·sql
秋说15 小时前
华为 DevKit 25.2.rc1 源码迁移分析使用教程(openEuler + ARM64)
后端
ServBay15 小时前
C# 成为 2025 年的编程语言,7个C#技巧助力开发效率
后端·c#·.net
黑符石15 小时前
【论文研读】Madgwick 姿态滤波算法报告总结
人工智能·算法·机器学习·imu·惯性动捕·madgwick·姿态滤波
JQLvopkk16 小时前
智能AI“学习功能”在程序开发部分的逻辑
人工智能·机器学习·计算机视觉
我的offer在哪里16 小时前
Hugging Face:让大模型触手可及的魔法工厂
人工智能·python·语言模型·开源·ai编程
收获不止数据库16 小时前
黄仁勋2026CES演讲复盘:旧世界,裂开了!
大数据·数据库·人工智能·职场和发展
老胡全房源系统16 小时前
房产中介管理系统哪一款性价比高
大数据·人工智能·房产经纪人培训