Spring 之父的 Agent 框架 Embabel:把"规划"从 LLM 手里抢回来

简介: Embabel是由Spring之父Rod Johnson打造的JVM原生智能体框架,基于GOAP目标导向规划与OODA闭环,实现确定性、可解释的多步任务编排。它构建于Spring AI之上,以强类型领域模型(Java/Kotlin)驱动动态路径规划,让LLM专注内容生成,而非流程决策,专为高可靠企业AI场景而生。

如果你写 Java 写过十年以上,Rod Johnson 这个名字不需要介绍。2002 年那本 Expert One-on-One J2EE Design and Development 和他随后放出的 Spring,把整个企业级 Java 从 EJB 的泥潭里捞了出来。二十多年后,这位老哥又回来了,带着一个叫 Embabel 的 JVM 智能体框架。

他的原话很冲:"自从我创立 Spring 以来,从没这么确信一个新项目是必要的。"("Not since I founded the Spring Framework have I been so convinced that a new project is needed.")对于一个见惯了技术泡沫的人,这种表态本身就值得停下来看看。


一、Embabel 站在哪一层:它不是来替代 Spring AI 的

很多人第一反应是:"Spring AI 不是已经有了吗?"——这是对 Embabel 最大的误解。

Rod Johnson 自己的类比非常锋利:

层级 类比 代表
底层基础设施 Servlet API Spring AI / LangChain4j
高级应用框架 Spring MVC Embabel

Spring AI 解决的是"怎么连上 LLM、怎么管 Embedding、怎么接向量库"这类管道问题。Embabel 构建在 Spring AI 之上,解决的是"怎么让智能体在复杂业务流程里可靠、可解释、确定性地完成多步骤任务"这类编排(orchestration)问题。

二者是互补关系,不是竞争。Embabel 直接吃 Spring AI 的模型连接能力,所以 Spring AI 生态里的 @Tool、ChatClient 那套你都能继续用,只是"谁来编排动作顺序"这件事交给了 Embabel。

实战经验:评估一个新框架时,先问它"站在哪一层"。Embabel 的定位是 Spring AI 之上的编排层,这意味着它不重复造模型适配的轮子,而是把精力放在"流程可控"上。对企业里已经有一套 Spring 技术栈的团队,这条路径比直接用 Python 的 LangChain 平滑得多——你不用为了写 Agent 去学一门新语言。

它的基本定义(引自官方 README):Embabel (Em-BAY-bel) is a framework for authoring agentic flows on the JVM that seamlessly mix LLM-prompted interactions with code and domain models. Supports intelligent path finding towards goals. Written in Kotlin but offers a natural usage model from Java.

注意最后一句:用 Kotlin 写,但给 Java 开发者提供地道的用法。框架 95% 是 Kotlin 实现的,但你写业务时一个 kt 导入都看不到,Java 代码完全地道。这点对纯 Java 团队很友好。


二、核心概念:Action、Goal、Condition、Domain Model、Plan

Embabel 把"智能体工作流"建模成五个核心概念,全部来自官方 Key Concepts:

  • Action(动作):智能体执行的具体步骤。比如"写一个草稿""做事实核查""调用航班服务"。
  • Goal(目标):智能体想要达成的最终状态。
  • Condition(条件):执行某动作前的前置条件,或判断目标是否达成的依据。每完成一个动作,系统都会重新评估所有条件。
  • Domain Model(领域模型):支撑整个流程的对象模型,为 Action、Goal、Condition 提供数据基础。通常用 Java record 或 Kotlin data class 强类型化。
  • Plan(规划):为达成目标而串联起来的动作序列。规划由系统动态生成,不是程序员硬编码的。每执行完一个动作,系统重新规划一次。

最关键的一句话:应用开发者通常不需要直接操作这些概念,因为绝大多数条件都可以由代码中的数据流自动推导出来,系统据此推断前置条件(pre-condition)和后置条件(post-condition)。

换句话说,你只管写动作(@Action 方法)和领域对象,规划器自己从方法签名推断"谁依赖谁"。


三、杀手锏:GOAP 确定性规划,不让 LLM 决定"下一步"

这是 Embabel 最反直觉、也最硬核的设计。

主流 Agent 框架(无论 Python 还是 Java)的通病是:你声明一堆工具,让 LLM 自己决定下一步调哪个。Demo 很惊艳,一上生产就露馅——动作序列靠幻觉、流程不可测、出错无法解释。

Embabel 借用游戏 AI 里的 GOAP(Goal-Oriented Action Planning,目标导向动作规划)。GOAP 最早由 Jeff Orkin 在 *F.E.A.R.*(2005)里落地,后来被多款游戏用于角色决策。它的做法是:

  1. 你定义目标(Goal)和动作(Action),每个动作标注前置条件执行效果
  2. 一个确定性的、非 LLM 的规划器,从"当前世界状态"出发,用搜索算法(底层是 A* 一类的最短路径思想)算出通往目标的动作序列;
  3. 计划在执行前就可检视;
  4. LLM 只在每个动作"内部"做内容工作(写、总结、分类),永远不决定下一步跑谁。

Rod Johnson 的点评一针见血:"你给它相同的世界状态和目标,它每次都会生成同样的计划。而不像 LLM 一样,背后藏着几百亿参数,让你根本无法解释它为何那样决策。"

每完成一个动作,规划器重新评估世界状态、重新规划。官方明确说这就是一个 OODA 循环(观察-判断-决策-行动)

为什么"不让 LLM 做规划"在企业场景如此重要?因为企业系统(金融交易、订单处理、合规审计)容不得那 10% 的随机性。生成式 AI 天生是非确定性的——同样的提示每次结果可能不同。把"编排"这件事交给确定性算法,等于在随机的 AI 响应之上,重新给你套了一层可解释、可预测的工程外壳。这是 Embabel 区别于大多数框架的根本。

值得一提的是,Embabel 还开箱支持 Utility AI(效用 AI):同样的动作,但不靠严格的前置/后置条件,而是根据(可能动态的)效用评分来选择动作。也就是说 GOAP 不是唯一选项,框架把选择留给了你。


四、三种执行模式:确定性 ↔ 自主性

AgentPlatform 支持三种执行模式,覆盖不同场景的确定性需求(引自官方 README):

  • Focused(聚焦):用户代码直接调用某个特定 Agent,传入输入。适合事件驱动、代码驱动的流程。
  • Closed(封闭):用户意图被分类以选择 Agent;平台在它已知的 Agent 里挑,但只跑该 Agent 内定义的动作。
  • Open(开放):平台评估用户意图,从所有已知的目标与动作出发,动态组装一个定制 Agent 去达成目标。最强,但也最不确定——可能发现开发者没设想过的路径,甚至组合多个模型提供商的能力。

即便在 Open 模式下,平台执行的单个步骤仍然是你显式定义的;GoalChoiceApprover 接口还能进一步限制目标选择范围。


五、类型安全的领域模型:一个能跑的 Agent

Embabel 的全部优势,最终都落在"强类型"三个字上。你用 Java record 把 LLM 的输入输出结构钉死,编译期就能保证数据在动作间正确流动,没有魔法字符串 key,IDE 重构完全可用。

下面是一个完整的示例(采用 0.4.0+/1.x 的流式 API)。它定义了一个"写博客 + 审校"的智能体:你只写了两个动作和一个目标,动作之间的顺序由规划器推断,全程没有任何"第一步调 A、第二步调 B"的硬编码流程。

// 领域模型:用 Java record 强类型化 LLM 的结构化输出
public record UserInput(String content) {}
public record BlogDraft(String title, String content) {}
public record ReviewedPost(String title, String content, String feedback) {}

@Agent(description = "Write and review a blog post about a given topic")
public class BlogWriterAgent {

   @Action(description = "Write a first draft of the blog post")
   public BlogDraft writeDraft(UserInput userInput, AI ai) {
       return ai.withDefaultLlm()
           .withId("blog-post-draft-writer")
           .creating(BlogDraft.class)
           .fromPrompt("""
               Write a blog post about %s.
               Keep it practical and beginner-friendly.
               Use short sentences and plain language.
               Include code examples but keep them short and simple.
               Write the content in Markdown.
               """.formatted(userInput.content()))
;
   }

   @Action(description = "Review and improve the draft")
   @AchievesGoal(description = "A reviewed and polished blog post")
   public ReviewedPost reviewDraft(BlogDraft draft, AI ai) {
       return ai.withLlmByRole("reviewer")
           .withId("blog-post-reviewer")
           .creating(ReviewedPost.class)
           .fromPrompt("""
               Review and improve this blog post:
               Title: %s
               Content: %s
               Fix any technical errors.
               Tighten the writing.
               Provide a revised title, revised content,
               and a brief summary of the changes you made as feedback.
               """.formatted(draft.title(), draft.content()))
;
   }
}

几个要点拆一下:

  • @Agent 是 Spring 构造型注解,加了它类既是 Spring Bean(白捡组件扫描、依赖注入),又被注册进 Agent 运行时。
  • @Action 标记一个动作,方法签名里的 BlogDraft 输入就是规划器推断"依赖关系"的依据——因为 reviewDraft 需要 BlogDraft,规划器自然知道得先跑 writeDraft
  • @AchievesGoal 告诉规划器:完成这个动作 = 目标达成。
  • ai.withDefaultLlm() 用默认模型;ai.withLlmByRole("reviewer") 按角色换模型。这背后是 Embabel 的 LLM mixing 思想——不同任务用最合适的模型,成本与能力自己平衡。角色在 application.yaml 里配:

embabel:
 models:
   default-llm: gpt-4.1-mini
   llm:
     reviewer: gpt-4.1
spring:
 ai:
   openai:
     api-key: ${OPENAI_API_KEY}

踩坑提醒:Embabel 的 API 迭代很快。上面这套 ai.withDefaultLlm().creating(X.class).fromPrompt(...) 是 0.4.0 之后引入的流式写法;更早的版本(以及官方 main 分支 README 的部分示例)用的是 ai.withLlm(OpenAiModels.GPT_41).createObjectIfPossible(prompt, X.class) 风格。二者功能等价,方法名不同。写代码前先 mvn dependency:tree 确认你引入的 Embabel 版本,照着对应版本的官方文档写,别凭记忆拼方法名。

Maven 依赖(以 1.0.0 GA 为例,已发布到 Maven Central,无需额外仓库):

<dependency>
   <groupId>com.embabel.agent</groupId>
   <artifactId>embabel-agent-starter-shell</artifactId>
   <version>1.0.0</version>
</dependency>
<dependency>
   <groupId>org.springframework.ai</groupId>
   <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>

embabel-agent-starter 是基础平台(按 DI 注入),embabel-agent-starter-shell 额外带交互式控制台,适合调试。还有 embabel-agent-starter-ollamaembabel-agent-starter-dockermodels 等按需引入。注意 0.2.0 之前的快照版需要从 repo.embabel.com 加私有仓库,1.0.0 这种 GA 版直接在 Maven Central 上。


六、工具、MCP 与 A2A:把企业系统接进来

光会调 LLM 不够,Agent 得能碰真实系统。Embabel 在这块的设计很"企业向":

  • ToolGroup 抽象:把一组相关工具聚成一个组。自带的 SelfToolGroup 暴露 Agent 自身方法,McpToolGroup 桥接 MCP 服务器。你在 @Action 上声明 toolGroups = {CoreToolGroups.WEB} 就能让这个动作具备联网能力,不用关心协议细节。
  • MCP 原生支持:既能作为 MCP 客户端消费外部工具,也能通过 @EnableAgentMcpServer 把自己的 Agent 暴露成 MCP 服务(经 SSE 暴露),直接融进 MCP 生态。1.0 起 MCP 工具的错误会正确向上传播——旧版会"静默消失",这点对排障很重要。
  • A2A 协议:启用 a2a profile,Agent 就能原生与其他 A2A 服务通信,走向多智能体协作。
  • RAG:1.0 起 RAG API 从实验晋升为稳定,可以在 JVM 上构建向量支撑的 Agent。
  • ONNX 本地嵌入:不调外部 API 就能跑本地嵌入模型,对成本敏感或气隙(air-gapped)部署是关键能力。

模型覆盖上,因为底层是 Spring AI,所以 OpenAI、Anthropic、Bedrock、Google GenAI、Ollama、LM Studio 等主流提供商全支持,环境变量配 OPENAI_API_KEYANTHROPIC_API_KEY 即可。


七、测试:这是 Embabel 最被低估的优势

企业落地的另一半是"能不能测"。Embabel 在设计之初就把可测试性当一等公民,这点很多框架根本没考虑。

单元测试FakeOperationContextFakePromptRunner,不真正调 LLM 就能隔离测动作:

var context = FakeOperationContext.create();
context.expectResponse(new Story("Once upon a time..."));

var story = agent.craftStory(userInput, context.ai());

var prompt = context.getLlmInvocations().getFirst().getMessages().getFirst().getContent();
assertTrue(prompt.contains("knight"));

集成测试继承 EmbabelMockitoIntegrationTest,在完整启动的 Spring Boot 环境里跑整个工作流,还能验证超参数(比如温度):

whenCreateObject(prompt -> prompt.contains("Craft a short story"), Story.class)
   .thenReturn(new Story("AI will transform our world..."))
;

var invocation = AgentInvocation.create(agentPlatform, ReviewedStory.class);
var result = invocation.invoke(input);

verifyCreateObjectMatching(
   prompt -> prompt.contains("Craft a short story"),
   Story.class,
   llm -> llm.getLlm().getTemperature()
== 0.7
);

我见过一些"AI 功能上线即翻车"的项目,根因就是 LLM 调用没法测、没法断言。Embabel 这套 Fake 上下文 + 超参数校验,让你能在 CI 里锁住"提示词里到底有没有关键约束、温度配没配错"。对要上生产的团队,这一条比花哨的规划算法更值钱。


八、横向对比:Embabel vs Spring AI vs LangChain4j

维度 Embabel Spring AI LangChain4j
定位 高层编排框架(Spring MVC of AI) 底层抽象(Servlet API of AI) 社区驱动的 Agent 库
规划方式 GOAP/Utility AI 确定性规划器,非 LLM 通常手动编排或简单链 多靠 LLM 路由决策
类型安全 强类型领域模型,record 化 中等,偏底层 API Builder/注解,RAG 强
与 Spring 关系 构建于 Spring AI 之上 Spring 官方 Spring 集成一般
可解释性 高(白盒规划路径) 取决于你怎么写 中(常黑盒)
适合场景 复杂、多步骤、需可审计的企业流程 轻量接入 AI / 已有 Spring 项目 快速验证、RAG 场景

一句话总结:**Embabel 不替代 Spring AI 或 LangChain4j,它在它们上面加了一层"纪律"**。如果你只是想给现有 Spring 服务加个聊天接口,Spring AI 够了;如果你要的是一群模型按可控、可解释的流程协作完成多步骤任务,Embabel 才是那个更对位的工具。


九、我的实战判断

作为写了十几年 Java、又在做 LLM 方向的人,我对 Embabel 的判断是:方向对,但别急着上核心生产。

对的地方有三:第一,把规划从 LLM 手里拿回来,是这个赛道里少数真正解决"企业级可靠性"问题的设计,而不是又一个"让模型自己决定"的套壳;第二,强类型 + 领域模型,正好踩中 Java 开发者最舒服的那条肌肉记忆,重构、测试、可维护性都在;第三,构建在 Spring AI 之上,不重复造轮子,生态继承成本几乎为零。

要警惕的地方也得说清楚:项目仍在快速迭代,API 版本间有差异,生产环境落地前务必锁定版本、读完你那个版本的官方文档。另外,GOAP 的确定性带来可解释性,但也意味着"智能体自主发现全新路径"的能力弱于纯 LLM 路由的框架——这是取舍,不是缺陷。对金融、订单、合规这类"错不起"的场景,我反而觉得这是优点。

如果你是 Java/Spring 团队,想认真做 Agent,Embabel 值得现在就开始关注、用官方 Java 模板跑通第一个例子。它现在最像 2003 年的 Spring:雏形已立、理念清晰、生态刚起。至于能不能复刻 Spring 的传奇,时间会给出答案,但 Rod Johnson 这次的判断,至少在我这个老兵看来,站得住脚。


参考资料:Embabel 官方仓库 README(github.com/embabel/embabel-agent)、官方 Java 模板(github.com/embabel/java-agent-template)、Dan Vega《Embabel First Look》。版本信息以 1.0.0 GA(2026 年 7 月)为基准,API 示例已标注对应版本区间。

目录
相关文章
人工智能 缓存 前端开发
11530 55
人工智能 JavaScript 开发工具
4522 14
开发工具 Swift git
1817 4
人工智能 Java BI
1157 1
人工智能 JavaScript 测试技术
1965 2
Web App开发 人工智能 API
1028 1
缓存 JavaScript Shell
2008 3
人工智能 JavaScript 测试技术
991 4

热门文章

最新文章