Spring AI Alibaba Graph实战:从ReAct Agent到Workflow,企业AI复杂流程该如何编排?

目录

前言

[1. 为什么企业 Agent 不能只有 ReAct?](#1. 为什么企业 Agent 不能只有 ReAct?)

[2. Graph到底是什么?](#2. Graph到底是什么?)

[3. 第一步不是写API,而是拆业务流程](#3. 第一步不是写API,而是拆业务流程)

[4. State:整个Graph共享的业务上下文](#4. State:整个Graph共享的业务上下文)

[4.1 ReplaceStrategy](#4.1 ReplaceStrategy)

[4.2 AppendStrategy](#4.2 AppendStrategy)

[5. 一个很重要的原则:State不要存Prompt](#5. 一个很重要的原则:State不要存Prompt)

[5.1 Prompt可以独立迭代](#5.1 Prompt可以独立迭代)

[5.2 不同Node可以使用同一份数据](#5.2 不同Node可以使用同一份数据)

[5.3 更容易调试](#5.3 更容易调试)

[6. Node怎么设计?先完成一个工单分类节点](#6. Node怎么设计?先完成一个工单分类节点)

[7. Edge:让Graph真正形成工作流](#7. Edge:让Graph真正形成工作流)

[8. 为什么复杂任务应该交给Agent?](#8. 为什么复杂任务应该交给Agent?)

[9. Human-in-the-Loop:Graph为什么需要暂停和恢复?](#9. Human-in-the-Loop:Graph为什么需要暂停和恢复?)

[10. Checkpoint解决的,不只是"保存记忆"](#10. Checkpoint解决的,不只是“保存记忆”)

[11. 从Demo到生产,还需要解决哪些问题?](#11. 从Demo到生产,还需要解决哪些问题?)

[11.1 超时与重试](#11.1 超时与重试)

[11.2 幂等](#11.2 幂等)

[11.3 可观测性](#11.3 可观测性)

[12. 最后把完整Graph组装起来](#12. 最后把完整Graph组装起来)

[13. Graph为什么越来越像Agent Runtime?](#13. Graph为什么越来越像Agent Runtime?)

[14. Workflow、Agent、Graph到底怎么选?](#14. Workflow、Agent、Graph到底怎么选?)


前言

前面我们已经聊过 Tool CallingReAct Agent

Tool Calling 解决的是:

如何让大模型不只是"说",而是真正调用企业系统完成查询、计算和操作。

ReAct Agent 又向前走了一步:

不再由开发者提前规定每一步,而是让模型根据当前任务动态决定下一步调用什么工具、如何继续执行。

于是很容易产生一个问题:

既然 Agent 已经可以自己思考、自己调用工具,为什么企业 AI 还需要 Workflow?

原因其实并不复杂。

企业业务真正需要的,往往不是一个"完全自由发挥"的 Agent,而是一套:

关键流程确定、局部决策智能、执行状态可追踪、异常可以恢复、高风险操作可以人工介入的执行系统。

例如一个企业智能工单,完整流程可能是:

用户提交工单 → 问题分类 → 选择处理方式 → 知识库检索 / Agent 分析 → 生成处理方案 → 风险判断 → 自动执行 / 人工审核 → 完成工单

这里有些步骤应该固定执行,有些步骤适合交给 Agent,还有一些高风险步骤必须经过人工确认。

这其实就是典型的:

Workflow + Agent + Human-in-the-Loop。

而在 Spring AI Alibaba 中,Graph 提供的正是这类复杂 AI 工作流的建模与执行能力。

需要先说明一个容易混淆的概念:

Workflow 是业务流程,Graph 是对 Workflow / Agent 执行流程进行建模、编排和运行的一种机制。

所以:

Graph ≠ Workflow。

更准确地说:

我们使用 Graph 来构建和运行 Workflow。

本文参考当前 Spring AI Alibaba Graph 官方工作流编排思路,但不会简单复述官方客服邮件示例,而是通过一个更通用的企业智能工单场景,理解 State、Node、Edge、Checkpoint、Human-in-the-Loop,以及 Graph 为什么会逐渐承担 Agent Runtime 的一部分职责。

**适用说明:**本文代码和接口设计基于 2026 年 8 月 Spring AI Alibaba Graph 官方文档示例整理。Graph Framework 仍在持续演进,具体 API 请以当前官方版本为准。


1. 为什么企业 Agent 不能只有 ReAct?

先看一个典型的 ReAct Agent。

用户提出问题之后,Agent 会经历:

理解问题 → 思考下一步 → 选择 Tool → 执行 Tool → 观察结果 → 再次思考 → 最终回答

它最大的优势就是:

灵活。

例如用户问:

我的订单为什么还没有发货?

Agent 可能动态决定:

  1. 查询订单状态;
  2. 发现订单已经付款;
  3. 查询商品库存;
  4. 发现库存不足;
  5. 查询预计补货时间;
  6. 根据这些信息生成解释。

开发者并不需要提前把每一种情况都写成固定流程。

这种模式非常适合:

  • 原因分析;
  • 动态工具选择;
  • 多步骤信息收集;
  • 开放式任务;
  • 无法提前确定完整路径的复杂任务。

但如果用户说:

帮我修改这张订单的收货地址。

事情就完全不同了。

企业系统通常至少要经过:

身份校验 → 查询订单状态 → 判断是否允许修改 → 参数校验 → 用户确认 → 调用订单系统 → 更新结果 → 记录审计日志

其中:

  • 身份校验不能跳过;
  • 已经发货的订单不能修改;
  • 修改之前需要确认;
  • 最终操作必须留下审计记录。

这些属于确定性业务规则

如果全部交给 Agent 自己判断,就意味着:

原本由 Java 代码、业务规则和权限体系控制的执行边界,被交给了概率模型。

这显然不是理想的企业系统设计。

所以我更喜欢用一句话来区分:

Agent 解决"不知道下一步应该做什么",Workflow 解决"这几步必须这么做"。

真正的企业 AI 更常见的形态不是二选一,而是:

Workflow 控制主流程,Agent 负责局部不确定任务。

能力 更适合 Workflow 更适合 Agent
身份与权限校验
固定业务规则
状态流转
风险审核
意图理解
动态 Tool 选择
多步骤原因分析
开放式任务规划

2. Graph到底是什么?

Spring AI Alibaba Graph 最核心的几个概念,其实并不复杂。

主要可以先记住五个:

Graph概念 作用 Java开发类比
State 保存整个流程共享的数据 Context
Node 完成一个具体任务 Service 方法
Edge 决定节点之间如何流转 if / switch / 路由
StateGraph 定义整个执行图 Workflow Definition
CompiledGraph 编译后真正可以执行的 Graph Workflow Runtime

最简单的一句话就是:

Node 负责执行任务,Edge 负责决定下一步去哪,State 负责在整个过程中传递数据。

例如一个最简单的 Graph 可以是:

START → classify → search → generate → END

但 Graph 真正有价值的地方,并不是把几个 Java 方法连接起来。

如果只是:

A 方法调用 B 方法,B 方法调用 C 方法

普通 Java Service 已经完全够用了。

Graph 真正解决的是:

如何把一次复杂 AI 任务,从"一次模型调用"变成一个有状态、可路由、可暂停、可恢复的执行过程。

这也是后面理解 Checkpoint、Human-in-the-Loop 和 Runtime 的基础。


3. 第一步不是写API,而是拆业务流程

Spring AI Alibaba 官方 Graph Quick Start 有一个思路,我非常认同:

先从需要自动化的流程开始,而不是先考虑 Graph API 怎么写。

企业 AI 开发尤其应该如此。

我们以一个企业智能工单为例。

系统需要支持:

  • 产品使用问题;
  • 系统故障;
  • 账号问题;
  • 订单问题;
  • 复杂业务咨询。

完整业务过程可以先定义成:

读取工单 → 问题分类 → 选择处理方式 → 查询知识库 / Agent分析 → 生成方案 → 风险判断 → 自动处理 / 人工审核 → 完成工单

接下来再把它拆成 Node:

Node 职责
classify_ticket 工单分类
search_knowledge 查询知识库
complex_agent 复杂问题分析
generate_solution 生成处理方案
risk_check 风险判断
human_review 人工审核
execute_action 执行业务操作

到这里,其实 Graph 的主要骨架已经出现了。

这也是 Graph 开发和普通"写 Prompt"的一个明显区别:

我们首先设计的是任务执行过程,而不是 Prompt。


4. State:整个Graph共享的业务上下文

Graph 中的 Node 不会孤立执行。

一个工单从开始处理到最终结束,会持续产生各种数据,例如:

  • ticket_id
  • ticket_content
  • ticket_type
  • knowledge_results
  • agent_result
  • solution
  • risk_level
  • approved
  • status

这些需要跨 Node 传递的数据,就应该进入 State。

Spring AI Alibaba Graph 可以通过 Key Strategy 定义不同状态字段的更新策略:

java 复制代码
public static KeyStrategyFactory createStateStrategy() {

    return () -> {
        Map<String, KeyStrategy> strategies = new HashMap<>();

        strategies.put("ticket_id", new ReplaceStrategy());
        strategies.put("ticket_content", new ReplaceStrategy());
        strategies.put("ticket_type", new ReplaceStrategy());

        strategies.put("knowledge_results", new ReplaceStrategy());
        strategies.put("agent_result", new ReplaceStrategy());

        strategies.put("solution", new ReplaceStrategy());
        strategies.put("risk_level", new ReplaceStrategy());

        strategies.put("approved", new ReplaceStrategy());
        strategies.put("status", new ReplaceStrategy());

        strategies.put("messages", new AppendStrategy());

        return strategies;
    };
}

这里比较常见的是两种策略。

4.1 ReplaceStrategy

新的值覆盖旧值。

例如:

  • ticket_type
  • risk_level
  • status

这些字段通常只需要保留当前状态。

4.2 AppendStrategy

新的数据追加到已有数据之后。

例如:

  • messages
  • 多轮执行记录;
  • 某些需要保存历史的数据。

这也是为什么 State 不能简单理解成:

一个 Map。

真正重要的是:

State 是整个 Graph 执行过程中的共享业务上下文。


5. 一个很重要的原则:State不要存Prompt

官方 Graph 文档中特别强调了一点:

State 应该保存原始数据,而不是已经拼装好的 Prompt 文本。

我认为这一点在真实项目里非常重要。

例如知识库查询结果,更合理的是:

java 复制代码
knowledge_results = List<Document>

而不是把它提前变成:

java 复制代码
String prompt = """
        以下是知识库查询结果:
        文档1:...
        文档2:...

        请根据这些内容回答用户问题。
        """;

为什么?

因为:

State 是业务数据,Prompt 是某个具体 Node 使用这些业务数据的方式。

正确关系应该是:

State 原始数据 → Node读取数据 → Prompt Template组织上下文 → LLM执行

这样做至少有三个明显好处。

5.1 Prompt可以独立迭代

例如:

Prompt V1 → V2 → V3

不需要修改整个 Graph 的状态结构。

5.2 不同Node可以使用同一份数据

同样的知识库结果:

  • 生成节点可以用于回答;
  • 风险节点可以用于判断依据是否充分;
  • 审核节点可以展示给人工审核人员。

5.3 更容易调试

如果 State 里保存的是原始数据,就可以明确知道:

  • RAG 实际召回了什么;
  • Agent 得到了什么;
  • Node 输入是什么;
  • Prompt 又做了什么转换。

这其实仍然是传统软件工程中很重要的一条原则:

业务数据和表现形式分离。

到了 Agent 开发里,这个原则并没有失效。


6. Node怎么设计?先完成一个工单分类节点

Node 本质上就是:

读取当前 State → 执行任务 → 返回需要更新的 State。

例如我们实现一个工单分类节点:

java 复制代码
public class ClassifyTicketNode implements NodeAction {

    private final ChatClient chatClient;

    public ClassifyTicketNode(ChatModel chatModel) {
        this.chatClient = ChatClient.builder(chatModel).build();
    }

    @Override
    public Map<String, Object> apply(OverAllState state) {

        String ticketContent = state
                .value("ticket_content")
                .map(Object::toString)
                .orElse("");

        String result = chatClient.prompt()
                .system("""
                        你是企业工单分类助手。

                        请将当前工单分类为:
                        faq:普通知识问题
                        business:普通业务问题
                        complex:复杂分析问题

                        只返回分类结果。
                        """)
                .user(ticketContent)
                .call()
                .content();

        return Map.of(
                "ticket_type", result
        );
    }
}

这个 Node 的职责非常清晰:

  1. 从 State 获取 ticket_content
  2. 调用模型完成分类;
  3. 返回 ticket_type
  4. Graph 将新的数据合并回 State。

Node 本身并不关心:

分类完成以后应该执行哪个节点。

路由应该交给 Edge。

Node应该拆多细?

这里有一个很实用的原则:

一个 Node 最好只负责一个可以独立描述、独立观察、独立失败的业务步骤。

例如:

  • classify_ticket 只负责分类;
  • search_knowledge 只负责检索;
  • risk_check 只负责风险判断。

不要直接写成:

复制代码
handleTicket();

然后在一个方法里同时完成:

分类、检索、生成、风险判断、执行。

这样即使使用了 Graph,本质上仍然只是把一个大方法包装成 Node。

合理拆分以后,如果最终结果异常,我们可以明确看到:

  • 分类节点:SUCCESS
  • 知识库节点:SUCCESS
  • Agent节点:SUCCESS
  • 风险判断节点:FAILED

同时也更方便:

  • 节点级测试;
  • 节点级重试;
  • 节点级监控;
  • 中间状态检查;
  • Checkpoint;
  • 失败恢复。

这也是官方文档为什么强调 Node 粒度的重要原因。


7. Edge:让Graph真正形成工作流

如果所有 Node 永远都是:

A → B → C → D

那么普通 Java 调用链同样可以完成。

Graph 真正开始体现价值,是因为它可以根据 State 进行:

条件路由。

例如工单分类后:

  • faq → 查询知识库;
  • business → 普通业务处理;
  • complex → 交给 Agent。

可以定义:

java 复制代码
workflow.addConditionalEdges(
        "classify_ticket",

        edge_async(state -> state
                .value("ticket_type")
                .map(Object::toString)
                .orElse("complex")),

        Map.of(
                "faq", "search_knowledge",
                "business", "business_handler",
                "complex", "complex_agent"
        )
);

这样整个执行流程就会根据 State 动态选择路径。

例如用户问:

怎么修改账号绑定手机号?

可能执行:

classify_ticket → search_knowledge → generate_solution

而用户问:

昨天开始偶发订单创建失败,有些用户又可以正常下单,帮我分析原因。

可能执行:

classify_ticket → complex_agent → generate_solution

同一套 Graph,根据不同输入形成不同执行路径。

这就是 Graph 和简单 Pipeline 的一个明显区别。


8. 为什么复杂任务应该交给Agent?

假设用户提出:

昨天开始订单服务偶发失败,但是部分用户仍然可以正常创建订单,帮我分析可能是什么原因。

这类问题很难提前规定:

第一步必须查什么,第二步必须查什么。

Agent 可能需要:

  • 查询订单服务状态;
  • 查询异常日志;
  • 查看数据库连接情况;
  • 查询最近发布时间;
  • 分析异常时间分布;
  • 再次查询指定实例日志。

而另一个问题的分析过程可能完全不同。

如果继续用 Workflow 穷举所有路径,最终很容易变成:

大量 if / else if / switch 和复杂分支。

这种局部高度不确定的任务,更适合交给 ReAct Agent。

因此更合理的结构是:

Graph控制主流程 → 识别复杂任务 → ReAct Agent动态执行 → 返回分析结果 → Graph继续后续流程

这时候 Agent 只是 Graph 中负责"不确定任务"的一个执行单元。

整体关系可以理解为:

Workflow 中嵌 Agent,而不是让 Agent 接管整个 Workflow。

例如 Agent 可以负责:

  • 动态 Tool 选择;
  • 多步信息收集;
  • 原因分析;
  • 开放式决策。

而 Graph 继续负责:

  • 主流程;
  • 状态流转;
  • 风险控制;
  • 人工审核;
  • 后续确定性业务动作。

9. Human-in-the-Loop:Graph为什么需要暂停和恢复?

假设 Agent 最终给出的建议是:

重新执行订单扣款。

接下来能不能直接执行?

在真实企业系统里,很多情况下不应该。

例如:

  • 发起退款;
  • 修改订单;
  • 删除资源;
  • 修改权限;
  • 数据写操作;
  • 发送重要通知。

这些操作应该根据风险等级决定:

自动执行,还是人工确认后再执行。

因此可以设计:

generate_solution → risk_check → LOW自动执行 / HIGH人工审核

但这里有一个很重要的问题。

很多 Demo 所谓的人工审核只是:

java 复制代码
if (highRisk) {
    return "等待审核";
}

这其实只是把业务状态改成了:

等待审核。

真正的 Human-in-the-Loop 还需要回答:

  • JVM 重启以后怎么办?
  • 人工两小时以后才审核怎么办?
  • Graph 如何知道之前执行到了哪里?
  • 已经完成的节点是否重新执行?
  • 审核通过以后如何从原位置继续?

这就需要:

Checkpoint + Interrupt + Resume。

Spring AI Alibaba Graph 可以通过 interruptBefore 在指定 Node 之前暂停:

java 复制代码
MemorySaver saver = new MemorySaver();

CompileConfig compileConfig = CompileConfig.builder()
        .saverConfig(
                SaverConfig.builder()
                        .register(saver)
                        .build()
        )
        .interruptBefore("human_review")
        .build();

CompiledGraph graph = workflow.compile(compileConfig);

当 Graph 执行到 human_review 之前时,会暂停当前执行。

同时通过 Checkpointer 保存执行状态。

每次 Graph 执行还可以使用一个 threadId

java 复制代码
RunnableConfig config = RunnableConfig.builder()
        .threadId("ticket-202608260001")
        .build();

可以把 threadId 理解成:

一次 Graph 执行实例的标识。

例如:

  • 工单 10001;
  • 工单 10002;
  • 工单 10003;

它们虽然运行的是同一个 Graph Definition,但每一个实例都有自己的:

  • State;
  • Checkpoint;
  • 执行位置;
  • 业务数据。

第一次执行:

java 复制代码
Map<String, Object> initialState = Map.of(
        "ticket_id", "ticket-10001",
        "ticket_content", "订单扣款异常,请帮我处理"
);

graph.stream(initialState, config)
        .doOnNext(output -> log.info("节点输出: {}", output))
        .blockLast();

当流程执行到人工审核前,会自动中断。

此时可以读取当前状态:

java 复制代码
var currentState = graph.getState(config);

Map<String, Object> stateData =
        currentState.state().data();

人工审核通过以后更新 State:

java 复制代码
var updatedConfig = graph.updateState(
        config,
        Map.of(
                "approved", true
        ),
        null
);

然后继续执行:

java 复制代码
graph.stream(null, updatedConfig)
        .doOnNext(output -> log.info("节点输出: {}", output))
        .blockLast();

这里最关键的一点是:

不是重新从 START 执行一次。

而是:

保存执行现场 → 暂停 → 等待人工输入 → 更新 State → 从原执行流程继续。

这才是真正意义上的 Human-in-the-Loop。


10. Checkpoint解决的,不只是"保存记忆"

上面的例子使用的是:

复制代码
MemorySaver

它非常适合:

  • 本地开发;
  • Demo;
  • 单元测试;
  • 快速验证。

但生产环境显然不能只依赖 JVM 内存。

否则服务一旦重启:

执行状态也会跟着消失。

因此生产环境中的 Checkpoint 更重要的价值,是:

保存 Workflow 的执行现场。

例如一个长流程:

开始任务 → Node A → Checkpoint → Node B → Checkpoint → 等待人工审核 → 服务重启 → 加载Checkpoint → 继续后续Node

这样才真正具备长任务执行能力。

所以:

Checkpoint ≠ 普通聊天记忆。

它解决的是:

这个 Workflow 已经执行到了哪里,现在状态是什么,恢复以后应该从哪里继续。

从工程角度看,这已经开始非常接近传统 Workflow Engine 的 Runtime 能力了。


11. 从Demo到生产,还需要解决哪些问题?

用了 Graph,并不意味着系统自动变成生产级 Agent。

Graph 解决了任务编排和运行机制,但真正上线以后,还需要继续补齐:

11.1 超时与重试

不同 Node 应该根据调用对象设置不同策略。

例如:

节点类型 示例超时思路
普通内部Tool 秒级
RAG检索 秒级
外部HTTP Tool 根据SLA设置
LLM Node 根据模型和任务单独设置

具体参数不应该简单复制统一值,而要根据:

  • 服务 SLA;
  • 模型延迟;
  • 用户体验;
  • 下游接口特性;

进行配置和压测。

重试也不是所有异常都适合。

例如:

适合有限重试:

  • 网络瞬时异常;
  • 429;
  • 临时服务不可用。

通常不应该重试:

  • 参数错误;
  • 权限不足;
  • 业务规则拒绝;
  • 明确的数据不存在。

官方 Graph 文档也将错误区分成不同类型:

错误类型 处理方式
瞬时错误 系统重试
LLM可恢复错误 将错误写入State,让Agent重新规划
用户可修复错误 暂停流程等待输入
未知错误 向上抛出,进入异常治理

这非常值得在生产环境里继续扩展。

例如 Tool 调用发现订单号不存在,可以把错误写入 State:

java 复制代码
return Map.of(
        "tool_error", "ORDER_NOT_FOUND",
        "next_node", "complex_agent"
);

Agent 再根据:

ORDER_NOT_FOUND

决定要求用户补充正确订单号,而不是整个系统统一返回:

java 复制代码
catch (Exception e) {
    return "系统繁忙";
}

也就是说:

错误本身,也可以成为 Workflow 的一部分。

11.2 幂等

假设一个流程:

生成退款方案 → 人工审核 → 执行退款 → 保存Checkpoint

出现一种异常情况:

退款已经成功,但保存 Checkpoint 失败。

系统恢复以后,如果再次执行退款节点,就可能造成重复退款。

因此所有具有副作用的 Node / Tool,都应该考虑幂等。

可以使用类似:

workflowInstanceId + nodeId + businessId

生成 idempotencyKey

业务系统执行前先检查:

当前操作是否已经成功完成?

如果已经成功,就直接返回已有结果,而不是重复执行。

所以:

Checkpoint 解决"执行到了哪里",幂等解决"已经做过的事情不能重复做"。

两者不能互相替代。

11.3 可观测性

一个完整 Graph 在线上运行以后,仅仅知道:

请求耗时 23 秒。

远远不够。

更应该能够看到:

Node 耗时 状态 关键数据
classify_ticket 230ms SUCCESS faq
search_knowledge 420ms SUCCESS TopK结果
complex_agent 8.3s SUCCESS Token / Tool
risk_check 180ms SUCCESS HIGH
human_review WAITING PAUSED 等待人工
execute_action 620ms SUCCESS 执行结果

进一步还可以记录:

  • Graph Instance ID;
  • 节点输入输出;
  • State变化;
  • 模型信息;
  • Token;
  • Tool调用;
  • 异常;
  • 重试次数;
  • 实际执行路径;
  • Human Review;
  • Audit Log。

否则 Agent 越复杂,线上越接近黑盒。

Graph 一个很重要的工程价值,就是:

把原本隐藏在"大Agent"中的执行过程拆开。

拆开以后,才有机会真正做到:

可观察、可调试、可恢复、可评测。


12. 最后把完整Graph组装起来

现在整个企业工单流程已经非常清楚:

START → classify_ticket → 条件路由

其中:

  • faq → search_knowledge
  • complex → complex_agent

然后两条路径重新汇合:

generate_solution → risk_check

再根据风险等级:

  • LOW → execute_action
  • HIGH → human_reviewexecute_action

最终:

execute_action → END

核心 Graph 可以简化成:

java 复制代码
StateGraph workflow = new StateGraph(createStateStrategy())

        .addNode(
                "classify_ticket",
                node_async(new ClassifyTicketNode(chatModel))
        )

        .addNode(
                "search_knowledge",
                node_async(new SearchKnowledgeNode())
        )

        .addNode(
                "generate_solution",
                node_async(new GenerateSolutionNode(chatModel))
        )

        .addNode(
                "risk_check",
                node_async(new RiskCheckNode())
        )

        .addNode(
                "human_review",
                node_async(new HumanReviewNode())
        )

        .addNode(
                "execute_action",
                node_async(new ExecuteActionNode())
        );

再定义固定 Edge:

java 复制代码
workflow.addEdge(
        StateGraph.START,
        "classify_ticket"
);

workflow.addEdge(
        "search_knowledge",
        "generate_solution"
);

workflow.addEdge(
        "complex_agent",
        "generate_solution"
);

workflow.addEdge(
        "generate_solution",
        "risk_check"
);

workflow.addEdge(
        "human_review",
        "execute_action"
);

workflow.addEdge(
        "execute_action",
        StateGraph.END
);

工单分类后进行条件路由:

java 复制代码
workflow.addConditionalEdges(
        "classify_ticket",

        edge_async(state -> state
                .value("ticket_type")
                .map(Object::toString)
                .orElse("complex")),

        Map.of(
                "faq", "search_knowledge",
                "complex", "complex_agent"
        )
);

风险判断后再次路由:

java 复制代码
workflow.addConditionalEdges(
        "risk_check",

        edge_async(state -> state
                .value("risk_level")
                .map(Object::toString)
                .orElse("HIGH")),

        Map.of(
                "LOW", "execute_action",
                "HIGH", "human_review"
        )
);

最后配置 Checkpoint 和人工中断:

java 复制代码
MemorySaver saver = new MemorySaver();

CompileConfig compileConfig = CompileConfig.builder()
        .saverConfig(
                SaverConfig.builder()
                        .register(saver)
                        .build()
        )
        .interruptBefore("human_review")
        .build();

CompiledGraph graph = workflow.compile(compileConfig);

到这里,一套基本的:

Workflow + Agent + Human-in-the-Loop

企业 AI 执行框架就已经形成了。

需要注意,上面重点展示的是 Graph 的核心编排方式,知识库查询、复杂 Agent、业务 Tool 等具体实现可以根据自己的项目进行替换。

真正开发时,建议至少分别验证三条路径:

场景1:普通知识问题

输入:

怎么修改账号绑定手机号?

预期路径:

START → classify_ticket → search_knowledge → generate_solution → risk_check → END

场景2:复杂分析问题

输入:

昨天开始订单服务偶发失败,有些用户又可以正常创建订单,请分析可能原因。

预期路径:

START → classify_ticket → complex_agent → generate_solution → risk_check → END

场景3:高风险操作

输入:

帮我重新执行这笔订单的扣款。

预期路径:

START → classify_ticket → complex_agent → generate_solution → risk_check → PAUSE → human_review → execute_action → END

实际项目中应该结合日志或者 Trace 验证:

Graph 真正走过的 Node 是否与预期执行路径一致。

这一步对于后面的 Agent Evaluation 同样非常重要。


13. Graph为什么越来越像Agent Runtime?

理解完前面的内容,再回头看 Graph,就会发现它已经不只是:

Node + Edge。

当它进一步拥有:

  • State;
  • Conditional Routing;
  • Checkpoint;
  • Interrupt;
  • Resume;
  • Streaming;
  • Agent Node;

之后,它开始承担另一层职责:

如何稳定地把一次 Agent 任务执行完整。

可以把三个角色简单区分成:

Agent:决定"想什么、下一步做什么"。

Tool:负责"真正执行什么"。

Graph Runtime:负责"整个任务怎么被控制和运行"。

例如:

  • 当前执行到了哪个 Node?
  • 下一步走哪条路径?
  • State 如何传递?
  • 失败以后怎么恢复?
  • 需要人工审核怎么办?
  • 服务重启怎么办?
  • 已经完成的 Node 是否需要重新执行?
  • Agent 如何嵌入固定业务流程?

这些问题都不是单纯依靠 Prompt 可以解决的。

也不是一个 ReAct Loop 就能完整解决的。

这里需要特别注意:

Graph 是 Runtime 的重要组成部分,但 Graph 本身并不等于完整的企业 Agent Runtime。

真正生产环境仍然需要把:

Graph + 稳定性治理 + 可观测性 + 安全 + Evaluation + Audit

组合起来。


14. Workflow、Agent、Graph到底怎么选?

最后再把三个概念统一一下。

确定性任务

例如:

  • 身份校验;
  • 权限校验;
  • 参数校验;
  • 固定业务规则;
  • 状态更新;
  • 审计记录。

优先使用:

普通 Java 代码 / Workflow。

不确定性任务

例如:

  • 复杂问题分析;
  • 动态 Tool 选择;
  • 原因推理;
  • 多步骤信息收集;
  • 开放式任务规划。

优先使用:

Agent。

高风险任务

例如:

  • 修改数据;
  • 退款;
  • 审批;
  • 删除;
  • 重要通知;
  • 权限变更。

优先考虑:

Workflow + Human-in-the-Loop。

Graph解决什么?

Graph 并不是和 Workflow、Agent 并列选择。

它更像一层编排与运行机制:

使用 State、Node、Edge 把 Workflow、Agent 和人工控制组织成一个完整的执行图。

因此最终可以形成:

AI Application → Graph Runtime → Workflow / Agent / HITL → Tool / MCP → Enterprise Systems

这也是为什么我认为,企业 Agent 最终不会只是:

LLM + 一堆Tools。

它会逐渐演进成:

Model + Agent + Workflow + State + Checkpoint + Human-in-the-Loop + Observability + Evaluation + Governance


写在最后

从 Tool Calling 到 ReAct Agent,再到 Graph,我越来越觉得:

企业 Agent 真正困难的地方,并不是:

怎么让大模型更聪明。

而是:

怎么把一个具有概率性和不确定性的模型,放进一个要求稳定、可靠、可控制、可审计的企业系统。

Tool Calling 解决的是:

模型怎么连接业务能力。

Agent 解决的是:

复杂任务怎么动态决策。

Graph 进一步解决的是:

如何把这些能力建模成一个有状态、可路由、可暂停、可恢复的执行过程。

这时候,我们讨论的就不再只是:

"怎么写一个 Agent?"

而开始进入另一个更接近生产环境的问题:

怎么运行和治理一套企业 Agent 系统?

下一篇我们继续往下走:

一个企业 Agent 开发完成以后,到底怎么证明它"真的能用"?

这就涉及另一个越来越重要的方向:

Agent Evaluation。


🧩 AI项目实战

我也在持续维护一个面向企业真实业务场景的 Java AI 开源项目:Spring AI Business Copilot

项目基于 Java + Spring AI 构建,包含知识库、数据分析(NL2SQL)、客服、报表、招聘等典型企业 AI 场景,并持续实践 RAG、Tool Calling、Workflow、Agent、Human-in-the-loop、Guardrails、调用审计与工程治理

如果你正在学习 Java AI 应用开发,或者想看看一个 AI 项目如何从 Demo 逐步走向完整业务系统,可以作为学习和项目实践参考。

👉 GitHub:qcodingdev/spring-ai-business-copilot

如果项目对你有帮助,欢迎 Star 支持。


👨‍💻 关于作者|QCoding

专注 AI应用开发与Java技术实践

持续分享 Spring AI、RAG、 Agentic、Agent Evaluation、企业AI架构、工程治理与AI转型实践

相关推荐
mldong1 小时前
一个 App,十三套后端:手机审批端 uni-jeeflow-app 开源了
java·架构
豪气的程序猿2 小时前
电商图片工作流怎么选?Lingko AI 对比折叠键盘主图与详情页
人工智能
tqs_123452 小时前
MySQL RR隔离级别死锁|Gap间隙锁、临键锁,订单并发范围查询死锁根因方案
java
逆境不可逃2 小时前
Pi Agent 学习笔记:多个工具怎样并行执行
java
小刘快学习2 小时前
把 AI 账单拆到部门:企业 AI 网关的精准分账思路
人工智能
win4r2 小时前
RSI真的临近了吗?AI开始改进AI,但“智能爆炸”还差关键一跳
aigc·openai·ai编程
米小虾2 小时前
你让监控模型读的思维链,可能是攻击者写好的剧本
人工智能
deepseek233 小时前
Iris 开源搜索智能体拆解:35B 与 397B 中文仅差 0.3 分,上下文管理胜过堆参数
人工智能·ai agent·开源模型
ai小陈3 小时前
CUDA Stream实战:让数据传输与GPU计算真正重叠
人工智能·深度学习·ai·pdf·云计算·gpu算力
residual_fan3 小时前
【学术论文】航空发动机故障诊断智能体:基于持续对比强化学习的动态优化方法
人工智能·算法·数据挖掘·数据分析