Java 转 Agent 开发之路(八):Spring AI Alibaba Graph 与多 Agent 协作的工程代价

举报
小不点和猫 发表于 2026/10/27 13:41:07 2026/10/27
【摘要】 第七篇讨论了多 Agent 协作的四种模式——顺序、并行、路由、监督者——但停留在概念层面。这一篇把监督者模式用可运行代码落地,然后回答一个概念篇回答不了的问题:当你真的把多个 Agent 组装成一个系统之后,代价是什么?Spring AI Alibaba(SAA)的三层结构是:底层 Spring AI 提供模型接入和 Advisor 链,中间 Graph Core 提供基于 DAG 的工作...

第七篇讨论了多 Agent 协作的四种模式——顺序、并行、路由、监督者——但停留在概念层面。这一篇把监督者模式用可运行代码落地,然后回答一个概念篇回答不了的问题:当你真的把多个 Agent 组装成一个系统之后,代价是什么?

Spring AI Alibaba(SAA)的三层结构是:底层 Spring AI 提供模型接入和 Advisor 链,中间 Graph Core 提供基于 DAG 的工作流编排、OverAllState 状态管理和断点恢复,上层 Agent Framework 提供 ReactAgent、SequentialAgent、SupervisorAgent 以及多 Agent 协作模式。理解这三层的分工,是理解“SAA 能做什么、不能做什么”的前提。这一篇要给出的核心判断是: SAA 的 Graph Core 解决的是“多 Agent 工作流的状态管理”,不是“多 Agent 运行时的治理” 。多租户隔离、成本归因、评测流水线、PII 掩码,这些仍然需要在 Graph 之上自己补。

一、临界点:什么时候单 Agent 真的不够了

在动手拆多 Agent 之前,先确认自己是否真的到了临界点。SAA 文档给出的信号很具体:单个 Agent 挂着十几二十个工具,工具选择准确率下降;上下文或记忆增长到单个 Agent 难以有效跟踪;任务需要明确专业化分工(规划器、研究员、评审者) 。

这三个信号背后的共同逻辑是上下文工程。多 Agent 拆分真正的收益不是“并行更快”,而是每个 Agent 只看它该看的东西。当一个 Agent 需要同时理解订单查询、退款政策、物流规则和用户历史时,它的 System Prompt 会膨胀到工具描述互相干扰的程度。拆分的本质是把一个过大的上下文切分成多个受控的局部上下文。

但拆分有一个容易被低估的代价: Agent 之间的通信本身需要上下文。Supervisor 需要知道每个子 Agent 的能力边界才能正确路由,子 Agent 需要从 Supervisor 那里拿到足够的任务描述才能执行。如果拆分之后每个子 Agent 仍然需要完整的全局上下文,那么拆分只是把复杂度从 Prompt 转移到了通信层。

二、监督者模式的最小可运行实现:ReactAgent 作为 AgentTool

SAA 的监督者模式在实现上非常简洁:把子 Agent 包装成工具,挂到 Supervisor Agent 上。ReactAgent 是 Agent Framework 中最常用的实现,内部由一个 model 节点和一个 tools 节点组成循环。AgentTool.create() 把一个 ReactAgent 转换成可以被另一个 ReactAgent 调用的工具。

一个最小的旅游规划监督者示例,包含三个专家 Agent 和一个监督者:

// 景点专家
ReactAgent attractionAgent = ReactAgent.builder()
    .name("attraction_agent")
    .model(chatModel)
    .description("景点推荐专家,根据城市和偏好推荐景点")
    .instruction("你是一个景点推荐专家。根据用户的需求推荐合适的景点,返回景点名称、简介和推荐理由。")
    .outputKey("attraction_result")
    .build();

// 住宿专家
ReactAgent hotelAgent = ReactAgent.builder()
    .name("hotel_agent")
    .model(chatModel)
    .description("住宿推荐专家,根据城市和预算推荐酒店")
    .instruction("你是一个住宿推荐专家。根据用户的需求推荐合适的酒店,返回酒店名称、价位和位置。")
    .outputKey("hotel_result")
    .build();

// 监督者:把专家 Agent 作为工具
ReactAgent supervisor = ReactAgent.builder()
    .name("travel_supervisor")
    .model(chatModel)
    .instruction("""
        你是旅游规划监督者。根据用户需求,调用合适的专家工具来收集信息。
        先调用景点专家,再调用住宿专家,最后整合两份结果给出完整方案。
        如果用户只问景点,不要调用住宿专家。
        """)
    .tools(
        AgentTool.create(attractionAgent),
        AgentTool.create(hotelAgent)
    )
    .maxIterations(10)
    .build();

String result = supervisor.call("帮我规划一下杭州两日游,预算中等,喜欢自然风光");

这个示例能跑,但能跑和能用是两件事。下面逐项拆解它在生产环境中的代价。

三、代价一:上下文膨胀是乘法,不是加法

Supervisor 的每一次路由决策,都需要把子 Agent 的工具描述、之前的调用结果、以及 Supervisor 自己的 System Prompt 一起放进上下文。当子 Agent 数量增加时,上下文膨胀的速度远快于线性。

一个具体的放大链条是这样的:每个子 Agent 的 description 进入 Supervisor 的工具列表,每个子 Agent 的执行结果进入 Supervisor 的对话历史。如果 Supervisor 做了 5 次路由决策,每次决策都要把完整的历史(包括前几次的工具结果)传给模型,那么 Token 消耗不是 5 次单轮调用之和,而是每一轮都在重复传递之前的所有轮次。一个客服对话场景中,如果 Supervisor 需要协调查询、退款、物流三个子 Agent,几十轮对话的 Token 成本会迅速失控。

SAA 提供了一些上下文瘦身参数,但它们需要被主动配置,而不是默认生效:

  • returnReasoningContents:子 Agent 的中间推理是否回传父流程,生产环境建议关闭,只回传最终结果。
  • includeContents:子 Agent 执行时是否携带父流程的全部上下文,建议设为 false,让子 Agent 专注自己的局部任务。

这两个参数的默认值在调试期是有用的(true 能看清全链路),但在生产环境如果不收窄,它们会把 Supervisor 的上下文膨胀问题放大一个量级。一个务实的配置原则是:调试期全开,生产期全关,用轨迹日志而不是上下文来观测中间过程。

四、代价二:路由准确率会随子 Agent 数量衰减

监督者模式的路由决策本质上是一次 LLM 分类。Supervisor 从工具列表中选出“该调用哪个子 Agent”,这个选择的质量取决于工具描述的区分度和子 Agent 数量。

当子 Agent 只有两三个时,路由通常可靠。但当子 Agent 增加到七八个、且类别之间存在语义重叠时(比如“订单查询”和“物流查询”的边界在用户说“我的东西到哪了”时并不总是清晰),路由准确率会下降。更严重的是,路由错误在监督者模式中是隐式的:Supervisor 选错了子 Agent,子 Agent 会在一个错误的前提任务描述下工作,而且它通常没有能力识别“我本来不该被调用”。最终输出可能看起来合理,但偏离了用户的真实意图。

SAA 1.1.2.0 引入的 Agent Skills 提供了一条缓解路径:技能以渐进式披露的方式加载,System Prompt 中只注入技能列表(name、description、skillPath),模型在需要时通过 read_skill 加载完整内容。这减少了工具描述的常驻上下文占用,但没有从根本上解决“子 Agent 数量增加导致路由候选集膨胀”的问题。当路由候选集超过一定规模,分层路由(先选领域,再选具体 Agent)比扁平的 Supervisor 路由更可靠,但这需要额外的编排层,不在 AgentTool 的默认能力范围内。

五、代价三:AgentTool 的同步阻塞与流式输出的冲突

这是一个 SAA 当前版本中具体的工程限制,不是抽象的设计权衡。

ReactAgent.call(List<Message>) 在 v1.1.2.2 中是同步的,没有原生的 Flux 流式 Agent 方法。AgentTool 的默认调用方式同样是同步阻塞的。这意味着当 Supervisor 调用一个子 Agent 时,整个调用链会阻塞到子 Agent 返回完整结果,中间过程无法流式推送给前端。一个旅游规划场景中,用户可能在十几秒内看不到任何输出,直到三个专家 Agent 全部串行执行完毕。

SAA 在 Graph 层面提供了异步工具执行和流式节点输出的能力(1.1.2.0 的 AgentToolNode 异步执行支持),但 AgentTool 的同步语义和 Graph 节点的异步执行是两层不同的抽象。如果你用的是 AgentTool.create() 把子 Agent 挂到 Supervisor 上,你拿到的是同步调用;如果你需要流式输出,要么在 Graph 层把子 Agent 编排为独立节点并配置异步执行,要么用 Sinks.Many 等机制在应用层做流式桥接。这不是一个“改一个配置”就能解决的问题,它决定了你选择 AgentTool 还是 Graph 节点编排作为多 Agent 的承载方式。

六、代价四:Graph 解决了状态管理,但没有解决运行时治理

OverAllState 是 SAA Graph 的核心抽象:一个可序列化的中央状态对象,承载节点之间的数据流转,支持基于 key 的合并策略,用于检查点、恢复和断点续执行。持久化执行通过 checkpointer 实现:每个执行步骤的状态保存到存储,工作流可以在任何时候暂停和恢复,即使中断一周后也能从最后记录的节点恢复。

这是 Graph Core 相对于 ChatClient Advisor 链的核心增量:Advisor 链管的是“一次调用内的顺序”,Graph 管的是“跨节点的有状态流转”。当一个多 Agent 任务需要运行十分钟、中间可能调用十几个工具、失败后需要从中间步骤恢复时,OverAllState 加 checkpointer 提供了 Advisor 链不具备的能力。

但状态管理不等于运行时治理。Graph Core 不提供以下能力,它们需要在 Graph 之上自己补:

  • 多租户隔离:OverAllState 本身没有租户维度的分区。如果多个租户共享同一个 Graph 实例,状态 key 的命名冲突和跨租户数据泄露是应用层必须自己解决的问题。
  • 成本归因:Graph 的 checkpointer 保存的是状态,不是 Token 用量。每一次 Supervisor 路由、每一次子 Agent 调用消耗了多少 Token,需要额外接入 Micrometer 或 OTel 指标,并附加租户和 Agent 维度的标签。
  • 评测轨迹:OverAllState 的最终快照不等于执行轨迹。路由经过了哪些节点、每个节点的输入输出是什么、是否发生了重试,需要 Graph 的 Listener 机制或自定义节点埋点来捕获。而且 Listener 的异常不会中断图执行,生产环境需要给每个 Listener 方法包 try-catch 做降级。
  • PII 掩码与审计:状态中可能包含工具返回的原始数据,日志和快照的导出层需要独立的脱敏逻辑,不能依赖“不要在节点里记录敏感信息”的纪律。

七、什么时候该用 AgentTool,什么时候该用 Graph 节点

把上面的代价收束成一个可操作的判断:

用 AgentTool(Supervisor + ReactAgent 作为工具) 适合:子 Agent 数量有限(2—4 个)、路由类别边界清晰、不需要流式中间输出、任务可以在有限的迭代次数内收敛。它的优点是代码量极小,ReactAgent.builder().tools(AgentTool.create(expert)) 几行就能跑起来。

用 Graph 节点编排适合:需要断点恢复、需要并行分支和聚合策略(AllOf/AnyOf)、需要将子 Agent 的执行作为图上的独立节点来观测和拦截、或者需要流式输出每一步的结果。代价是代码量显著增加,StateGraph、OverAllState、条件边、检查点器的配置都需要显式声明。

一个常见的混合策略是:用 Graph 作为外层骨架管理状态和持久化,把 ReactAgent 作为节点嵌入图中;在 ReactAgent 内部,如果需要子 Agent 协作,用 AgentTool 做局部的监督者。这样既获得了 Graph 的状态管理能力,又保留了 AgentTool 的简洁性。但需要清醒地认识到:Graph 层的 checkpointer 保存的是图的节点状态,AgentTool 内部的工具调用循环是否被完整捕获,取决于 ReactAgent 节点的实现细节——不要把“图能恢复”等同于“子 Agent 的每一步都能恢复”。

八、收束:SAA 的定位是“装配线”,不是“治理层”

这一篇从监督者模式的最小实现出发,拆解了 SAA Graph 在多 Agent 协作中的四个工程代价:上下文膨胀的乘法效应、路由准确率随 Agent 数量的衰减、AgentTool 同步阻塞与流式输出的冲突、以及 Graph 状态管理不覆盖运行时治理。

SAA 的三层结构——Spring AI 接入层、Graph Core 状态管理层、Agent Framework 协作层——把 Agent 装配线分了层。分层带来的不是功能,而是成本:当你只需要单轮问答时,这套分层是纯粹的开销;只有当你需要长运行状态、可中断、多 Agent 协作时,它才开始回本。

真正需要记住的判断是:Graph 给你的是“能不能把多 Agent 跑起来、跑断了能不能接上”,你要自己补的是“跑起来之后能不能交代清楚” 。Token 花在了哪个 Supervisor 路由上、哪个子 Agent 的路由决策错了、这次调用属于哪个租户、PII 有没有进入日志——这些问题的答案不在 OverAllState 里,在你围绕 Graph 构建的可观测性和治理层里。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。