Spring AI Alibaba Graph 实战:用图编排复杂业务,替代硬编码,支持分支、并行与人工确认

Spring AI Alibaba Graph 实战:用图编排复杂业务,替代硬编码,支持分支、并行与人工确认

前面几篇我们分别做了单 Agent、Memory+Tools+RAG、多 Agent Supervisor。到了真实企业业务里,你会遇到一类新问题:

  • 请假要先 AI 校验、再人工确认信息、再算余额、再主管审批、再通知;
  • 客服邮件要读信、分类、检索知识、Bug 建单、起草、人工审核、发送;
  • 研报要并行查多个数据源、汇总、人工反馈、再写报告;
  • 发布流程要在"灰度/全量/回滚"之间按条件分支,关键步骤必须人工点确认。

如果把这些用 if/else、硬编码 Service 调用、线程池手动串起来,很快会变成"意大利面条":分支难加、并行难控、人工中断难恢复、出问题难复盘。Spring AI Alibaba Graph 的思路是把流程建成有向图:节点是步骤,边是流转,共享状态贯穿全程,模型可按状态做条件路由,人工节点可暂停恢复。

本文按"为什么用图 → 核心概念 → 顺序/分支/并行/人工确认四种编排 → 完整审批示例 → 生产注意"展开。


一、为什么复杂流程不用硬编码

硬编码多步 AI 业务通常有这些痛点:

1. 分支膨胀

"如果分类是 A 走检索,B 走建单,C 走人工,人工后又可能回到起草或结束"------用 switch能写,但加一个新分类就要改主流程,回归成本高。

2. 并行难做

同时查订单、查知识库、查风控,用线程池能跑,但要自己合并结果、处理异常、控制超时;Graph 可用并行节点/Fork-Join 统一编排。

3. 人工中断难恢复

"主管审批"如果放在普通 Service 里,通常只能同步等结果;要支持"先返回前端、主管后来在后台点通过并继续",就需要把流程状态落盘、按 checkpoint 恢复。Graph 的 Human-in-the-loop 正是解决这个问题。

4. 可观测性差

硬编码流程出问题时,你只知道抛了异常,不知道到底走到哪一步、状态里有什么。Graph 可导出 Mermaid/PlantUML,可打流程快照,每一步状态可查。

5. 模型自主 vs 业务确定性的平衡

纯 ReAct Agent 太自由,适合探索;企业审批、合规、工单要求"哪几步必须走、哪几步可模型决策"。Graph 把确定部分写死成边,把不确定部分放在 LLM 节点里,兼顾可控与智能。


二、Spring AI Alibaba Graph 核心概念

Graph 模块灵感接近 LangGraph 的 Java 实现,核心四件套:

  • StateGraph :图定义容器,添加节点、边,最后 compile()CompiledGraph
  • Node :原子步骤,可调模型、调工具、写 Java 逻辑、做人工节点;异步用 node_async,同步实现 NodeAction
  • Edge :节点间流转。普通边 addEdge固定走;条件边 addConditionalEdges按状态路由;
  • OverAllState :全局共享状态,KV 结构。各节点读输入、写结果;通过 KeyStrategy定义同名 key 的合并方式(替换/追加等)。

内置能力还包括:多 Agent(ReAct、Supervisor)、工作流节点、流式、HITL、记忆/持久化、流程快照、嵌套/并行分支、可视化导出。

一个最朴素的图:

less 复制代码
StateGraph graph = new StateGraph(keyStrategyFactory)
    .addNode("classify", node_async(classifyNode))
    .addNode("rag", node_async(ragNode))
    .addNode("ticket", node_async(ticketNode))
    .addNode("human_review", node_async(humanReviewNode))
    .addEdge(START, "classify")
    .addConditionalEdges("classify", edge_async(state ->
        (String) state.value("next").orElse("rag")),
        Map.of("rag", "rag", "ticket", "ticket", "human", "human_review"))
    .addEdge("rag", END)
    .addEdge("ticket", END)
    .addEdge("human_review", END);

START/END是内置常量。节点返回或写入 OverAllState,边再从状态里取路由字段。


三、四种典型编排

3.1 顺序编排:确定流水线

适合步骤固定、前一步输出是后一步输入的场景,比如"读邮件→分类→检索→起草→发送"。

官方客服邮件示例:

less 复制代码
var readEmail = node_async(new ReadEmailNode());
var classifyIntent = node_async(new ClassifyIntentNode());
var searchDocs = node_async(new SearchDocsNode(vectorStore));
var bugTracking = node_async(new BugTrackingNode());
var draft = node_async(new DraftResponseNode(chatClientBuilder));
var humanReview = node_async(new HumanReviewNode());
var sendReply = node_async(new SendReplyNode());

StateGraph workflow = new StateGraph(createKeyStrategyFactory())
    .addNode("read_email", readEmail)
    .addNode("classify_intent", classifyIntent)
    .addNode("search_documentation", searchDocs)
    .addNode("bug_tracking", bugTracking)
    .addNode("draft_response", draft)
    .addNode("human_review", humanReview)
    .addNode("send_reply", sendReply);

workflow.addEdge(START, "read_email");
workflow.addEdge("read_email", "classify_intent");
workflow.addEdge("send_reply", END);

workflow.addConditionalEdges("classify_intent",
    edge_async(state -> (String) state.value("next_node")
        .orElse("draft_response")),
    Map.of(
        "search_documentation", "search_documentation",
        "bug_tracking", "bug_tracking",
        "human_review", "human_review",
        "draft_response", "draft_response"));

workflow.addEdge("search_documentation", "draft_response");
workflow.addEdge("bug_tracking", "draft_response");
workflow.addEdge("draft_response", "human_review");
workflow.addEdge("human_review", "send_reply");

分类节点把路由结果写进 next_node,条件边按它分流;简单问题走检索起草,Bug 走建单,复杂走人工。

3.2 条件分支:模型决策 + 业务规则双控

条件边不一定只靠模型。可以:

  • 模型分类写状态字段,条件边路由;
  • 规则节点直接读参数路由(如金额>1万走人工,否则自动);
  • 两者结合:先模型抽实体,再规则判断。

示例:审批金额分支

vbnet 复制代码
workflow.addConditionalEdges("extract_info",
    edge_async(state -> {
        Double amount = (Double) state.value("amount").orElse(0d);
        String risk = (String) state.value("risk_level").orElse("low");
        if ("high".equals(risk)) return "human_legal";
        if (amount != null && amount > 10000) return "manager_approve";
        return "auto_approve";
    }),
    Map.of(
        "human_legal", "human_legal",
        "manager_approve", "manager_approve",
        "auto_approve", "auto_approve"));

这样既有模型抽数,又有确定规则,避免模型自己决定是否绕开合规。

3.3 并行编排:Fork-Join

Graph 支持并行节点,但官方当前实现有明确限制:

  • 采用 Fork-Join 模型:一个父节点分多个子节点,全部完成后汇合到下一节点;
  • 通常只放一个并行步骤,不要在并行分支里再嵌套复杂条件边;
  • 要真正并发,需要给并行节点配 Executor,否则可能顺序调度;
  • 通过 RunnableConfig.addParallelNodeExecutor配置线程池。

示例:同时做"营销文案、规格抽取、风控评分"

arduino 复制代码
KeyStrategyFactory factory = () -> {
    Map<String, KeyStrategy> map = new HashMap<>();
    map.put("query", new ReplaceStrategy());
    map.put("copy", new ReplaceStrategy());
    map.put("spec", new ReplaceStrategy());
    map.put("riskScore", new ReplaceStrategy());
    return map;
};

StateGraph g = new StateGraph(factory)
    .addNode("marketingCopy", node_async(new CopyNode(chatClient)))
    .addNode("specExtraction", node_async(new SpecNode(chatClient)))
    .addNode("riskScoring", node_async(new RiskNode()))
    .addNode("merge", node_async(new MergeNode()))
    .addEdge(START, "marketingCopy")
    .addEdge(START, "specExtraction")
    .addEdge(START, "riskScoring")
    .addEdge("marketingCopy", "merge")
    .addEdge("specExtraction", "merge")
    .addEdge("riskScoring", "merge")
    .addEdge("merge", END);

RunnableConfig rc = RunnableConfig.builder()
    .addParallelNodeExecutor("marketingCopy", ForkJoinPool.commonPool())
    .addParallelNodeExecutor("specExtraction", ForkJoinPool.commonPool())
    .addParallelNodeExecutor("riskScoring", ForkJoinPool.commonPool())
    .build();

CompiledGraph compiled = g.compile();
compiled.execute(initialState, rc);

注意:从 START同时连三个节点表示分叉,三个都连到 merge表示汇合。若只是"逻辑并行"但不配 Executor,执行模型可能退化为顺序,具体以所用版本运行时为准。

3.4 人工确认(Human-in-the-loop)

这是企业流程最核心的能力。两种落地方式:

方式 A:同步人工节点

流程跑到人工节点,节点内部阻塞等前端/接口传入结果,写回状态后继续。适合内部工具、低频审批。

官方 HITL 示例是"扩写→人工反馈→翻译/结束":

java 复制代码
KeyStrategyFactory factory = () -> {
    Map<String, KeyStrategy> m = new HashMap<>();
    m.put("query", new ReplaceStrategy());
    m.put("expandContent", new ReplaceStrategy());
    m.put("feedback", new ReplaceStrategy());
    m.put("humanNextNode", new ReplaceStrategy());
    m.put("translateLanguage", new ReplaceStrategy());
    m.put("translateContent", new ReplaceStrategy());
    return m;
};

StateGraph g = new StateGraph(factory)
    .addNode("expander", node_async(new ExpanderNode(chatClientBuilder)))
    .addNode("humanfeedback", node_async(new HumanFeedbackNode()))
    .addNode("translate", node_async(new TranslateNode(chatClientBuilder)))
    .addEdge(START, "expander")
    .addEdge("expander", "humanfeedback")
    .addConditionalEdges("humanfeedback",
        AsyncEdgeAction.edgeAsync(new HumanFeedbackDispatcher()),
        Map.of("translate", "translate", END, END))
    .addEdge("translate", END);

HumanFeedbackNode不直接调模型,而是把"待确认内容"返回给调用方;人工通过接口写 feedbackhumanNextNode后再次 execute/resume,图从人工节点继续。

方式 B:Checkpoint 断点恢复

长流程、跨天审批用这种:人工节点触发中断,状态落库;主管后来在后台点"通过/驳回",带着新状态恢复编译图。Spring AI Alibaba Graph 支持流程快照、记忆与持久存储,HITL 可通过人工确认节点修改状态并恢复执行。

审批流可这样拆:

less 复制代码
.addNode("ai_check", node_async(new LeaveInfoCheckNode(chatClient)))
.addNode("info_check", node_async(new LeaveInfoConfirmNode()))   // 人工确认信息
.addNode("calc_balance", node_async(new LeaveCalcBalanceNode()))
.addNode("manager_approve", node_async(new ManagerApproveNode())) // 人工主管审批
.addNode("notify", node_async(new LeaveNotifyNode()))
.addEdge(START, "ai_check")
.addEdge("ai_check", "info_check")
.addConditionalEdges("info_check", infoCheckDispatcher,
    Map.of("ok", "calc_balance", "edit", "ai_check", "cancel", END))
.addEdge("calc_balance", "manager_approve")
.addConditionalEdges("manager_approve", approveDispatcher,
    Map.of("approve", "notify", "reject", "info_check", "pending", END))
.addEdge("notify", END);

info_check是人工校信息,manager_approve是人工审批;若选 pending则中断落盘,后续 resume。


四、完整示例:请假+知识+RAG+工具混合审批

把前面系列能力串起来:RAG 查制度、Tools 查假期余额、Graph 做分支与人工。

4.1 状态策略

java 复制代码
KeyStrategyFactory factory = () -> {
    Map<String, KeyStrategy> m = new HashMap<>();
    m.put("query", new ReplaceStrategy());
    m.put("threadId", new ReplaceStrategy());
    m.put("leaveForm", new ReplaceStrategy());       // 结构化请假单
    m.put("policyContext", new ReplaceStrategy());   // RAG返回的制度片段
    m.put("balance", new ReplaceStrategy());          // 剩余假期
    m.put("infoConfirmed", new ReplaceStrategy());    // 人工确认信息 Y/N
    m.put("managerDecision", new ReplaceStrategy());  // 主管 approve/reject/pending
    m.put("next", new ReplaceStrategy());
    return m;
};

4.2 节点

  • policyRag:用 QuestionAnswerAdvisor/VectorStore 检索"年假规则",写 policyContext
  • extractForm:LLM 从用户自然语言抽姓名、起止日期、类型,写 leaveForm
  • infoConfirm:人工节点,返回确认结果;
  • calcBalance:调用假期系统工具,返回 balance
  • managerApprove:人工审批;
  • notify:调用通知工具。
less 复制代码
.addNode("policy_rag", node_async(new PolicyRagNode(chatClient, vectorStore)))
.addNode("extract_form", node_async(new ExtractFormNode(chatClient)))
.addNode("info_confirm", node_async(new InfoConfirmNode()))
.addNode("calc_balance", node_async(new CalcBalanceNode(leaveTools)))
.addNode("manager_approve", node_async(new ManagerApproveNode()))
.addNode("notify", node_async(new NotifyNode(notifyTools)))

4.3 边与分支

less 复制代码
.addEdge(START, "policy_rag")
.addEdge("policy_rag", "extract_form")
.addEdge("extract_form", "info_confirm")
.addConditionalEdges("info_confirm",
    edge_async(s -> (String) s.value("infoConfirmed").orElse("edit")),
    Map.of("ok", "calc_balance", "edit", "extract_form", "cancel", END))
.addEdge("calc_balance", "manager_approve")
.addConditionalEdges("manager_approve",
    edge_async(s -> (String) s.value("managerDecision").orElse("pending")),
    Map.of("approve", "notify", "reject", "extract_form", "pending", END))
.addEdge("notify", END);

这里既有 RAG 注入制度,又有工具算余额,又在两个关键点人工确认;比硬编码 if清晰很多,新增"HR 复核"只需加节点和一条条件边。

4.4 执行与恢复(人工)

首次发起:

ini 复制代码
OverAllState init = new OverAllState();
init.put("query", "我要请年假3天,下周一至周三");
init.put("threadId", "leave-1001");

CompiledGraph graph = stateGraph.compile();
CompletableFuture<OverAllState> f = graph.execute(init);
// 到 info_confirm 若设计为阻塞,就拿人工输入;若断点恢复,就持久化并返回前端

人工审批恢复(伪代码):

ini 复制代码
OverAllState saved = checkpointer.load("leave-1001");
saved.put("managerDecision", "approve");
graph.execute(saved); // 从 manager_approve 后续边继续到 notify

生产里 checkpointer可接数据库;Spring AI Alibaba 支持记忆与持久存储、流程快照。


五、Graph 与多 Agent Supervisor 的关系

前面写的 Supervisor 多 Agent,也可以用 Graph 显式编排。官方 Supervisor 示例:

java 复制代码
SupervisorNode supervisor = new SupervisorNode(chatModel, members);
ResearcherNode researcher = new ResearcherNode(chatModelWithTool);
CoderNode coder = new CoderNode(chatModelWithTool);

StateGraph workflow = new StateGraph(factory)
    .addNode("supervisor", node_async(supervisor))
    .addNode("researcher", node_async(researcher))
    .addNode("coder", node_async(coder))
    .addEdge(START, "supervisor")
    .addConditionalEdges("supervisor",
        edge_async(state -> (String) state.value("next").orElse("FINISH")),
        Map.of("FINISH", END, "researcher", "researcher", "coder", "coder"))
    .addEdge("researcher", "supervisor")
    .addEdge("coder", "supervisor");

return workflow.compile();

要点:

  • Supervisor 节点只写 next=researcher/coder/FINISH
  • worker 执行完回 supervisor,形成"思考→派活→观察→再派活"的图式循环;
  • 若 researcher 需要 RAG、coder 需要代码工具、reviewer 需要静态扫描,分别做成节点即可;
  • 比"把所有工具注册给一个 ChatClient"更可控,也不会把不相关工具塞进同一上下文。

DeepResearch 类复杂图还可加 coordinator、background_investigator、planner、human_feedback、research_team、reporter 等节点,条件边做多层路由。


六、可视化与调试

Graph 可导出图结构,方便和产品/运维对流程:

ini 复制代码
GraphRepresentation rep = stateGraph.getGraph(
    GraphRepresentation.Type.PLANTUML, "leave-approval");
log.info(rep.content());

// Mermaid 同理按所用 API 切换 Type

输出可用于 Confluence、研发文档;配合节点日志(进入节点、读取状态 key、写出 key、边路由结果),定位"为什么走了人工而不是自动通过"会很轻松。

建议每个节点统一打:

  • nodeNamethreadId
  • 读取的关键 state key;
  • LLM token/工具耗时;
  • 条件边最终路由目标。

七、生产落地建议

1. 哪些步骤用确定边,哪些用模型路由

合规、金额、权限、状态机必走确定边;意图分类、摘要、起草、抽取可让模型写路由字段。不要把"是否走人工审批"完全交给模型。

2. 人工节点设计成"可恢复"

前端只发"待办 ID + 决策 + 修改后表单",后端从 checkpointer 恢复;不要让人把全部上下文重新粘贴一遍。

3. 并行别滥用

Graph 并行当前更偏 Fork-Join、单并行段;多个互相依赖的 AI 步骤别硬并行,否则状态合并复杂、成本难控。 真要管线并行,可在节点内部用 CompletableFuture自己聚。

4. 状态 key 合并策略要显式

列表类(审核历史、检索片段)用追加;最终结论、表单用替换。避免多节点写同一 key 互相覆盖。

5. 限流与最大步数

图里若有回边(supervisor↔worker、人工 edit↔extract),必须设最大迭代/最大人工次数,防止"模型一直重新起草"或"用户一直改表单"导致死循环。Spring AI Alibaba Agent 层还提供模型调用限制等拦截器思路,可作补充。

6. 与前面系列能力组合

  • Memory:会话级用 ChatMemory,流程级用 OverAllState+checkpointer;
  • Tools:节点内调用 @Tool/ToolCallback,人工节点不调模型只改状态;
  • RAG:policy/产品知识节点走 VectorStore+Advisor;
  • 多 Agent:Supervisor/Researcher/Coder/Reviewer 各成节点,Graph 负责大流程。

八、总结

复杂业务不要用"一个 Controller 调多个 Service 再套 LLM"的硬编码方式。用 Spring AI Alibaba Graph 的落地公式:

scss 复制代码
StateGraph = 确定边(固定流程) + 条件边(分支/规则/模型路由) + 并行节点(Fork-Join) + 人工节点(HITL) + OverAllState(共享上下文)
  • 顺序流程用 addEdge
  • 意图/风险/金额分支用 addConditionalEdges
  • 多源查询用并行节点+Executor,注意 Fork-Join 限制;
  • 合规/审批用人工节点,同步确认用阻塞、长流程用 checkpoint 恢复;
  • 多专家用 Supervisor 回边,RAG/Tools/Memory 作为节点能力接入。
相关推荐
Lyy1 小时前
DevOps平台 — 第十一篇:工作项的设计与实现
后端·devops
衝鋒壹号1 小时前
鸿蒙 PC 能跑 Docker 吗?一次从安装失败到成功运行的实测记录
后端·harmonyos
用户8181870627461 小时前
第27章 消息丢失/重复消费的全链路排查(生产者→Broker→消费者)
java·后端
程序员cxuan1 小时前
ChatGPT 开启无限 token
人工智能·后端·程序员
Gopher_HBo2 小时前
beego ORM 源码(上):模型元数据与注册
后端
索隆zoro2 小时前
Army 对 jOOQ
java·后端
泡海椒2 小时前
规则热更新实现:JQuick-Java无需重启更新业务规则实战
后端
右耳朵猫AI2 小时前
PHP周刊2026W37 | Symfony 三维护版齐发、Laravel AI SDK 0.11、LSP 服务器上线
后端·php·laravel
geovindu2 小时前
CSharp:Condition Variable Pattern
后端·设计模式·c#·.net·.netcore·条件变量模式·同步型模式