让Java开发者像写Spring Boot一样开发AI应用——第四课
写在前面
前三天,我们学会了创建单Agent、理解ReAct循环、构建多Agent协作系统。
但有一个问题始终在困扰着许多开发者:ReAct Agent运行时,为什么经常“原地打转”?
明明已经查过天气了,它又查一遍;明明已经得到答案了,它还在继续思考。
这不是你的代码写错了,而是ReAct模式本身存在结构性缺陷。
今天的课,我们不仅要解剖ReAct的三大缺陷,还要引入另一种截然不同的思考方式——Plan-and-Execute,并告诉你什么时候该用哪种模式。
一、ReAct的困境:为什么Agent会“原地打转”?
先快速回顾ReAct的核心循环:
Thought(思考)→ Action(行动)→ Observation(观察)→ Thought → …
ReAct将推理(Reasoning)和行动(Acting)相结合,通过持续观察环境反馈动态调整决策路径。它的本质是:CoT(思维链)+ 工具调用 + 环境反馈闭环。
就像一个经验丰富的老医生——问一句、查一下、想一想,再决定下一步怎么做。
但问题也恰恰出在这里。
缺陷一:无限循环(Infinite Loop)
ReAct最致命的缺陷:Agent可能陷入Thought→Action→Observation的死循环,反复执行相同的操作而无法推进任务。
Thought: 我需要查天气 Action: 调用 get_weather("上海") Observation: 上海晴,25°C Thought: 我需要查天气 ← 又回到原点! Action: 调用 get_weather("上海") ← 重复调用 Observation: 上海晴,25°C ← 无限循环...为什么会发生?
- 模型“忘记”了自己已经做过什么
- 模型对当前进度缺乏清晰的认知
- 没有内置的“停止”机制
💡解决方案:设置最大迭代次数(recursion limit),通常25-30次是合理阈值。
缺陷二:上下文爆炸(Context Explosion)
ReAct的每一步Thought→Action→Observation都会追加到上下文中。随着循环次数增加,上下文越来越长,最终导致:
- Token消耗激增
- 触发上下文窗口限制
- “关键信息遗忘”(Lost in the Middle)问题
💡 就像一个人不停地往背包里塞东西——背包越来越重,最后连钥匙都找不到了。
缺陷三:缺乏全局规划视角
ReAct的“规划”仍是单路径、线性的——它不能并行探索多条方案,也不能在推理链死胡同时回溯。每一步只考虑当前信息,缺乏对整体任务的全盘把握。
💡课堂话术:ReAct就像一个没有地图的探险家——边走边看,遇到岔路就随机选一条。运气好能走出来,运气不好就在原地打转。
ReAct缺陷总结
| 缺陷 | 表现 | 后果 |
|---|---|---|
| 无限循环 | 反复执行相同操作 | 任务永远无法完成,API额度刷光 |
| 上下文爆炸 | 每一步都追加到上下文 | Token消耗激增,关键信息丢失 |
| 缺乏全局视角 | 每一步只考虑当前信息 | 容易“走弯路”,路径低效 |
ReAct仍然适用的场景
尽管有上述缺陷,ReAct在以下场景中仍然是最佳选择:
- 动态环境:需要实时响应环境变化的任务(如实时故障修复、股票交易)
- 探索性任务:解决方案路径不明确,需要边试边找
- 对话式交互:用户与Agent多轮对话的场景
- 工具数量有限:工具集在3-5个以内
二、Plan-and-Execute:换个思路解决问题
Plan-and-Execute模式的哲学很简单:先把整个任务想明白,拆解成有序的步骤,然后逐步执行。
用户输入 → Planner(规划器)生成完整计划 → Executor(执行器)按步骤执行 → 输出结果两个核心角色
| 角色 | 职责 | 特点 |
|---|---|---|
| Planner(规划器) | 根据用户输入生成详细的任务规划和执行方案 | “想”,不调用工具 |
| Executor(执行器) | 依据规划内容执行具体任务 | “做”,按计划调用工具 |
完整流程
第一步:规划阶段(Planning)
Planner Agent接收用户目标,生成一个结构化的任务计划:
{"goal":"规划一次杭州三日游","steps":[{"id":1,"action":"查询杭州未来3天天气","tool":"get_weather"},{"id":2,"action":"根据天气推荐景点","tool":"recommend_attractions"},{"id":3,"action":"规划每日行程安排","tool":"plan_itinerary"},{"id":4,"action":"推荐酒店和餐饮","tool":"recommend_hotels"}]}第二步:执行阶段(Execution)
Executor按计划逐步执行,每一步可调用工具。如果步骤之间相互独立,还可以并行执行。
第三步:(可选)重规划阶段(Replanning)
如果某一步执行失败或环境发生变化,可以触发Replanner重新规划后续步骤。
Plan-and-Execute的优缺点
优点:
| 优点 | 说明 |
|---|---|
| 结构清晰 | 计划一目了然,便于人类理解和调试 |
| 避免死循环 | 执行路径由计划决定,不会无限循环 |
| 执行效率高 | 大模型只在规划和重规划时被调用 |
| 支持并行 | 独立步骤可并行执行,显著缩短总耗时 |
| 资源可控 | 可预先评估计算成本 |
缺点:
| 缺点 | 说明 |
|---|---|
| 计划可能失真 | 前期信息不足时,计划可能与真实环境脱节 |
| 动态调整弱 | 执行过程中的动态调整和容错能力较弱 |
| 灵活性不足 | 偏向静态工作流,难以应对突发变化 |
| 单轮对话限制 | 部分实现不支持基于历史上下文的多轮对话 |
Plan-and-Execute的适用场景
- 步骤繁多、逻辑依赖明确的长期复杂任务
- 任务可以预先分解为清晰的子任务
- 对实时性要求较低但对内容丰富度要求较高的任务(如深度分析报告、长篇网页生成)
- 批量数据处理等静态环境任务
三、ReAct vs Plan-and-Execute:全面对比
核心差异对比表
| 对比维度 | ReAct模式 | Plan-and-Execute模式 |
|---|---|---|
| 规划方式 | 动态生成,每轮迭代更新 | 一次性生成,执行前固定 |
| 执行方式 | 边想边做,循环迭代 | 先想后做,按计划执行 |
| 工具调用 | 按需调用,可能重复调用 | 按计划调用,顺序明确 |
| 状态管理 | 实时更新环境状态 | 仅在计划失败时更新状态 |
| 失败处理 | 通过循环自动修正 | 需显式设计重试/回滚机制 |
| 环境适应性 | 优秀(动态环境) | 一般(静态环境更高效) |
| 死循环风险 | 高 | 低 |
| 上下文增长 | 线性累积,易爆炸 | 可控 |
性能数据参考
在某自动化测试场景中,Plan-and-Execute模式相比ReAct:
- 任务完成率提升27%
- 工具调用次数减少42%
- 平均执行时间缩短35%
选型决策树
开始:我有一个任务要交给Agent │ ├─ 任务解决方案路径是否明确? │ ├─ 不明确,需要边探索边决策 → 【ReAct】 │ └─ 明确,可以预先拆解为子任务 → 继续 │ ├─ 任务是否需要实时响应环境变化? │ ├─ 是(如实时监控、故障修复)→ 【ReAct】 │ └─ 否 → 继续 │ ├─ 任务步骤是否繁多(>5步)且依赖关系明确? │ ├─ 是 → 【Plan-and-Execute】 │ └─ 否 → 继续 │ └─ 最安全的混合策略:Plan-and-Execute做全局规划 + ReAct做每步内的灵活执行💡核心观点:ReAct和Plan-and-Execute不是二选一的关系,而是两个不同粒度的策略。实战中最好的做法是混合使用——Plan-and-Execute做全局规划,ReAct做每步内的灵活执行。
四、Spring AI Alibaba Graph:底层工作流引擎
在深入代码之前,需要了解一个关键事实:Spring AI Alibaba的Agent Framework底层运行在Graph Runtime之上。
Graph是什么?
Graph是一个低级别的工作流和多智能体编排框架,能够帮助开发者实现复杂的应用程序编排。在底层,Spring AI Alibaba框架会将Agent编排为Graph,组成一个由节点串联而成的DAG(有向无环图)。
Graph的三大核心概念
| 概念 | 说明 |
|---|---|
| 状态(State) | 在Node与Edge之间传递的数据结构,是一个Map<String, Object> |
| 节点(Node) | 执行逻辑单元,接受State作为输入,执行操作后返回更新的State |
| 边(Edge) | 定义Node间的控制流,可为固定连接或条件分支 |
💡一句话总结:Node完成工作,Edge告诉下一步该做什么。
Graph的核心能力
- 流式输出(Streaming):将每个节点的运行情况实时发送到客户端
- 人机协同(Human In The Loop):允许对Agent运行过程中的工具调用进行评估、修改、批准
- 记忆管理(Memory & Context):处理短期记忆(会话内)和长期记忆(跨会话)
Agentic API vs Graph API
| Agentic API | Graph API | |
|---|---|---|
| 抽象层次 | 高层声明式API | 底层原子化API |
| 使用方式 | 使用预置的Agent模式 | 独立定义每个Node和Edge的逻辑 |
| 控制粒度 | 粗粒度 | 细粒度,完全控制 |
| 适用场景 | 大多数Agent应用开发 | 需要超高可靠性、大量自定义逻辑的场景 |
| 上手难度 | 低 | 中高 |
💡推荐策略:优先使用Agent Framework内置的Agent抽象(ReactAgent、SequentialAgent、ParallelAgent等)。只有当需要更灵活的编排、更直接的状态控制时,才考虑直接使用Graph API。
五、代码实战:对比两种方案
实战一:用ReAct模式实现“旅行规划Agent”
@Service@Slf4jpublicclassTravelReActAgentService{privatefinalChatModelchatModel;@Tool(description="查询指定城市的天气")publicStringgetWeather(Stringcity){returncity+"未来3天晴到多云,气温22-28°C";}@Tool(description="根据城市和天气推荐景点")publicStringrecommendAttractions(Stringcity,Stringweather){return"推荐景点:西湖、灵隐寺、宋城、西溪湿地";}publicvoidrunReActTravelAgent(){ReactAgentagent=ReactAgent.builder().name("react_travel_agent").model(chatModel).instruction(""" 你是一个旅行规划助手。用户会告诉你目的地和天数, 请按以下步骤完成任务: 1. 先查询天气 2. 根据天气推荐景点 3. 推荐酒店 4. 生成完整的行程计划 """).methodTools(this).build();Stringquestion="帮我规划杭州3日游";log.info("========== ReAct模式执行 ==========");longstart=System.currentTimeMillis();AssistantMessageresponse=agent.call(question);longend=System.currentTimeMillis();log.info("耗时:{}ms",end-start);log.info("回复:\n{}",response.getText());}}课堂观察点:
- 观察Agent的Thought→Action→Observation循环日志
- 记录工具调用次数和总耗时
- 注意Agent是否出现“重复查询”或“循环”现象
实战二:用SequentialAgent模拟Plan-and-Execute
@ConfigurationpublicclassPlanExecuteTravelConfig{// 1. Planner Agent - 只负责规划,不调用工具@BeanpublicReactAgentplannerAgent(ChatModelchatModel){returnReactAgent.builder().name("planner").model(chatModel).instruction(""" 你是一个旅行规划专家。请根据用户需求,生成一个详细的旅行计划。 计划必须包含以下部分: 1. 行程总览(天数、目的地) 2. 每日详细安排(上午、下午、晚上) 3. 住宿推荐 4. 餐饮推荐 注意:只生成计划文本,不要执行任何实际操作。 用户需求:{input} """).outputKey("travel_plan").build();}// 2. Executor Agent - 负责执行计划中的具体操作@BeanpublicReactAgentexecutorAgent(ChatModelchatModel){returnReactAgent.builder().name("executor").model(chatModel).instruction(""" 你是一个旅行执行助手。根据以下计划,执行具体的查询操作: {travel_plan} 请调用工具查询天气、景点等具体信息。 """).methodTools(newTravelTools()).outputKey("execution_result").build();}// 3. 组合为顺序Agent@BeanpublicSequentialAgentplanExecuteTravelAgent(ReactAgentplannerAgent,ReactAgentexecutorAgent){returnSequentialAgent.builder().name("plan_execute_travel_agent").subAgents(List.of(plannerAgent,executorAgent)).build();}}对比结果
| 对比维度 | ReAct模式 | Plan-and-Execute模式 |
|---|---|---|
| 执行路径 | 动态循环,路径不确定 | 固定顺序,路径清晰 |
| 工具调用次数 | 可能重复调用 | 按计划调用,无重复 |
| 总耗时 | 较长(多次模型调用) | 较短(2次模型调用+工具执行) |
| 回复质量 | 灵活但可能遗漏信息 | 结构完整,覆盖全面 |
| 可调试性 | 较难(路径不确定) | 容易(计划即文档) |
六、Spring AI Alibaba中的混合实践
如前所述,实战中最好的做法是混合使用:
用户需求 → Planner(全局规划)→ 步骤1(ReAct执行)→ 步骤2(ReAct执行)→ ... → 最终结果混合模式的优势:
- Planner提供全局视角,避免ReAct“走弯路”
- 每个步骤内的ReAct提供灵活性,应对局部变化
- 既有结构又有弹性
Spring AI Alibaba中的实现方式:
- 使用
SequentialAgent串联多个Agent - 每个子Agent可以是ReActAgent(带工具)
- 第一个Agent作为Planner(不带工具,只生成计划)
- 后续Agent作为Executor(带工具,执行计划中的各步骤)
这种模式在Spring AI Alibaba的OpenManus实现中已有体现——Planning Agent负责任务分解,多个Manus Agent组成链式可顺序执行的子工作流。
七、读后挑战
任务:分析你的业务场景,判断更适合哪种Agent架构模式
分析框架:
| 分析维度 | 你的答案 |
|---|---|
| 任务目标是什么? | |
| 任务步骤是否可以预先明确? | |
| 是否需要实时响应环境变化? | |
| 任务步骤是否繁多(>5步)? | |
| 子任务之间是否可以并行? | |
| 推荐使用哪种模式?为什么? |
验收标准:
- 完成了6个维度的场景分析
- 给出了明确的模式选择建议及理由
- 针对所选模式的缺陷提出了应对方案
- 如果能画出架构图(可选加分)
八、本日核心收获
- ReAct有三大缺陷:无限循环、上下文爆炸、缺乏全局视角
- Plan-and-Execute是另一种思路:先全局规划,再逐步执行
- 两者不是二选一,而是可以混合使用的策略
- Spring AI Alibaba Graph是底层工作流引擎,提供State、Node、Edge三大核心概念
- 选型决策:动态环境→ReAct;静态复杂任务→Plan-and-Execute;最稳妥→混合模式
📌本文要点回顾:ReAct是“一步一想”,Plan-and-Execute是“先全局规划,再逐步执行”。前者适合探索性任务,后者适合确定性任务。而实战中最好的做法是混合使用——Plan-and-Execute做全局规划,ReAct做每步内的灵活执行。理解这两种模式的区别和各自的适用场景,是Agent架构设计的关键一步。
有任何问题,欢迎在评论区留言交流!
作者:Java老兵搞AI,专注Java生态下的AI应用开发
如果觉得有用,「点赞」+「关注」支持一下吧 公众号搜索“#Java老兵搞AI”查看详细教程