LLM - 基于 Spring AI Alibaba Graph 重构多智能体订单助手:从单体 Agent 到图工作流的工程实践

LLM - 基于 Spring AI Alibaba Graph 重构多智能体订单助手:从单体 Agent 到图工作流的工程实践

文章目录

在这里插入图片描述

Pre

LLM - 从生成式到 Agentic (Java技术栈)

在这一版 Demo 中,多智能体订单助手的核心是在 Spring AI 上手动编排 Orchestrator + ProductAgent + OrderAgent + RefundAgent 的调用逻辑。
这一版,将用 Spring AI Alibaba Graph 把它重构成「图工作流 + 多智能体」形态,让复杂逻辑变成一张清晰的状态图,具备更好的可维护性与扩展性。


背景:为什么要上 Graph?

传统基于代码 if-else 的 Orchestrator 很快会遇到几个问题:路由逻辑越写越乱、Agent 越加越多、人工插入 Human-in-the-loop 很痛苦。
Spring AI Alibaba Graph 用 StateGraph + Node + Edge + OverAllState 四个核心概念,把复杂业务拆成多个清晰节点,通过有向图描述执行流程与多智能体协作。

  • StateGraph:整张「订单助手」流程图,描述有哪些节点、怎么跳转。
  • Node:一个节点就是一个「步骤」,例如意图识别、商品咨询 Agent、订单查询 Agent、退款 Agent 等。
  • Edge:节点间的连线,可以是固定跳转,也可以是根据上下文条件路由。
  • OverAllState:全局状态,贯穿整个流程,存放用户输入、上下文、中间结果等。

这样做的好处是:

  • 业务逻辑从 if-else 中抽离出来,以图结构可视化管理。
  • 支持多智能体协作、并行节点和条件边,后续要加新 Agent 只需加节点和边。
  • 原生支持 streaming、Human-in-the-loop、快照与恢复,适合企业级智能体系统。

项目结构:按真实工程拆分

示例采用 Maven 多模块结构,参考官方与社区最佳实践,将 AI 能力与业务工程解耦。

springai-order-assistant/ ├── ai-starter/ # 封装 Spring AI Alibaba&Graph 的通用配置 ├── ai-graph-order/ # 本文核心:订单助手 StateGraph+Nodes 实现 ├── ai-domain/ # 领域模型与仓储(Order、Product 等) ├── ai-web/ # REST Controller,对外的 HTTP API └── pom.xml # 父工程 
  • ai-starter:统一引入 Spring AI Alibaba 依赖、模型配置、日志与监控等。
  • ai-graph-order:只关心「订单助手这张图」,包含 StateGraph、节点实现与状态定义。
  • ai-domain:普通领域模块,负责订单、商品等数据访问,与 AI 解耦。
  • ai-web:Spring Boot Web 启动入口 + Controller,接收用户请求并触发图执行。

订单助手 Graph 设计:从多 Agent 视角出发

业务场景回顾

订单助手要解决三类需求:

  • 商品咨询:用户问「这款耳机支持降噪吗?」
  • 订单查询:用户说「查一下我上次买的耳机能不能退」
  • 退货退款:用户想执行「帮我退掉昨天那单」

对应之前的四个 Agent 角色:

  • OrchestratorAgent:理解意图并路由。
  • ProductAgent:商品问答 / 推荐。
  • OrderAgent:订单查询。
  • RefundAgent:退款判断与创建。

在 Graph 版本中,这四个角色将对应 4 个主要节点,外加 1 个入口节点与 1 个结束节点,构成一张简单而清晰的状态图。

Graph 拆分为节点

用 Spring AI Alibaba Graph 的术语,可以设计如下节点:

  • entry:入口节点,初始化全局状态(如 userId、原始输入)。
  • intent_router:意图识别节点,决定走 PRODUCT / ORDER / REFUND 分支。
  • product_agent:商品咨询 Agent 节点。
  • order_agent:订单查询 Agent 节点。
  • refund_agent:退款 Agent 节点。
  • end:结束节点,返回最终回复。

图上的边关系为:

  • entry → intent_router
  • intent_router → product_agent / order_agent / refund_agent / end(条件边)
  • product_agent / order_agent / refund_agent → end

在真实项目中,只要 OverAllState 设计合理,后面要加「人工校验节点」「日志审计节点」「风控节点」都可以通过新增 Node+Edge 实现。


实战:定义 OverAllState 与 Graph

定义 OverAllState

OverAllState 是整条工作流共享的状态,一般包括:

  • userId、sessionId
  • 用户原始输入与模型回复
  • 意图识别结果(route)
  • 各 Agent 的中间结果(订单列表、退款条件判断等)

示例代码(放在 ai-graph-order 模块):

@DatapublicclassOrderAssistantState{// 上下文字段privateString userId;privateString sessionId;// 本轮输入与最终输出privateString userInput;privateString finalReply;// 路由结果:PRODUCT / ORDER / REFUND / UNKNOWNprivateString route;// 中间结果:订单、退款判断等privateList<Order> orders;privateboolean refundable;}

在 Graph 执行过程中,所有节点对状态的修改都会写回 OverAllState,确保后续节点可以复用信息。

构建 StateGraph

使用 Spring AI Alibaba Graph 的 StateGraph 定义节点和边。

@ConfigurationpublicclassOrderAssistantGraphConfig{@BeanpublicStateGraph<OrderAssistantState>orderAssistantGraph(EntryNode entryNode,IntentRouterNode intentRouterNode,ProductAgentNode productAgentNode,OrderAgentNode orderAgentNode,RefundAgentNode refundAgentNode,OrderAssistantStateSerializer stateSerializer){StateGraph<OrderAssistantState> graph =newStateGraph<>("order-assistant", stateSerializer);// 1. 注册节点 graph.addNode("entry",node(entryNode)); graph.addNode("intent_router",node_async(intentRouterNode)); graph.addNode("product_agent",node_async(productAgentNode)); graph.addNode("order_agent",node_async(orderAgentNode)); graph.addNode("refund_agent",node_async(refundAgentNode));// 2. 设置起始节点 graph.setStart("entry");// 3. 固定边:entry -> intent_router graph.addEdge("entry","intent_router");// 4. 条件边:intent_router -> 具体 Agent 或结束 graph.addConditionalEdges("intent_router",edge_async(newIntentRouteDispatcher()),Map.of("PRODUCT","product_agent","ORDER","order_agent","REFUND","refund_agent","END",StateGraph.END // 直接结束));// 5. 各 Agent 节点到结束节点 graph.addEdge("product_agent",StateGraph.END); graph.addEdge("order_agent",StateGraph.END); graph.addEdge("refund_agent",StateGraph.END);return graph;}}

这里使用了同步 node 与异步 node_async,对于需要调用外部服务或 LLM 的 Agent 节点,推荐异步以提高吞吐。


节点实现:把 Agent 融入 Graph

1. 入口节点 EntryNode

负责从 HTTP 请求参数构建初始状态。

@ComponentpublicclassEntryNodeimplementsNode<OrderAssistantState>{@OverridepublicOrderAssistantStateapply(OrderAssistantState state){// 一般通过外层传入已有 state,这里可以做一些初始化// 例如:写入时间戳、初始化列表等return state;}}

实际项目里通常由外层 Controller 构造好 state,再交给 Graph 执行,EntryNode 可以留空或作为「预处理」节点。

2. 意图路由节点 IntentRouterNode

对应之前 Orchestrator 里那段「只返回 PRODUCT / ORDER / REFUND」的逻辑,这里做成一个独立 Node。

@ComponentpublicclassIntentRouterNodeimplementsNode<OrderAssistantState>{privatefinalChatModel chatModel;publicIntentRouterNode(ChatModel chatModel){this.chatModel = chatModel;}@OverridepublicOrderAssistantStateapply(OrderAssistantState state){String instruction =""" 你是一个意图识别Agent。根据用户输入, 只返回以下四种之一:PRODUCT / ORDER / REFUND / END。 - PRODUCT:商品咨询或推荐 - ORDER:订单查询 - REFUND:退货或退款 - END:闲聊、问候等非业务问题 用户输入:%s """.formatted(state.getUserInput());ChatResponse resp = chatModel.call(newPrompt(instruction));String route = resp.getResult().getOutput().getContent().trim(); state.setRoute(route);return state;}}

Graph 执行到这里后,不直接决定下一个节点,而是交给条件边上的 IntentRouteDispatcher 来根据 route 决定跳转目标。

3. 条件边分发器 IntentRouteDispatcher

publicclassIntentRouteDispatcherimplementsEdgeDispatcher<OrderAssistantState>{@OverridepublicStringdispatch(OrderAssistantState state){String route =Optional.ofNullable(state.getRoute()).orElse("END");returnswitch(route){case"PRODUCT"->"PRODUCT";case"ORDER"->"ORDER";case"REFUND"->"REFUND";default->"END";};}}

配合前面的 addConditionalEdges 使用,使得 intent_router 节点可以被复用于更多复杂路由,而不是在节点内部写死 if-else。

4. 商品 Agent 节点 ProductAgentNode

这里直接复用前文 ProductAgent 的核心逻辑,把它包装成一个 Node,注意从 state 中拿 userInput,并把结果写回 state.finalReply。

@ComponentpublicclassProductAgentNodeimplementsNode<OrderAssistantState>{privatefinalChatClient productChatClient;publicProductAgentNode(ChatModel chatModel,ChatMemory chatMemory,ProductTools productTools){this.productChatClient =ChatClient.builder(chatModel).defaultSystem("你是专业的商品顾问,擅长基于商品知识库回答用户问题。").defaultTools(ToolSpecifications.from(productTools)).defaultAdvisors(newMessageChatMemoryAdvisor(ChatMemoryAdvisorSpec.builder().withChatMemory(chatMemory).withConversationIdProvider( s -> s.getSessionId()).build())).build();}@OverridepublicOrderAssistantStateapply(OrderAssistantState state){String reply = productChatClient.prompt().user(state.getUserInput()).call().content(); state.setFinalReply(reply);return state;}}

同理,实现 OrderAgentNodeRefundAgentNode 时,只需要替换工具集与 System Prompt,同时可在 state 中填充订单列表与退款判断结果以供后续节点使用。


对外暴露:Controller 触发 Graph 执行

Graph 本身只是一个可以被调用的「状态机」,需要一个入口来接收 HTTP 请求、构造初始状态并执行 Graph。

ai-web 模块中定义 Controller:

@RestController@RequestMapping("/api/order-assistant")publicclassOrderAssistantController{privatefinalStateGraphExecutor<OrderAssistantState> executor;publicOrderAssistantController(@Qualifier("orderAssistantGraph")StateGraph<OrderAssistantState> graph,OrderAssistantStateSerializer serializer){this.executor =newStateGraphExecutor<>(graph, serializer);}@PostMapping("/chat")publicResponseEntity<ChatResponseDto>chat(@RequestBodyChatRequestDto request){OrderAssistantState initState =newOrderAssistantState(); initState.setUserId(request.userId()); initState.setSessionId(request.sessionId()); initState.setUserInput(request.message());OrderAssistantState endState = executor.execute(initState);returnResponseEntity.ok(newChatResponseDto(endState.getFinalReply()));}}
  • StateGraphExecutor 封装了 Graph 的执行流程与状态序列化 / 反序列化。
  • 每次调用都会从入口节点开始,根据节点逻辑与条件边推进,直到 END。
  • 最终通过 finalReply 字段返回字符串响应,前端可以直接展示。

工程落地经验:从 Demo 到生产

1. 按模块拆分职责

  • Graph 与节点只负责「流程与智能体」,不直接依赖 Web、数据库细节。
  • 领域模块(ai-domain)负责订单、商品、用户等实体与仓储。
  • ai-starter 中统一管理模型、工具与监控配置,避免在每个业务模块重复引入依赖。

这种分层有利于:

  • 后续增加新的 Graph(如客服助手、营销 Agent)时复用 Starter 与领域模块。
  • 把 Graph 部分抽象成「AI 能力中台」,供多个业务线共享。

2. Graph 与多智能体的演进路径

  • 第一阶段:像本文这样,将之前单工程 Orchestrator 重构为 Graph,保持 Agent 逻辑基本不变。
  • 第二阶段:引入并行节点(ParallelNode),例如同时查询多个业务系统或同时跑多个推荐策略。
  • 第三阶段:加 Human-in-the-loop 节点,让关键步骤(如退款审批)必须经过人工确认后再继续。
  • 第四阶段:配合 Spring AI Alibaba Admin 或自研控制台,对 Graph 的配置、版本与监控进行可视化管理。

3. 与治理 / 观测体系集成

  • 在 Graph 层统一注入 TraceId / SpanId,利用阿里云或自建链路追踪体系做全链路观测。
  • 为每个节点打点:包括 LLM 调用次数、平均成本、错误率,支持按节点与按 Graph 分析瓶颈。
  • 对敏感节点(如 RefundAgentNode)增加审计日志与强制 HITL,避免业务风险。

小结

通过 Spring AI Alibaba Graph,将原本在 Spring AI 中手写 Orchestrator 的多 Agent 订单助手,重构为「状态图 + 节点 + 条件边」的架构,使智能体编排和业务工作流融为一体。
对于已经在 Java / Spring 生态中的团队,这种方式可以在保持熟悉技术栈的前提下,快速获得多智能体、工作流编排、Human-in-the-loop、可观测与治理等一整套能力,为 2025 年之后更大规模的智能体落地打好工程基础。

扩展阅读

一、Spring AI Alibaba Graph(核心概念 & 官方实现)

这一组是Graph / Agent 编排的中枢知识源


二、Spring AI 多 Agent / 编排(Orchestrator & Workflow)

这组回答的是:Agent 如何分工?如何调度?如何协作?


三、Spring AI Alibaba 最佳实践 & 教程

工程落地 + 实战经验,非常适合你这种偏架构视角的人:


四、Graph / Agent 原理解析 & 深度解读(中文社区)

适合写**“为什么要 Graph?”、“和传统流程引擎有什么本质区别?”**这种文章:


五、阿里云 / 企业级案例 & 生态视角

企业实践、平台化思路


在这里插入图片描述

Read more

Microsoft Visual C++ Redistributable 运行库怎么安装?(详细教程)

Microsoft Visual C++ Redistributable 运行库怎么安装?(详细教程)

前言 很多人安装软件或游戏时会遇到这样的提示:“无法启动程序,计算机中丢失 MSVCP140.dll”或“VCRUNTIME140.dll 未找到”。 这类问题通常是由于系统缺少 Microsoft Visual C++ Redistributable 运行库导致的。 Microsoft Visual C++ Redistributable 是 Windows 系统中必不可少的运行组件,几乎所有基于 C++ 的程序都依赖它。若运行库缺失或版本不匹配,会导致软件无法启动。本文将从原理、安装与修复三个方面,介绍如何正确配置运行库,并推荐实用工具快速解决 DLL 缺失问题。 Microsoft Visual C++ Redistributable运行库修复工具【免费版】http://www.ijinshan.com/functions/repairdll.html?channel=1506 一、为什么电脑提示“

C++日志管理从基础到完善

C++日志管理从基础到完善

万古教员有名言,自信人生二百年。 个人主页:oioihoii 喜欢内容的话欢迎关注、点赞、收藏!感谢支持,祝大家祉猷并茂,顺遂无虞! 版本一:基础日志代码 在设计C++日志系统时,我们需要考虑以下几个关键点: 1. 易用性:日志系统应该易于使用,开发者应该能够轻松地添加日志条目。 2. 性能:日志系统应该尽可能地减少对应用程序性能的影响。这意味着日志记录的过程应该尽可能地快速,以减少对应用程序的延迟。 3. 灵活性:日志系统应该能够支持不同级别的日志(如错误、警告、信息、调试等),并能够在运行时动态地更改日志级别。 4. 可配置性:日志系统应该允许开发者配置日志的各种参数,如日志文件的位置、日志的格式等。 5. 线程安全:如果应用程序是多线程的,那么日志系统也必须是线程安全的。 下面是一个简单的C++日志系统的设计,它考虑了上述的所有因素: #include<iostream>#include<

【C++初阶】C++入门相关知识(1):C++历史 & 第一个C++程序 & 命名空间

【C++初阶】C++入门相关知识(1):C++历史 & 第一个C++程序 & 命名空间

🎈主页传送门:良木生香 🔥个人专栏:《C语言》 《数据结构-初阶》 《程序设计》 🌟人为善,福随未至,祸已远行;人为恶,祸虽未至,福已远离 前言:我们在此之前已经学习了C语言和数据结构,明白了C语言的基本概念,同时也学习了初阶的数据结构,现在,我们已经具备了学习初阶c++的能力了,那么,从今天开始,我们就正式进入到C++的学习中了,本专栏会记录下小编的学习C++的历程,有什么讲的不对的地方还请大佬们指出错误,那么,现在我们就正式进入到C++的学习吧 本专栏介绍:在我们之前已经学习过的C语言和数据结构的基础上,我们将会在本C++专栏上继续学习C++语法、STL、以及高阶数据结构 目录 一、C++历史介绍 1.1、起源与诞生(1979~1983) 1.2、核心 1.3发展与完善(

C/C++变量三兄弟:局部、静态局部、全局变量的区别+场景,一篇讲透

前言 写 C/C++ 时,很多人困惑:同样是变量,为啥有的只能在函数里用?有的能记住上一次的值?有的全文件都能访问?核心差异在于定义方式、作用域、存储期—— 这篇文章直击重点,让初学者快速掌握核心,避开踩坑! 一、先明确:三类变量的定义方式 这是区分三者的第一判断标准,直接决定变量的所有特性: 变量类型定义位置是否加static关键字局部变量函数/代码块内❌ 不加静态局部变量函数/代码块内✅ 加普通全局变量函数外(文件顶部)❌ 不加文件静态全局变量函数外(文件顶部)✅ 加 二、局部变量(auto变量):“临时短命”的自动变量 1. 核心特性 * 作用域:仅定义所在的函数/代码块内可见,离开作用域立即不可见 * 存储期:自动存储期(进入代码块时创建,离开时销毁) * 链接属性:无链接(仅当前作用域内的名字有效,其他区域无法引用) * 内存位置: