目录
[1. 为什么复杂业务任务需要Agent?](#1. 为什么复杂业务任务需要Agent?)
[2. ReAct Agent到底是怎么工作的?](#2. ReAct Agent到底是怎么工作的?)
[3. 实战:设计一个订单异常处理Agent](#3. 实战:设计一个订单异常处理Agent)
[3.1 定义业务请求和返回对象](#3.1 定义业务请求和返回对象)
[4. 使用Spring AI Alibaba实现ReAct Agent](#4. 使用Spring AI Alibaba实现ReAct Agent)
[4.1 引入Agent Framework](#4.1 引入Agent Framework)
[4.2 创建订单查询Tool](#4.2 创建订单查询Tool)
[4.3 创建ReAct Agent](#4.3 创建ReAct Agent)
[4.4 执行Agent](#4.4 执行Agent)
[5. 一次完整任务到底是如何执行的?](#5. 一次完整任务到底是如何执行的?)
[6. 企业Agent真正难的不是"会调用",而是"可控"](#6. 企业Agent真正难的不是“会调用”,而是“可控”)
[6.1 Agent必须有停止条件](#6.1 Agent必须有停止条件)
[6.2 Tool调用失败不能让Agent自己猜](#6.2 Tool调用失败不能让Agent自己猜)
[6.3 查询型Tool和操作型Tool必须分级](#6.3 查询型Tool和操作型Tool必须分级)
[6.4 Agent必须可观测](#6.4 Agent必须可观测)
[7. Agent和Workflow到底怎么选?](#7. Agent和Workflow到底怎么选?)
前言
如果只是让大模型回答问题,现在的实现已经非常成熟。
如果需要获取企业系统中的实时数据,也可以通过 Tool Calling,让模型调用订单、库存、物流、知识库等业务能力。
但真正开始做企业 AI 项目后,会遇到另一类需求:
用户给你的不是一个明确的操作,而是一个目标。
比如:
我的订单 A1001 三天没发货了,帮我看看是什么原因,如果是库存问题,就帮我创建一个客服工单。
这句话看起来简单,但真正执行起来并不是调用一个接口就能完成。
系统至少需要:
查询订单
↓
判断订单当前状态
↓
根据结果决定是否查询物流
↓
必要时继续查询库存
↓
判断异常原因
↓
决定是否创建工单
↓
汇总执行结果
其中最关键的一点是:
后面执行哪一步,并不能在任务开始前完全确定。
如果订单已经发货,下一步可能查询物流。
如果订单还没出库,下一步可能查询库存。
如果库存正常,又可能需要检查订单审核状态。
执行路径依赖上一步的结果动态变化。
这就是 Agent 开始发挥作用的地方。
这篇文章不准备只演示如何创建一个 ReactAgent,而是通过一个企业订单异常处理场景,完整看看:
- Tool Calling 和 Agent 到底差在哪里;
- ReAct Agent 如何完成多步骤任务;
- Spring AI Alibaba 如何实现 ReAct Agent;
- Tool 应该如何设计;
- 企业项目为什么不能让 Agent 无限自主执行;
- Agent 和 Workflow 到底应该怎么选。
1. 为什么复杂业务任务需要Agent?
先从最简单的 Tool Calling 开始。
用户问:
查询订单 A1001 当前是什么状态。
模型只需要:
识别用户意图
↓
选择 queryOrder
↓
生成 orderNo=A1001
↓
执行 Tool
↓
获得订单状态
↓
生成最终回答
这是一个非常典型的 Tool Calling 场景。
但是,如果用户的问题变成:
我的订单 A1001 一直没有发货,帮我查一下原因,如果库存异常就创建客服工单。
问题就发生了变化。
因为模型在任务开始之前,并不知道究竟需要调用几个 Tool。
可能是:
queryOrder
↓
queryInventory
↓
createTicket
也可能是:
queryOrder
↓
queryLogistics
甚至查询一次之后就已经能够回答用户。
这时候真正需要解决的问题已经不是:
调用哪个 Tool?
而是:
为了完成用户目标,下一步应该做什么?
我现在更倾向于这样理解两者的区别:
Tool Calling解决的是"模型如何使用一个外部能力"。
而:
Agent解决的是"模型如何围绕一个目标,持续决定下一步行动"。
Spring AI Alibaba 当前的 ReactAgent 本身就是按照这种循环方式执行:模型进行决策,需要外部能力时调用 Tool,读取 Tool 返回结果后继续执行,直到模型生成最终答案或者达到停止条件。

2. ReAct Agent到底是怎么工作的?
ReAct 来自两个单词:
Reasoning
+
Acting
它的核心思想并不复杂。
Agent 不要求模型第一次就生成完整答案,而是允许它在任务执行过程中不断经历:
判断当前状态
↓
选择下一步行动
↓
执行行动
↓
观察执行结果
↓
再次判断
直到任务完成。
为了避免把 ReAct 理解得过于抽象,我们直接套到订单场景里。
用户:
订单 A1001 为什么还没发货?如果是库存问题,就创建一个客服工单。
Agent 第一步需要获得订单信息:
Action
queryOrder(A1001)
Tool 返回:
订单状态:待发货
商品SKU:SKU-10086
现在 Agent 获得了新的业务事实。
下一步可能继续:
java
Action
queryInventory(SKU-10086)
得到:
可用库存:0
预计补货时间:2026-08-17
这时候 Agent 才能确定:
订单无法发货与库存不足有关。
然后根据用户最开始给出的目标:
Action
createTicket(...)
创建工单成功以后,系统已经拥有足够信息,最后再生成结果:
订单 A1001 当前处于待发货状态,商品库存不足,预计 8 月 17 日补货。我已经为该订单创建客服工单 T20260815001。
这里真正重要的不是它调用了三个 Tool。
而是:
第二个、第三个 Tool 是否执行,是由前面 Tool 的结果决定的。
这就是动态任务执行。

3. 实战:设计一个订单异常处理Agent
理解执行方式以后,再开始写代码。
这次准备四个业务能力:
queryOrder
queryInventory
queryLogistics
createTicket
分别负责:
| Tool | 作用 |
|---|---|
| queryOrder | 查询订单及商品信息 |
| queryInventory | 查询商品库存 |
| queryLogistics | 查询已发货订单物流 |
| createTicket | 创建客服工单 |
这里有一个我认为非常重要的设计原则:
不要给 Agent 一个万能 Tool。
例如直接设计:
processOrder(String orderNo)
然后在方法内部:
查订单
查库存
查物流
创建工单
技术上当然可以。
但这样一来,真正决定执行流程的其实还是 Java 代码。
Agent 只是调用了一个传统业务接口,并没有真正参与任务决策。
更合理的方式是把稳定、明确的业务能力拆出来:
Agent
├── queryOrder
├── queryInventory
├── queryLogistics
└── createTicket
然后由 Agent 根据当前任务状态动态组合。
但是 Tool 也不能拆得无限细。
我一般会遵循:
一个Tool完成一个完整、明确、可审计的业务能力。
比如:
queryOrder
是合理的。
但如果拆成:
queryOrderStatus
queryOrderAmount
queryOrderSku
queryOrderCreateTime
就很容易变成过度拆分。
Tool 数量增加以后,不仅增加模型选择难度,Tool 名称、描述和输入 Schema 本身也都会成为模型调用上下文的一部分。Spring AI 官方也强调,Tool 的名称、描述和参数 Schema 会帮助模型判断什么时候以及如何调用工具。
3.1 定义业务请求和返回对象
首先准备几个简单对象:
java
public record OrderQuery(String orderNo) {
}
public record OrderInfo(String orderNo, String skuId, String status) {
}
public record InventoryQuery(String skuId) {
}
public record InventoryInfo(String skuId, Integer availableStock, String expectedRestockTime) {
}
public record LogisticsQuery(String orderNo) {
}
public record LogisticsInfo(String orderNo, String status, String latestNode) {
}
public record TicketRequest(String orderNo, String reason) {
}
public record TicketResult(String ticketNo, String status) {
}
真实项目中这些对象背后可以继续调用:
MySQL
Redis
订单中心
库存中心
物流服务
客服工单系统
Agent 不需要知道底层数据从哪里来。
它只需要知道:
我拥有哪些稳定的业务能力。
4. 使用Spring AI Alibaba实现ReAct Agent
当前 Spring AI Alibaba Agent Framework 可以直接通过 ReactAgent.builder() 创建 Agent,并向 Agent 注册 ToolCallback。官方文档也提供了 FunctionToolCallback、Memory、Hooks 和 Interceptors 等扩展方式。
4.1 引入Agent Framework
以官方 Agent Framework 文档中的依赖方式为例:
XML
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-agent-framework</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
模型 Starter 根据项目实际使用的模型选择。
例如使用 DashScope:
XML
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>
实际项目建议通过 BOM 或统一版本变量管理依赖版本,避免不同模块分别写死版本。
4.2 创建订单查询Tool
这里使用 FunctionToolCallback。
java
@Component
public class QueryOrderTool
implements Function<OrderQuery, OrderInfo> {
private final OrderService orderService;
public QueryOrderTool(OrderService orderService) {
this.orderService = orderService;
}
@Override
public OrderInfo apply(OrderQuery request) {
return orderService.queryOrder(
request.orderNo()
);
}
}
注册 Tool:
ToolCallback queryOrderTool =
FunctionToolCallback.builder(
"queryOrder",
queryOrderToolFunction)
.description("""
查询订单基础信息。
当需要确认订单是否存在、
当前订单状态以及订单对应商品时使用。
""")
.inputType(OrderQuery.class)
.build();
Spring AI 会根据输入类型生成参数 Schema,模型根据 Tool 名称、描述以及 Schema 判断如何调用工具。
其他几个 Tool 采用相同方式:
java
ToolCallback queryInventoryTool =
FunctionToolCallback.builder(
"queryInventory",
queryInventoryFunction)
.description("""
查询商品当前可用库存。
当需要判断订单是否因为库存不足
导致无法发货时使用。
""")
.inputType(InventoryQuery.class)
.build();
物流:
java
ToolCallback queryLogisticsTool =
FunctionToolCallback.builder(
"queryLogistics",
queryLogisticsFunction)
.description("""
查询已发货订单的物流状态。
仅当订单已经发货,
需要确定物流进度时使用。
""")
.inputType(LogisticsQuery.class)
.build();
创建工单:
java
ToolCallback createTicketTool =
FunctionToolCallback.builder(
"createTicket",
createTicketFunction)
.description("""
为异常订单创建客服工单。
当已经确认订单存在异常,
并且需要客服继续处理时使用。
不允许在没有明确异常原因时创建工单。
""")
.inputType(TicketRequest.class)
.build();
这里尤其不要忽略 Tool Description。
模型看不到:
createTicket()
里面究竟有什么业务代码。
它判断"什么时候调用",主要依赖的就是 Tool Definition。
所以 Tool Description 本质上也是 Agent Prompt Engineering 的一部分。
4.3 创建ReAct Agent
准备系统指令:
java
String instruction = """
你是企业订单异常处理助手。
你的目标是帮助用户分析订单异常原因。
执行原则:
1. 涉及实时订单信息时必须调用工具查询,
不允许猜测订单状态。
2. 根据订单当前状态决定后续动作。
3. 未发货订单可以继续检查库存。
4. 已发货订单可以查询物流状态。
5. 只有确认存在需要人工继续处理的异常时,
才允许创建客服工单。
6. 不允许伪造订单、库存、物流或工单信息。
7. 工具调用失败时明确告诉用户,
不允许根据经验补全结果。
完成任务后,向用户说明:
- 当前订单状态
- 判断出的异常原因
- 已执行的处理动作
""";
创建 Agent:
java
ReactAgent orderAgent =
ReactAgent.builder()
.name("order_exception_agent")
.model(chatModel)
.instruction(instruction)
.tools(
queryOrderTool,
queryInventoryTool,
queryLogisticsTool,
createTicketTool
)
.saver(new MemorySaver())
.build();
到这里,一个基础 ReAct Agent 就已经完成。

4.4 执行Agent
调用非常简单:
java
AssistantMessage response = orderAgent.call("""
我的订单A1001三天没有发货了,
帮我看看是什么原因。
如果确认是库存异常,
帮我创建一个客服工单。
""");
System.out.println(response.getText());
可能得到:
订单A1001当前状态为待发货。
经查询,该订单商品SKU-10086当前可用库存为0,
预计8月17日补货,因此暂时无法正常发货。
已为该订单创建客服工单:
T20260815001。
客服人员将继续跟进该订单。
代码看起来并不复杂。
但和普通 Tool Calling 相比,真正发生变化的是:
执行流程不再完全由开发人员提前写死。
5. 一次完整任务到底是如何执行的?
假设订单数据如下:
订单:A1001
状态:待发货
SKU:SKU-10086
库存:
SKU-10086
可用库存:0
预计补货:2026-08-17
用户输入:
订单A1001一直没有发货,
帮我查下什么原因。
如果库存有问题,
就创建一个客服工单。
Agent 第一次处理时并不知道:
订单是否存在
订单有没有发货
商品是什么
库存是否正常
是否真的需要创建工单
因此第一步会调用:
queryOrder
得到:
java
{
"orderNo": "A1001",
"skuId": "SKU-10086",
"status": "待发货"
}
因为订单没有发货,物流查询没有意义。
Agent 可以继续选择:
queryInventory
得到:
java
{
"skuId": "SKU-10086",
"availableStock": 0,
"expectedRestockTime": "2026-08-17"
}
现在才真正确定:
订单未发货
+
库存为0
=
库存异常
用户又提前授权:
如果库存异常,就创建客服工单。
因此继续:
createTicket
得到:
java
{
"ticketNo": "T20260815001",
"status": "CREATED"
}
信息已经足够,Agent 停止调用 Tool,生成最终回答。
这就是一次完整的 ReAct 循环。
这里也能看出:
Agent真正有价值的地方并不是"能够调用很多Tool",而是能够根据中间结果调整执行路径。
如果订单第一次查询出来:
status = 已发货
那后面合理的路径就应该变成:
queryOrder
↓
queryLogistics
↓
返回物流状态
而不是继续查询库存和创建工单。
同一个用户入口,因为业务状态不同,可以形成完全不同的执行链。
6. 企业Agent真正难的不是"会调用",而是"可控"
如果文章写到这里就结束,其实只能算一个 Agent Demo。
真正进入企业项目以后,我认为 Agent 最难解决的问题并不是:
怎么让模型调用 Tool?
而是:
怎么保证它只在允许的范围里行动。
6.1 Agent必须有停止条件
ReAct 本质上是一个循环。
正常情况:
Model
↓
Tool
↓
Model
↓
Tool
↓
Model
↓
Final Answer
但异常情况下完全可能出现:
queryOrder
↓
queryInventory
↓
queryOrder
↓
queryInventory
↓
queryOrder
↓
......
如果没有控制,就可能带来:
- 模型调用次数持续增加;
- Token成本不断增加;
- 外部接口反复调用;
- 整体请求长时间不结束。
Spring AI Alibaba 当前 Agent Framework 已经提供 ModelCallLimitHook 对模型调用次数进行限制,用来避免失控循环和过高调用成本。
例如:
java
ModelCallLimitHook modelCallLimit =
ModelCallLimitHook.builder()
.runLimit(6)
.build();
加入 Agent:
java
ReactAgent orderAgent =
ReactAgent.builder()
.name("order_exception_agent")
.model(chatModel)
.instruction(instruction)
.tools(
queryOrderTool,
queryInventoryTool,
queryLogisticsTool,
createTicketTool
)
.hooks(modelCallLimit)
.saver(new MemorySaver())
.build();
实际生产中,我还会在 Agent 外层增加整体 Timeout。
最终形成:
Agent最大执行步数
+
模型调用限制
+
Tool自身Timeout
+
整个Agent链路Timeout
多层控制。
6.2 Tool调用失败不能让Agent自己猜
真实企业环境一定会出现:
数据库超时
RPC失败
库存中心不可用
物流接口异常
参数错误
订单不存在
权限不足
如果 Tool 抛异常以后直接把整个 Agent 打成 500,用户体验很差。
但另一种做法更危险:
Tool失败以后,让模型自己推测结果。
比如库存接口失败,模型却回答:
大概率是库存不足。
这是企业 AI 中必须避免的。
更合理的是把错误转换成明确的 Tool Result:
INVENTORY_SERVICE_UNAVAILABLE
然后告诉模型:
当前无法确认库存情况,不允许继续推断库存异常。
Spring AI Alibaba 当前也提供 Tool Interceptor,可用于工具错误处理、重试和失败结果转换;官方还提供了 ToolRetryInterceptor 对适合重试的瞬时错误进行有限重试。
但重试也不能无脑使用。
例如:
queryInventory
查询失败,可以安全重试一次。
而:
createTicket
如果请求超时,却不能直接判断"没有创建成功"。
这时候首先应该依赖:
幂等Key
+
业务状态查询
确认第一次是否已经成功,再决定是否重试。
这仍然是传统后端工程问题。
Agent 并没有改变这一点。
6.3 查询型Tool和操作型Tool必须分级
我不会让所有 Tool 都拥有同样的执行权限。
例如:
低风险
查询订单
查询库存
查询物流
查询知识库
通常经过身份和数据权限校验后,可以允许 Agent 自动执行。
中风险
创建客服工单
生成报表
创建草稿
提交内部任务
可以根据业务规则决定是否允许自动执行。
高风险
退款
删除数据
最终审批
修改权限
付款
批量修改
我不会简单地通过 Prompt:
执行之前一定要询问用户。
然后就认为系统安全了。
因为 Prompt 是模型约束。
真正的企业安全边界应该是:
代码权限
+
业务规则
+
审批/确认
+
审计
高风险 Tool 最好通过 Human-in-the-loop 进行人工确认。
Spring AI Alibaba 当前已经提供 HumanInTheLoopHook,可以针对特定 Tool 暂停 Agent 执行,等待人工批准、修改或拒绝;这种中断恢复需要配合持久化 Checkpointer 保存 Agent 状态。
逻辑可以设计成:
Agent判断需要退款
↓
准备refund Tool Call
↓
暂停
↓
人工/用户确认
↓
批准?
├── 否 → 取消
└── 是 → 真正执行refund
这也是我认为企业 Agent 和 Demo Agent 最大的差别之一:
不是Agent越自主越高级。
企业真正需要的是:
在可控边界内自主。
6.4 Agent必须可观测
传统接口出问题,我们通常会看:
TraceId
接口耗时
SQL
RPC
异常日志
到了 Agent 场景还要再增加一层。
至少应该能够知道:
一次Agent任务执行了多少步
调用了多少次模型
调用了哪些Tool
Tool参数是什么
Tool执行是否成功
每一步耗时多少
整个Agent耗时多少
消耗多少Token
最终任务是否完成
否则用户说:
AI昨天把一个订单处理错了。
开发人员连它当时:
调用过什么Tool
为什么进入下一步
哪一个Tool返回异常
都不知道,就几乎没有办法定位问题。
所以 Agent 真正生产化以后:
Tracing
+
Tool Audit
+
Token Usage
+
Latency
+
Success Rate
都应该纳入监控。
这也是后面做 Agent Evaluation 的基础。
7. Agent和Workflow到底怎么选?
理解 Agent 以后,另一个很容易出现的问题是:
既然 Agent 可以动态决定下一步,是不是复杂业务全部交给 Agent 就行?
我认为恰恰相反。
越是核心业务,越不应该为了"智能"而强行Agent化。
Workflow更适合什么?
如果业务流程天然确定:
创建订单
↓
参数校验
↓
库存锁定
↓
支付
↓
生成订单
↓
通知
这类流程最重要的是:
稳定
可预测
可测试
可回滚
显然 Workflow 更合适。
Agent更适合什么?
如果只有目标明确,但具体执行路径不确定:
分析这个订单为什么异常,并根据情况给出处理方案。
系统开始时不知道:
需要查订单?
需要查物流?
需要查库存?
需要查规则?
是否需要创建工单?
路径依赖运行时信息动态产生。
这种场景 Agent 更合适。
企业项目中更常见的是混合模式
真正让我更认可的一种设计其实是:
Workflow
↓
固定确定性流程
↓
Agent
↓
处理局部不确定任务
↓
Human-in-the-loop
↓
Workflow继续执行
例如一个售后流程:
用户提交售后申请
↓
Workflow完成身份和订单校验
↓
Agent分析问题与相关资料
↓
Agent给出处理建议
↓
人工确认高风险操作
↓
Workflow执行退款/换货
↓
记录审计结果
这里:
确定的事情交给代码和Workflow。
不确定的判断交给Agent。
高风险决策交给人。

这也是我现在理解企业 Agent 架构非常重要的一条原则:
Agent不是Workflow的替代品。
它更像是给原来完全确定性的企业系统增加了一块:
能够处理不确定任务的动态决策能力。
总结
如果只是看代码,创建一个 Spring AI Alibaba ReactAgent 并不复杂:
java
ReactAgent.builder()
.name("order_exception_agent")
.model(chatModel)
.tools(...)
.instruction(...)
.build();
真正困难的是代码之外的问题。
什么时候应该调用 Tool?
Tool 应该拆多细?
什么时候继续执行?
什么时候停止?
调用失败怎么办?
哪些 Tool 可以自动调用?
哪些操作必须人工确认?
Agent 和 Workflow 应该如何组合?
这些问题才决定一个 Agent 最后是:
一个能够演示的Demo。
还是:
一个真正能够进入企业系统的AI能力。
我现在更愿意把这几个概念理解成:
Tool Calling
让AI拥有"做事的能力"
Agent
让AI能够围绕目标
动态决定"下一步做什么"
Workflow
负责确定性的业务流程
Human-in-the-loop
负责控制高风险决策
所以企业 AI 从 Tool Calling 走向 Agent,并不是简单地:
多调用几个 Tool。
真正发生的变化是:
我们开始把一部分原本必须提前写死在代码中的执行路径,交给模型根据上下文动态决策。
而企业级架构真正需要解决的,则是另一半问题:
如何把这种不确定性,限制在一个确定、可观测、可审计、可控制的边界之内。
这才是 Agent 真正从 Demo 走向生产的开始。
📚 推荐专栏
🏗️ 企业AI架构与实战
从企业真实落地视角,持续分享RAG、Agent、Workflow、AI Gateway、工程治理与系统架构设计。
👉 https://blog.csdn.net/qupengkun/category_13192323.html
🤖 Java转AI技术专栏
从0到1学习AI应用开发,持续分享Spring AI、RAG、Agent与企业级AI项目实战。
👉 https://blog.csdn.net/qupengkun/category_13184360.html
🧩 AI开源项目实战
持续分享Java AI业务项目、AI Coding效率工具和工程治理实践。
👉 https://blog.csdn.net/qupengkun/category_13189373.html
🚀 AI转型日记
记录一名10年Java开发者从传统后端转向AI应用开发的全过程。
👉 https://blog.csdn.net/qupengkun/category_13183497.html
👨💻 关于作者
QCoding
专注AI应用开发与Java技术实践。
持续分享Spring AI、RAG、Agent、企业级AI项目实战、架构设计与职业成长。