1. 从“提示词编排”到“目标驱动”:Java企业AI Agent的路线分叉口
过去一年,我接触了不少做企业级系统的Java团队,聊到AI Agent时,几乎所有人第一反应都是同一个问题:“我们是不是得把整套技术栈换成Python?”这个焦虑非常真实——LangChain、AutoGPT、各种Agent框架的Demo清一色是Python写的,教程、社区、招聘JD也都在往Python倾斜。但真正把AI Agent往生产系统里落的时候,很多Java团队会发现一个尴尬的现实:业务逻辑、事务、权限、审计、监控全在Java这边,Agent却要另起一个Python服务,中间靠HTTP或消息队列来回传,链路一长,稳定性和可观测性都成了问题。
标题里提到的两个信号值得拆开看。一个是“Spring之父出手”,指的是Spring生态的缔造者Rod Johnson在AI Agent方向上的动作,他主导的项目把Agent的构建方式从“写提示词”转向了“定义目标与行为”,并且是原生Java技术栈。另一个是“GOAP算法替代提示词编排”,GOAP是Goal-Oriented Action Planning的缩写,中文一般叫“面向目标的行为规划”,它原本是游戏AI里非常成熟的一套决策算法——NPC不靠脚本硬编码,而是给定一个目标状态,自己搜索出一串动作来达成。把GOAP搬到AI Agent上,意味着Agent的行为不再是一长串if-else式的提示词拼接,而是由“当前状态、目标状态、可用动作、动作前置条件与效果”这套结构化描述驱动,规划器自动算出执行路径。
这两件事叠加起来,给Java企业指出的其实是一条不卷Python的路:用Java原生生态构建Agent,用GOAP这类经典规划算法替代脆弱的提示词编排,让Agent的行为可测试、可审计、可回滚。这篇文章我想把这条路线讲透——GOAP到底怎么工作、为什么它比提示词编排更适合企业场景、Java侧怎么落地、以及我在实际搭建过程中踩过的那些坑。适合有Java后端基础、正在评估AI Agent技术选型的同学,也适合对Agent架构感兴趣但不想被Python生态绑死的开发者。
2. GOAP到底解决了提示词编排的哪些死穴
2.1 提示词编排的本质是“把决策逻辑写进自然语言”
先说说大多数Agent框架现在的做法。所谓“提示词编排”,典型形态是这样:你写一段系统提示词,告诉模型“你是一个客服助手,当用户询问退款时,先确认订单号,再查询订单状态,如果已发货则引导退货流程,如果未发货则直接退款……”然后靠模型自己去理解、分支、调用工具。复杂一点的会用上ReAct模式,让模型在“思考-行动-观察”的循环里自己决定下一步。
这套东西做Demo很惊艳,但放到企业生产环境里,问题会一个接一个冒出来。第一个死穴是不可测试。你想验证“用户说‘我要退钱’时Agent会不会走退款流程”,只能反复跑模型,靠概率判断,没法写一个断言说“给定这个输入,必须产生这个动作序列”。第二个死穴是不可审计。模型为什么在第三步选择了查物流而不是直接退款?你拿不到一个确定的决策依据,只有一段自然语言推理,合规部门看了直摇头。第三个死穴是脆弱。提示词里改一个词、加一句话,可能整个行为链路就变了,而且这种变化是隐性的,回归测试根本覆盖不住。
我见过一个团队,他们的Agent在测试环境跑了两百个用例都正常,上线后遇到一个用户输入里带了特殊符号,模型的理解偏了,直接跳过了身份校验步骤去调退款接口。这种事在提示词编排体系里几乎无法从根上防住,因为决策逻辑本身就是概率性的。
2.2 GOAP把“怎么做”变成可搜索的规划问题
GOAP的思路完全不同。它不要求你告诉Agent“第一步做什么、第二步做什么”,而是让你定义三样东西:
- 世界状态(World State):一组键值对,描述当前世界的客观情况,比如
orderStatus=shipped、userVerified=true、refundIssued=false。 - 目标(Goal):你希望达成的状态,比如
refundIssued=true。 - 动作(Action):每个动作有前置条件(Precondition)和效果(Effect)。比如“发起退款”这个动作,前置条件是
orderStatus=notShipped AND userVerified=true,效果是refundIssued=true。
定义完这些,规划器(Planner)会用搜索算法(通常是A*或Dijkstra的变体)从当前状态出发,找出一条能到达目标状态的动作序列。这条序列就是Agent的执行计划。整个过程是确定性的、可枚举的、可验证的——给定相同的状态和目标,规划器每次都会给出相同的计划(除非有多个等价路径,那也可以按成本排序)。
这就把“决策”从模型的脑子里搬到了一个显式的、可检查的规划器里。模型在这个体系里的角色变了:它不再负责“决定做什么”,而是负责“理解用户意图、把自然语言映射成状态和目标”,以及“执行具体动作时生成内容”。决策权交给了算法,模型只做它擅长的事——语言理解和生成。
2.3 一个对比表格看清两者差异
| 维度 | 提示词编排 | GOAP规划 |
|---|---|---|
| 决策依据 | 模型对自然语言的概率性理解 | 显式状态与动作定义的确定性搜索 |
| 可测试性 | 难,只能端到端跑,结果不稳定 | 强,规划器可单元测试,给定状态必得计划 |
| 可审计性 | 弱,只有自然语言推理链 | 强,计划是一串明确动作,每步有前置条件记录 |
| 修改影响 | 隐性、全局,改一词可能崩全链 | 局部、显式,改一个动作只影响相关路径 |
| 模型依赖 | 高,模型能力直接决定行为质量 | 低,模型只做意图识别和内容生成 |
| 适合场景 | 开放式、探索型任务 | 流程明确、合规要求高的企业任务 |
这张表不是要否定提示词编排,开放式创意任务里它依然好用。但企业里大量的Agent场景——订单处理、工单流转、审批、风控——本质上是流程明确的,用GOAP这种结构化规划反而更稳。
3. Java原生Agent的技术底座:为什么不用换语言
3.1 Spring生态对Agent的原生支持意味着什么
“Spring之父出手”这件事的真正价值,不在于多了一个框架,而在于它把Agent的构建方式拉回了Java工程师熟悉的世界。你想想,一个Java团队要落地Agent,最怕的是什么?是引入一套完全陌生的编程范式、一套独立的运行时、一套跟现有Spring容器割裂的依赖管理。如果Agent框架本身就是Spring生态的一部分,那意味着:
- Agent的各个组件(意图识别器、规划器、动作执行器、记忆存储)都可以是Spring Bean,用依赖注入管理,用AOP做横切关注点。
- 现有的
@Transactional、@Cacheable、@Async这些注解可以直接作用在动作执行上,事务和并发控制不用重造。 - 监控可以直接接Micrometer,日志接现有的Logback配置,链路追踪接现有的方案,不需要为Agent单独搭一套可观测性。
- 配置管理走
application.yml,环境隔离、灰度发布这些运维能力全部复用。
我实际搭过一个原型,把GOAP规划器做成一个@Service,动作执行器做成一组实现了统一接口的Bean,意图识别器封装成一个调用大模型的组件。整个Agent的启动、装配、生命周期管理跟一个普通Spring Boot应用没有任何区别。团队里没有一个人写过Python,两天就把骨架跑通了。
3.2 动作执行器如何复用企业现有的Service层
这是Java路线最爽的一点。企业系统里,那些“动作”其实早就存在了——查询订单、校验用户、发起退款、发送通知,这些方法在Service层里躺了好几年,经过了充分测试。GOAP体系里的“动作”,本质上就是对这些已有方法的封装。
你只需要给每个动作定义一个描述:它的前置条件是什么、执行后世界状态怎么变、调用哪个Service方法。比如:
public class RefundAction implements AgentAction { @Override public Map<String, Object> preconditions() { return Map.of("orderStatus", "notShipped", "userVerified", true); } @Override public Map<String, Object> effects() { return Map.of("refundIssued", true); } @Override public void execute(WorldState state, ActionContext ctx) { orderService.refund(state.get("orderId")); } }规划器只关心preconditions和effects,执行时才真正调Service。这样一来,Agent的行为直接建立在经过生产验证的业务代码之上,而不是让模型去“自由发挥”调用一堆没测过的工具。安全边界清晰得多。
3.3 状态管理:把“世界”建模成可持久化的对象
GOAP的核心是状态。在Java里,世界状态可以就是一个普通的POJO或者Map,但企业场景下我建议做成可持久化的实体。原因有两个:一是Agent执行可能是长流程,中间要暂停、恢复,状态必须能存下来;二是审计需要,每一步执行前后的状态快照都要留痕。
我的做法是把WorldState设计成一个带版本号的实体,每次动作执行后生成一个新版本,存进数据库。这样任何一个历史时刻的状态都能回溯,出了问题能精确复现。这个思路跟事件溯源(Event Sourcing)很像,实际上GOAP的执行日志天然就是一条事件流。
4. 从零搭一个GOAP Agent:核心组件与实操步骤
4.1 规划器的选型与A*搜索的实现要点
规划器是GOAP的心脏。它的输入是当前状态、目标状态、动作集合,输出是一个动作序列。最常用的算法是A*,启发函数用“当前状态与目标状态的差异数量”就行——差异越少,离目标越近。
实现时有几个关键点。第一,状态比较要高效。世界状态是键值对,判断两个状态是否相等、计算差异,用不可变的Map加哈希比较最稳。第二,动作的适用性判断要快。每次扩展节点时,要遍历所有动作,检查前置条件是否被当前状态满足,这个检查要尽量轻量。第三,防止无限循环。有些动作的效果可能互相抵消,导致规划器在两个状态间来回跳,必须用已访问状态集合去重。
我踩过的一个坑是:动作的前置条件里如果包含“不等于某值”这种否定条件,搜索空间会变大,因为状态空间里那个键可能有无数种取值。解决办法是尽量把状态设计成枚举或有限集合,避免开放取值。
public List<AgentAction> plan(WorldState current, WorldState goal, List<AgentAction> actions) { PriorityQueue<PlanNode> open = new PriorityQueue<>(Comparator.comparingInt(n -> n.cost + heuristic(n.state, goal))); Set<WorldState> visited = new HashSet<>(); open.add(new PlanNode(current, List.of(), 0)); while (!open.isEmpty()) { PlanNode node = open.poll(); if (node.state.satisfies(goal)) return node.actions; if (!visited.add(node.state)) continue; for (AgentAction action : actions) { if (node.state.satisfies(action.preconditions())) { WorldState next = node.state.apply(action.effects()); open.add(new PlanNode(next, append(node.actions, action), node.cost + action.cost())); } } } return List.of(); // 无解 }这段代码不长,但它是整个Agent确定性的来源。给定相同的输入,它永远返回相同的计划。
4.2 意图识别层:让大模型只做“翻译”不做“决策”
GOAP规划器需要的是结构化的状态和目标,但用户输入的是自然语言。这中间的翻译工作交给大模型,但要注意——只让它翻译,不让它决策。
具体做法是设计一个严格的输出格式,让模型把用户的话转成“状态更新”和“目标声明”。比如用户说“我上周买的东西还没到,我要退款”,模型应该输出类似:
{ "stateUpdates": {"orderId": "从上下文提取", "userIntent": "refund"}, "goal": {"refundIssued": true} }然后系统根据orderId去查真实订单状态,补全orderStatus等字段,再交给规划器。这里的关键是模型不参与任何分支判断,它只负责抽取信息。哪怕模型抽错了,规划器也会因为前置条件不满足而拒绝执行某些动作,不会造成越权操作。这比让模型直接决定“要不要退款”安全得多。
4.3 动作注册与前置条件校验的工程细节
动作多了以后,注册和校验要工程化。我建议用一个注册中心统一管理所有动作,启动时扫描所有实现了AgentAction接口的Bean,按名称或标签索引。规划器需要动作集合时直接从注册中心拿。
前置条件校验分两层。第一层是规划时的静态校验,只比对状态键值,不产生副作用。第二层是执行时的动态校验,因为从规划到执行之间,真实世界可能变了——比如规划时订单还没发货,执行时已经发货了。所以每个动作在执行前必须重新校验一次前置条件,不满足就中止并触发重新规划。这个“执行前二次校验”是我踩坑之后加的,之前有一次规划完隔了几秒执行,中间订单状态变了,结果执行了不该执行的动作。
4.4 执行引擎与回滚机制的设计
执行引擎按计划顺序执行动作,每执行一个就更新世界状态并持久化。如果某个动作执行失败,要有回滚策略。GOAP本身不提供回滚,但企业场景必须有。
我的做法是给每个动作定义一个补偿动作(Compensating Action),执行失败时反向执行已完成的动作的补偿。这跟Saga模式是一个思路。比如“发起退款”的补偿是“撤销退款”,“扣减库存”的补偿是“恢复库存”。补偿动作不参与规划,只在失败时调用。
5. 实测中暴露的问题:GOAP不是银弹
5.1 状态空间爆炸与动作粒度控制
GOAP最容易被低估的问题是状态空间。如果你的世界状态有20个键,每个键有5种取值,理论状态空间是5的20次方,A*搜索会直接卡死。我第一个原型就犯了这个错,把用户画像、历史行为、实时库存全塞进状态里,规划一次要好几秒。
解决办法是严格控制状态维度。只把跟当前目标相关的状态放进规划空间,其他信息作为动作执行时的上下文参数传入,不参与搜索。比如用户的历史购买记录,规划器不需要知道,执行“推荐商品”动作时再查就行。一般来说,参与规划的状态键控制在8个以内,每个键的取值控制在有限枚举,搜索就很快。
另一个是动作粒度。动作太细,规划步数多,搜索慢;动作太粗,灵活性差。我的经验是:一个动作对应一个业务上不可再分的原子操作,且这个操作有明确的成功/失败语义。比如“校验用户身份”是一个动作,“查询订单”是一个动作,但“处理退款”应该拆成“校验”“发起退款”“通知用户”三个动作,因为中间任何一步都可能失败需要单独处理。
5.2 规划失败时的降级策略
规划器返回空计划,意味着当前状态下无法达成目标。这在企业场景里很常见——用户要退款但订单已发货,GOAP找不到直接退款的动作序列。这时候不能简单报错,要有降级策略。
我的处理是分三级:第一级,放宽目标,尝试达成一个“次优目标”,比如从“退款”降级为“引导退货流程”;第二级,如果次优目标也达不成,触发人工介入,把当前状态和失败原因推给人工坐席;第三级,给用户一个明确的、可解释的回复,说明为什么当前无法处理。关键是每一级降级都要有明确的触发条件和记录,不能悄悄失败。
5.3 大模型翻译错误如何不污染规划结果
模型把用户意图翻译错,是必然会发生的事。比如用户说“我不想等了”,模型可能翻译成goal: cancelOrder,但用户其实只是想催发货。如果这个错误目标直接进规划器,Agent就会去执行取消订单的动作。
防这一手的办法是目标白名单加确认机制。系统只接受预定义的目标集合,模型输出的目标如果不在白名单里,直接拒绝。对于高风险目标(如取消订单、退款),在执行计划前插入一个确认步骤,让用户明确确认。这个确认步骤本身也是一个动作,前置条件是“目标已声明”,效果是“目标已确认”,规划器只有在确认后才继续后续动作。这样即使模型翻译错了,用户也有机会拦住。
6. 这条Java路线的边界与适用判断
6.1 什么场景该用GOAP,什么场景别硬上
GOAP适合的是流程相对确定、合规要求高、需要可审计的场景。典型如:订单售后处理、工单自动流转、审批链自动化、风控决策、运维故障自愈。这些场景的共同点是:目标明确、动作可枚举、状态可建模、每一步都需要留痕。
不适合的场景也很明确:开放式创意生成、探索型研究任务、需要大量常识推理的对话。这些场景里,目标本身就不清晰,动作空间无法枚举,硬套GOAP只会把简单问题复杂化。我见过有人想用GOAP做“智能写作助手”,结果状态和动作根本定义不出来,最后又退回提示词编排。选型要看任务本质,不是看哪个技术新。
6.2 团队能力匹配:Java团队的真实优势
对Java团队来说,这条路线最大的优势是能力复用。你的团队已经会写Spring Bean、会配事务、会做单元测试、会搭监控,这些能力在GOAP Agent里全部用得上。规划器可以写单元测试,动作可以写集成测试,状态流转可以画状态图,这些都是Java工程师的日常。
反过来说,如果硬转Python,团队要重新学一套生态、一套测试框架、一套部署方式,学习成本不说,两套技术栈的维护成本才是长期的痛。我见过一个团队,Java主系统加Python Agent服务,结果Agent服务成了没人愿意维护的“飞地”,出了问题两边互相甩锅。技术栈统一的价值,在长期维护里会越来越明显。
6.3 混合架构:Java做规划,模型做生成
最后说一个我觉得最务实的架构:Java侧负责规划、状态管理、动作执行、审计,模型侧只负责意图识别和内容生成。两者通过清晰的接口交互,模型是一个可替换的组件,今天用这个模型,明天换那个模型,规划逻辑完全不受影响。
这种架构下,模型的能力波动被隔离在“翻译”和“生成”两个环节,核心的决策链路是确定性的Java代码。模型升级了,翻译更准了,Agent整体变好;模型出问题了,最坏情况是翻译错,但规划器的前置条件校验和确认机制能兜住。这是我认为企业级AI Agent最稳的形态——把不确定性关进笼子,让确定性掌控流程。
我在实际搭建中最大的体会是:AI Agent的“智能”不一定来自模型本身,也可以来自结构化的规划。GOAP这套几十年前在游戏里验证过的算法,放到企业Agent场景里意外地合适,因为它解决的正是企业最在意的问题——可预测、可审计、可控制。Java团队不用羡慕Python生态,把已有的工程能力用好,配上合适的规划算法,一样能做出稳当的Agent。后续如果要做多Agent协作,GOAP的状态共享和规划分层还能继续扩展,这条路值得往下走。