在 Java 应用里接入大模型,很多团队的第一步是发一个 REST 请求,拿到模型返回结果就收工。看起来能用,但真正把大模型放进业务系统时,你会发现单轮“问-答”根本撑不住:用户问题需要拆解,业务逻辑需要分支,工具需要按条件调用,异常还要重试回退。这些复杂流程叠加在一起,就形成了一个关键命题——Java AI Agent 如何工程化落地。
这篇文章围绕 Java AI Agent 展开,分四层推进:先讲明白 Agent 与 ReAct/Loop 的底层关系,再解释为什么企业级项目要用 Graph 来编排节点,然后给出 Spring AI Alibaba 下图化实现思路,最后补充生产环境的故障排查与工程最佳实践。适合想把手头只有 Chat API 的 Spring Boot 项目升级成“会思考、会调用工具的 Agent”的开发者;也适合正在做 Agent 架构选型、想知道什么时候该从“手动循环”切换到“图编排”的架构师。
1. 背景与核心概念
1.1 什么是 AI Agent
AI Agent 通常被翻译为“智能体”,但不同资料里定义差别很大。简单理解:Agent 是一个能接收目标、自主推理、调用工具并完成任务的程序单元。它不是聊天机器人——聊天机器人只负责“回答”,而 Agent 负责“完成”。
一个相对完整的 AI Agent 由三部分组成:
- 大脑:大模型 LLM,负责理解意图、规划步骤、生成推理。
- 工具/执行器:API、数据库、外部服务、本地函数,是 Agent 影响现实世界的手脚。
- 状态/记忆:记录当前任务进度、上下文、历史调用结果,让 Agent 能在多轮迭代中保持一致性。
在 Java 生态里,最容易被忽略的是第三部分。很多团队用 Spring Boot 封装了一个 ChatClient,然后发现 Agent 执行到一半就“失忆”了,原因是状态没有纳入工程结构,只是靠 prompt 里的历史消息硬撑。
1.2 为什么单次 LLM 调用不够用
大模型提供的原生接口是 Chat Completion:输入 Prompt,输出文本。这个接口本身不具备“完成业务流程”的能力。举一个真实场景:用户问“我的订单为什么还没发货”,系统需要先识别意图,再根据订单号查询物流数据,最后结合业务规则生成用户可读的答复。
如果只用一次 LLM 调用,模型既拿不到用户私有订单数据,也没有能力执行数据库查询。常见的错误做法是把“所有相关数据”全部塞进 Prompt,让模型“看着办”。这在数据量大、权限敏感、接口调用有副作用的场景下根本不可行。
所以 Agent 的本质是一种“编排”:让 LLM 在合适的时机触发合适的工具,再把工具结果喂回 LLM,循环直到任务完成。这个“触发-观察-再推理”的过程,就是 ReAct/Loop 要解决的问题。
1.3 什么是 ReAct 模式
ReAct 是 Reasoning + Acting 的合成词,核心思想是让 LLM 交替进行“思考”和“行动”,而不是一次性生成最终答案。一个标准的 ReAct 循环包含四个阶段:
- Thought:LLM 根据当前信息推理,决定下一步该做什么。
- Action:输出一个结构化指令,比如“调用订单查询工具,参数为 orderId=20240817001”。
- Observation:工具执行后返回观察结果,比如“订单已出库,等待揽收”。
- Loop:观察结果作为新输入,继续进入下一轮 Thought,直到模型认为可以给出 Final Answer。
这里最关键的是:Action 不能是自然语言聊天,而应该是可执行的结构化指令。在 Java 工程中,常见的 Action 形式是 JSON,例如{"tool": "queryOrder", "args": {"orderId": "20240817001"}},然后由代码解析、路由、执行。
1.4 什么是 Graph 工程化
当一个 Agent 只有两三个步骤时,手写 while 循环完全够用。但生产系统的 Agent 往往有分支。比如用户申请退款,Agent 要先判断是否符合退款政策,再判断是否需要人工审批,失败还要走重试或上升通道。如果用if-else堆,代码很快就变成“面条式逻辑”。
Java AI Agent 的 Graph 工程化,就是把 Agent 流程拆成“节点”和“边”两部分:
- 节点:一个独立执行单元,可以是 LLM 调用、工具调用、规则判断。
- 边:定义节点的流转方向,或者条件分支。有向图的终点就是 Agent 任务结束。
Graph 把循环、分支、并行这些复杂逻辑从散落的代码中提取出来,变成显式定义的一张“流程图”。实现到这种程度,Agent 才具备企业级工程化落地的条件。
2. Java 侧大模型接入与 Spring AI Alibaba
2.1 直接 HTTP 调用整体流程
最早期的 Java 项目接入大模型,通常直接通过 HTTP 调用模型服务端接口。示例是:拼接 Prompt,请求/v1/chat/completions,解析响应中的choices[0].message.content。这种方式启动快,但问题也明显:每个模型厂商的请求格式不一样,流式输出要自己处理 SSE,工具调用要自己解析 JSON,换模型商等于重写一层。
Java 标准库自带的HttpClient可以完成最简单的调用,但一旦涉及超时、重试、拦截、多模型切换,代码会迅速膨胀。因此企业项目很少再直接裸写 HTTP。
2.2 Spring AI 的统一抽象
Spring AI 是目前 Java 生态中比较重要的大模型接入抽象层。它把不同模型厂商的统一接口封装成了ChatClient、ChatModel等组件。开发者面向 Spring AI 接口编写业务代码,底层 Model 可以通过配置切换。对于已有的 Spring Boot 项目,集成成本相对可控。
Spring AI 也提供了 Tool Calling 的基础支持,可以把 Java 方法暴露给模型。这其实就是 Agent 的“手”。但要注意:Spring AI 解决的是“调用”问题,不是“编排”问题。
2.3 Spring AI Alibaba 的图化增强
Spring AI Alibaba 是阿里巴巴基于 Spring AI 构建的工程化增强方案。它除了提供 DashScope、通义千问等模型的快速接入外,还提供了与阿里云基础设施的整合能力,例如 Nacos 配置、Redis 缓存、监控链路等。
真正把“Java AI Agent”和“Graph Engineering”结合起来的,是 Spring AI Alibaba 提供的 Graph 模块。按照模块的功能定位,它借鉴了图编排思想,让开发者用 Java 代码定义 State(状态)、Node(节点)、Edge(边),把 LLM 调用和工具调用组合成一张有向图。相比纯手写循环,Graph 模块让流程更清晰、分支更容易控制、日志和观测也更方便。
这里先明确一个前提:Spring AI Alibaba 版本迭代较快,不同版本的类名、方法名、坐标会有差异。本文示例重点演示设计思路,具体落地时要对照你当前版本的官方文档。
3. ReAct/Loop 原理与手动实现
3.1 ReAct 循环的四个阶段
先看一个典型的 ReAct 文本输出。模型收到任务“查询订单 20240817001 的物流状态”后,可能输出如下内容:
Thought: 用户需要查询订单物流状态,我需要调用订单查询工具。 Action: {"tool": "queryOrder", "args": {"orderId": "20240817001"}} Observation: 订单已出库,等待快递揽收。 Thought: 订单已出库,可以把状态整理成用户可读的回答。 Final Answer: 您好,您的订单已经出库,正在等待快递上门揽收。把四个阶段对应到工程实现:
| 阶段 | 作用 | 输出形式 |
|---|---|---|
| Thought | 让模型先分析当前状态和下一步计划 | 自然语言推理 |
| Action | 让模型发出可执行的工具调用指令 | 结构化 JSON |
| Observation | 让 Java 侧执行工具并把结果回传 | 文本或数据结构 |
| Loop/Final Answer | 重复以上流程,直到完成 | 最终回复 |
需要注意一个常见误区:不是每个循环都必须调用工具。当模型认为不再需要观察时,它应该输出 Final Answer 结束循环。循环终止条件必须由代码强约束,不能完全依赖模型自觉。
3.2 Java 手动实现一段 ReAct 循环
在引入 Graph 之前,先用手动循环体会一下 ReAct 的核心代码逻辑。以下是一个简化示例,核心是循环拼接 Prompt、解析 Action、执行工具、回填 Observation。
// 文件路径:src/main/java/com/example/agent/ReActLoop.java public class ReActLoop { private final ChatClient chatClient; private final Map<String, Function<String, String>> tools; public ReActLoop(ChatClient chatClient, Map<String, Function<String, String>> tools) { this.chatClient = chatClient; this.tools = tools; } public String run(String userTask, int maxSteps) { String currentInput = userTask; // 这里是一个 Text Block,简化了 prompt 模板拼接 for (int step = 0; step < maxSteps; step++) { String response = chatClient.prompt() .system(""" 你是一个能调用 Java 工具完成任务的 Agent。 如果需要调用工具,请输出 JSON: {"tool": "工具名称", "args": "参数"} 如果已经拿到结果,请输出: {"finalAnswer": "最终答案"} """) .user(currentInput) .call() .content(); String finalAnswer = extractFinalAnswer(response); if (finalAnswer != null) { return finalAnswer; } String toolName = extractToolName(response); String toolArgs = extractToolArgs(response); String observation = tools.getOrDefault(toolName, unknown -> "未知工具").apply(toolArgs); // 把上一轮模型输出和工具结果一起作为下一轮输入 currentInput = response + "\nObservation: " + observation; } return "已超过最大循环步数,任务未完成,建议转人工。"; } }上面代码中的extractFinalAnswer、extractToolName、extractToolArgs是对模型输出文本的解析。实际项目中,最好要求模型输出 JSON,再用 Jackson 解析,避免字符串匹配出错。这段代码形式上是完整的,但缺少超时控制、工具鉴权、日志埋点、循环步数动态调整等能力,所以不建议直接用于生产。
3.3 手动循环的工程痛点
手动 ReAct 循环在校验思路上没有问题,问题是工程化之后会渐渐失控。首先是状态管理混乱:每一步都要靠字符串拼接维护上下文,节点一多,代码里到处都是临时变量。其次是分支逻辑难以维护:如果工具查询失败要重试,重试三次还失败要转人工,全部写在同一个 for 循环里,条件判断会非常长。
还有一个容易被忽视的问题是可观测性。手写循环时,每一个节点做过什么、每一步模型调用了哪个工具、耗时多久,都要额外写日志。而图化编排天然把每个节点包装成独立单元,可以统一打点、监控、回放。这就是我们要引入 Graph 工程化的直接原因。
4. Graph 编排:把 Agent 变成一张图
4.1 图的三个基础要素:State、Node、Edge
理解 Graph 工程化,只需要抓住三个核心概念。
State 是贯穿整个流程的数据载体。在当前回合调用工具的花费、模型输出、用户输入等,都写在 State 里。不同节点通过 State 通信,节点之间不直接互相调用。这就解决了手动循环中“状态满天飞”的问题。
Node 是执行单元。它可以是一个 LLM 调用,可以是一个工具函数,也可以是一个普通的 Java 方法。每个 Node 接收前序传递的 State,处理后返回新的 State。Node 本身应该尽量无状态,这样便于测试和复用。
Edge 是节点之间的连接关系。基础边表示“无条件流转”,比如 A 节点执行完后必须走到 B 节点。条件边表示“按条件选择下一个节点”,例如根据工具调用结果判断是继续重试还是进入人工审批。
[Java入口] → [意图识别节点] → [工具调用节点] → [结果校验节点] ↑ | └──── 重试 ──────┘这张 ASCII 图展示了最精简的图化流程:意图识别完成后无条件进入工具调用,工具调用后进入结果校验,如果校验失败则回到工具调用节点,形成循环。
4.2 条件分支与循环的图化表达
循环在图里不是特殊语法,而是“边界回退”。只要有一条边从后置节点指回前置节点,就构成了循环。同样,分支也是普通边,只是选择哪个下游节点由条件函数决定。
以“智能客服工单 Agent”为例,当用户问“我的订单为什么还没发货”时,图化后的流程可能是:
- classify 节点:LLM 识别意图,输出“物流查询”。
- orderTool 节点:调用订单系统接口,查询物流状态。
- check 节点:判断查询结果是否合法。
- reply 节点:生成用户回答。
- 如果 check 失败,则从 check 节点回到 orderTool 节点,最多重试 2 次。
这种表达方式让每一步都变得可见、可控、可测试。代码里能看到整张图的路径,而不是在一大段 if-else 里找逻辑。
4.3 从 ReAct 到 Graph 的最小映射表
把前面的 ReAct 四阶段映射到图里,理解会很直观:
| ReAct 阶段 | Graph 中的位置 | 说明 |
|---|---|---|
| Thought | 推理节点 | 通常是 LLM Chat 节点 |
| Action | 工具节点 | 执行 Java 方法、API 或 SQL |
| Observation | 条件判断节点 | 决定下一步走向 |
| Loop | 循环边 | 工具节点 → 条件判断 → 工具节点 |
这里的重点不是“ReAct 被 Graph 替代”,而是“Graph 把 ReAct 变成一种显式流程”。Agent 仍然是循环思考再行动,只是循环边界、分支条件、状态传递都由 Graph 框架统一管理。
4.4 为什么 Graph 更适合工程化
Graph 工程化的优势主要体现在四个方面。第一是模块化:每个节点是一个独立函数,可以单独单测,替换实现时不影响其他节点。第二是可观测:框架可以在每个节点前后统一埋点,记录输入输出和耗时。第三是可审计:整张图的执行路径可以被记录下来,问题定位不再靠猜。第四是多 Agent 协作:当多个 Agent 需要编排时,图可以表达跨 Agent 的调用关系,而普通循环很难做到这一点。
所以并不是说手写 ReAct 不能做事,而是到了生产环境,图编排的维护成本明显更低。
5. Spring AI Alibaba Graph 实战:智能客服工单 Agent
5.1 业务场景设计
为了把前面概念落到实处,我们来设计一个“智能客服工单 Agent”。场景如下:用户在前端提交问题“我的订单为什么还没发货?”,后端需要完成意图识别、订单查询、结果校验、最终回复四步,并且查询失败时要自动重试一次。
这个场景非常适合图编排,因为流程不是一条直线:查询接口可能超时,订单号可能不存在,意图可能不明确。如果把这些判断全部写在if-else里,代码会非常臃肿;用 Graph 表达则清晰很多。
5.2 环境准备与版本说明
本文示例以 JDK 17 + Spring Boot 3.3.x + Maven 为例。Spring AI Alibaba 版本迭代较快,实际项目中请选择与 Spring Boot 匹配的稳定版本,不要直接把版本号写死成示例中的值。
项目依赖建议如下,具体坐标以当前版本官方文档为准:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.5</version> <relativePath/> </parent> <dependencies> <!-- Spring AI Alibaba 启动器 --> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> <version>${spring-ai-alibaba.version}</version> </dependency> <!-- 如果当前版本有独立 Graph 模块,则按官方坐标引入 --> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-graph</artifactId> <version>${spring-ai-alibaba.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>spring-ai-alibaba.version请选择与你的 Spring Boot 版本兼容的版本,并在引入前查看官方 release notes。配置文件如下:
# 文件路径:src/main/resources/application.properties spring.application.name=agent-demo spring.ai.dashscope.api-key=${DASHSCOPE_API_KEY} # 开启 Spring AI Alibaba 相关日志 logging.level.com.alibaba.cloud.ai=DEBUG这里使用${DASHSCOPE_API_KEY}从环境变量读取密钥,是推荐做法,避免把 API Key 提交到代码仓库。如果你使用的是其他模型厂商,按对应配置项修改即可。
5.3 定义状态与节点
Graph 里的 State 可以是自定义对象,也可以是对Map<String, Object>的封装。为了便于理解,这里定义一个简单的 AgentState:
// 文件路径:src/main/java/com/example/agent/AgentState.java public class AgentState { private Map<String, Object> data = new HashMap<>(); public AgentState(Map<String, Object> data) { this.data = data; } @SuppressWarnings("unchecked") public <T> T get(String key) { return (T) data.get(key); } public void set(String key, Object value) { data.put(key, value); } public static AgentState initial(String userQuestion) { Map<String, Object> map = new HashMap<>(); map.put("userQuestion", userQuestion); return new AgentState(map); } }实际 Spring AI Alibaba 的 Graph 模块可能自带了 State 封装类,如果你用的是自带类,则不需要自己定义。上面的代码只是为了表达“状态集中承载”的工程思想。
接下来定义三个节点。第一个是意图识别节点,负责调用 LLM 判断用户意图:
// 意图识别节点 Node classifyNode = state -> { String userQuestion = state.get("userQuestion"); String intent = chatClient.prompt() .system(""" 你是客服系统意图识别器。 用户问题只输出一个分类词:退款、物流、库存、其他。 不要输出任何解释。 """) .user(userQuestion) .call() .content(); state.set("intent", intent); return state; };第二个是订单查询工具节点。这里用模拟数据代替真实接口,实际项目中可以把方法体替换成 HTTP 调用或数据库查询:
// 订单查询工具节点 Node orderToolNode = state -> { String orderId = state.getOrDefault("orderId", "20240817001"); // 模拟调用订单服务,实际项目中替换为 RPC / HTTP / MyBatis 查询 String result = orderService.queryLogistics(orderId); state.set("orderResult", result); return state; };第三个是最终回复节点,负责把工具查询结果整理成用户可读的答复:
// 最终回复节点 Node replyNode = state -> { String orderResult = state.get("orderResult"); String finalAnswer = chatClient.prompt() .system("你是客服助手,请根据订单查询结果生成友好、清晰的回答。") .user("订单查询结果:" + orderResult) .call() .content(); state.set("finalAnswer", finalAnswer); return state; };这三个节点只与 State 打交道,彼此之间不直接依赖。这就是图工程化带来的模块化收益:想替换意图识别模型,只改 classifyNode;想调整话术,只改 replyNode。
5.4 构建图并执行
节点定义好之后,下一步是用 Graph 把它们连接起来。下面代码展示了最基本的图构建方式:
// 文件路径:src/main/java/com/example/agent/GraphDemo.java public String run(String userQuestion) { Node classifyNode = buildClassifyNode(); Node orderToolNode = buildOrderToolNode(); Node replyNode = buildReplyNode(); Graph graph = new Graph(); graph.addNode("classify", classifyNode); graph.addNode("orderTool", orderToolNode); graph.addNode("reply", replyNode); // 无条件边:意图识别 → 订单查询 graph.addEdge("classify", "orderTool"); // 条件边:如果查询结果为空,则回到 orderTool 重试;否则进入 reply graph.addConditionalEdge("orderTool", (state) -> { String result = state.get("orderResult"); if (result == null || result.isBlank()) { return "retry"; } return "reply"; }, Map.of( "retry", "orderTool", "reply", "reply" )); graph.addEdge("reply", "END"); // 编译图,实际 API 可能不需要显式 compile,按版本调整 graph.compile(); AgentState initialState = AgentState.initial(userQuestion); AgentState finalState = graph.execute(initialState); return finalState.get("finalAnswer"); }上面这段代码属于示例思路,不同版本的 Spring AI Alibaba Graph API 在addConditionalEdge、compile、execute等方法上可能存在差异。核心思想是固定的:把节点注册进图,用边定义流转,用条件函数决定分支。
5.5 运行与验证
启动 Spring Boot 应用后,通过接口触发 Agent 执行,预期流程和输出如下:
用户问题: 我的订单为什么还没发货? 意图识别: 物流查询 订单工具调用: orderService.queryLogistics(orderId=20240817001) 工具结果: 订单已出库,等待快递揽收。 最终回答: 您好,您的订单已经出库,正在等待快递上门揽收,请耐心等待物流更新。如果订单查询结果为空,条件边会返回retry,流程自动回到orderTool节点,实现循环重试,而不是程序崩溃或直接返回空结果。这就是图化表达循环的优势:循环是一个显式的边回退,日志里能清楚看到重试路径。
5.6 扩展思路:记忆、流式与多 Agent
在 Graph 基础上,Agent 可以直接扩展出更多能力。比如多轮对话,可以在 State 中加入chatHistory字段,每次执行前把历史消息组装进 Prompt。比如流式输出,可以把 LLM 节点的call()改成stream(),把 Token 逐段推送给前端。比如多 Agent 协作,可以让某个节点作为“调度节点”,内部再维护一张子图。
这些扩展都建立在“节点只负责一件事”的基础上。只要状态设计合理,图可以承载越来越复杂的业务,而不会变成不可维护的巨型类。
6. 常见问题与排查思路
Agent 和 Graph 落地时,最常遇到的问题集中在这几个方面:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 图执行时死循环 | 条件边一直没有返回结束分支 | 给图设置最大执行步数或超时时间 |
| LLM 返回的 Action 无法解析 | 模型输出夹杂了多余自然语言 | 强制 JSON 输出格式,配合解析兜底 |
| 工具调用超时 | 外部接口不稳定 | 为工具节点单独设置超时与重试 |
| 多轮执行后上下文越来越长 | 历史消息无节制追加 | 做摘要压缩或滑动窗口裁剪 |
| 同一问题结果不稳定 | 模型随机性高,提示词模板不固定 | 设置 temperature,固定评分和回归测试 |
| Agent 擅自执行高风险操作 | 工具链路缺少权限控制 | 对敏感节点增加人工确认或授权校验 |
排查时建议按下面顺序走一遍:
- 先确认图是否编译成功,节点名称和边名称是否拼写一致。
- 打开日志,确认每个节点是否按预期顺序执行。
- 检查条件边函数返回的分支名与
Map中的键是否完全一致。 - 检查 State 中关键字段是否在节点之间正确传递,有没有被覆盖。
- 如果调用 LLM 偶尔失败,确认 API Key、网络代理、限流参数是否正常。
大多数“图跑了但结果不对”的问题,根因都是条件判断分支名不统一,或者 State 字段被后置节点覆盖。加一个拦截器在每个节点执行后打印 State,问题通常一目了然。
7. 最佳实践与工程建议
7.1 用常量统一节点和分支名称
Graph 里边的目标节点名是字符串。一旦某个分支名写错,图可能直接执行失败。建议把所有节点名和分支名定义为常量或枚举,避免魔法字符串。比如:
public static final String NODE_CLASSIFY = "classify"; public static final String BRANCH_RETRY = "retry";这样编译期就能发现拼写错误,图构建阶段也更安全。
7.2 节点保持无状态
每个节点只依赖 State 输入,不依赖外部全局变量。节点的局部状态可以放在方法内部,但跨节点共享的一切数据必须写进 State。这样带来的直接好处是:可以随意调整节点顺序,可以单测节点,也可以在流程中断后重放。
7.3 合理拆分节点粒度
不要把“LLM 调用+工具调用+条件判断”塞进同一个节点。一个节点只做一件事,是 Graph 工程化的核心原则。节点粒度越细,图就越清晰,测试越容易。但也不能拆得过碎,否则图会变得非常大,光节点命名就让人头疼。一般以“一个可独立测试的业务动作”为一个节点。
7.4 给 Agent 流程设置安全边界
Agent 和普通程序不同,它会根据模型输出决定下一步动作。这带来很大的不确定性。生产环境必须包含:循环最大步数、单次工具调用超时、敏感接口白名单、人工审批节点。尤其是涉及退款、删除、转账等操作,建议在图中加入“人工确认节点”,Agent 只负责生成审批单,不直接执行。
7.5 用日志与监控覆盖整张图
每个节点的输入输出都应该被记录。可以用统一拦截器包装节点执行逻辑,记录节点名、耗时、是否成功。如果接入分布式链路追踪,可以在 State 中加入 traceId,让 Agent 执行链路和业务请求链路串联起来,排查问题会快很多。
7.6 重视回归测试和模型评估
SQL 和接口有标准答案,可以写断言。LLM 没有标准答案。正确的做法是维护一批典型测试用例,每个用例包含“输入问题、期望路径、期望结果关键词”,每次调整 Prompt 或模型参数后跑一遍回归。路径断言用 Graph 执行日志,结果断言用关键词匹配或人工标注。没有回归保护的 Agent,迟早会在某个新版本模型下悄悄坏掉。
7.7 不要在一个图里塞太多 Agent 能力
短期内把多个 Agent 能力塞进同一张图,会让状态字段暴增,条件边难以维护。更推荐的做法是:先为每个独立业务场景建立一张小图,图与图之间通过父节点进行嵌套调用。这样既能实现多 Agent 协作,又不会让单张图复杂到失控。
8. 总结与后续学习路线
读完本文,你应该对 Java AI Agent 的工程化路径有了清晰认知:单次 LLM 调用解决不了业务流程问题,ReAct 循环提供了“思考-行动-观察”的基本框架,而 Graph 工程化把循环、分支、状态统一纳入了工程结构,最终通过 Spring AI Alibaba 的 Graph 思想把 Agent 流程落地成可维护、可观测的代码。
下一步建议从一个小场景开始练手:把本文的客服工单 Agent 改成你自己的业务,比如“库存查询 + 价格咨询 + 优惠判断”,自己定义一张三四个节点的图。然后逐步加上重试、记忆、工具鉴权和日志埋点。当你发现节点越来越多、流程开始膨胀时,再去研究多 Agent 协作、RAG 检索增强和模型评测体系。
实际项目中,最终决定 Agent 能否稳定上线的,往往不是模型选得多先进,而是工程边界画得清不清楚。建议你优先关注两件事:一是条件分支的收敛性,确保每个流程最终都能走到结束节点;二是敏感操作的安全隔离,让 Agent 只建议、不擅动。如果本文对你有帮助,可以收藏备用,也欢迎在评论区聊聊你在 Java Agent 落地时遇到的坑。