文档
2、官方示例代码:https://github.com/embabel/embabel-agent
3、极客时间教程:https://time.geekbang.org/column/intro/101157301?tab=catalog
4、搜索工具:https://app.tavily.com/
是什么?
Embabel 是Spring 创始人 Rod Johnson 打造的 JVM 原生 Agent 框架,是声明式编程 + 自动规划的在JVM上运行的Agent框架。
目前AI Agent开发大部分都是python语言,而Agent开发是大势所趋,为了不被迫放弃熟悉的Java、Spring生态,于是有了Embabel。
优势
选 Embabel 的 3 个核心理由:
1、比Python更企业级,强类型加测试友好。Embabel一切基于Domain Model,不用Map或String,享受完整重构支持,单元测试友好,Mock容易,开发、维护成本最低,可复用当前已有的构建在JVM之上的业务系统,符合企业级开发规范。
2、比Spring AI更高层,不用硬编码流程。Spring AI就像Servlet API,只是封装了底层HTTP调用,你得自己写流程控制;而Embabel就像Spring MVC,用声明式的@Action和@Goal,框架自动规划。
3、比LangChain4j更快更省钱,规划默认不靠LLM(当然这个也支持)。LangChain4j用ReAct模式,每次决策都要问LLM,又慢又贵;而Embabel默认用GOAP算法做规划,这是游戏行业验证过的AI算法,不是每次都问LLM,规划用算法,执行按需调LLM,成本更低、速度更快。
Embabel 的核心执行模型是 OODA 循环(Observe-Orient-Decide-Act),每做完一件事情就重新观察、理解、决策。

OODA流程
OODA听起来高大上,结合下面的核心概念,其实是观察(黑板中数据变化)---->理解(如果某一个@Action方法的全部参数类型在黑板中能找到就执行,执行后的返回值写入黑板),通过黑板中存在的类型对象来决定Action执行顺序,在下面会详细描述。
核心概念

embabel核心概念
- Domain Model(领域模型)`:领域模型是用代码把“业务世界”表达出来。现实业务世界里有什么,代码里就对应有什么对象(类似于面向对象中的实体)。领域模型定义了 Agent 世界的统一契约:Actions 的输入输出、Conditions 的评估依据、Goals 的达成状态、乃至 Plan 的 Action 序列全都遵循这套契约
Action:Action 是 Agent 可以执行的单个步骤,基于 Domain Model 定义输入和输出。@Action:是 Embabel Agent 框架中用于标记方法为 Action 单元的核心注解。被标注的方法代表一个可被规划器(Planner)调度执行的步骤。Conditions(条件):控制流程的阀门,有pre和post属性
pre: 表示除了参数里的类型要求外的额外前置条件
Post:表示执行完成后需要满足的额外后置条件
pre方便理解指的是执行该方法前需要满足的条件(黑板中有这个类型的变量/变量值为true)。为入口门槛
post我觉得修改为 “执行完该方法后,会输出什么类型(对应数据类型写入黑板)/框架将对应的Condition标记为true(打开控制流的阀门)“更合适,为执行产出。
Goal(目标):Agent 想要达成的状态。Goal 是 Agent 试图达成的状态。达成 Goal 需要执行 Actions,并且需要满足前置 Conditions。具体来说,就是同时使用@AchievesGoal和@Action,表明达到了特定目标。Blackboard(黑板):Blackboard 是 Embabel 中的共享内存系统,维护整个 Agent 过程执行期间的状态。可以当作Agent 的工作台,所有需要的数据、中间结果都放在这里。每个 Action 执行时从这里取需要的输入,执行完后把输出放回去。
Blackboard 是一个内存中的线程安全的、不可变风格的共享上下文容器,通过 ConcurrentHashMap + synchronizedList/Set 实现并发安全,通过 hide() 而非删除来实现”软删除”,通过 Kotlin 类委托模式渗透到整个框架的每一层,并通过 BlackboardTools 向 LLM Agent 暴露结构化的数据访问能力。 Blackboard.kt(接口)→ InMemoryBlackboard.kt(实现)→ AbstractAgentProcess.kt(委托)→ BlackboardTools.kt(Agent 访问)。
常用注解
@Agent:@Action@Condition@AchievesGoal
每个 Agent 至少需要一个带有注解的操作 @AchievesGoal,来定义 Agent 工作的完成状态。
强类型领域建模
在 Embabel 中,领域模型就是由强类型类(如 Java Class / Kotlin Class)定义的一组业务对象,它们既承载数据又能封装行为,构成 Agent 理解世界的“统一契约”。
可控的工具暴露
类型依赖关系 = 自动规划的根基
Embabel是类型驱动规化,例如
@Action
public Blog fetchArticle(UserInput input) { /* ... */ } // 步骤1
@Action
public SocialMediaPost generatePost(Blog blog) { /* ... */ } // 步骤2(自动排在步骤1之后)
@Action
@AchievesGoal
public ReviewedPost reviewPost(SocialMediaPost post) { /* ... */ } // 步骤3(最终目标)
Embabel会自行推断执行该Action的前置条件: blackboard 中存在City,后置结果:执行后会产生 WeatherData。通过类型依赖编排Action执行流程。但是会存在一个问题,如果存在两个Action方法接受同样的参数类型,路径会如何规划?

BlackBoard数据流转全景图
与python agent对比

embabel与Python Agent对比
class是Java里通用的、灵活的类定义方式,而 record 是从Java 16开始正式引入的一种专门用来透明地承载不可变数据的特殊类。record 旨在大幅简化那些只需存储数据、不包含复杂业务逻辑的类的编写。
与Spring AI关系
Spring AI属于基础设施层,提供多种能力
- 多模型提供商支持:使用同一套API调用OpenAI、Anthropic、Amazon Bedrock、Google Vertex AI、Ollama等模型
- 通用抽象接口:就像JDBC统一了数据库访问,Spring AI统一了AI模型访问,使用同样的API访问
- 向量数据库集成:
- 工具/函数调用:LLM调用现有的函数和API
- Spring Boot自动配置:使用依赖和配置简单使用
Embabel是站在Spring AI之上的智能体编排框架
- 可解释性:当 Agent 做了一个决定,你需要知道它为什么这么做。如果只是让 LLM 自己决定,那就是黑盒。Embabel 提供了可解释的规划过程。
- 可发现性:你有 10 个工具,LLM 怎么知道该用哪个?Embabel 有一套机制来确保正确的工具被发现和使用。
- 模型混合:你不一定什么都用最新最强的模型。简单任务可以用本地模型(Ollama),省钱还保护隐私;复杂任务再用更强模型,Embabel 帮你做这种混合。
- 护栏注入:你不会想让 Agent 想做什么就做什么——比如不能让它随便删除数据。Embabel 让你在流程的任何点都能加上安全限制。
- 流程执行管理:Agent 执行失败了怎么办?重试?回滚?Embabel 提供了这种弹性。
- 大规模可组合:未来不会是单个 Agent,而是多个 Agent 协作——就像现在的微服务一样。Embabel 为这种可组合性设计。
- 与敏感系统的安全集成:你的数据库里有客户数据。你真的想让 LLM 直接写数据库吗?Embabel 让你安全地连接这些敏感系统。

层级示意图
@Service
public class SpringAIWeatherService {
private final ChatClient chatClient;
private final WeatherApiClient weatherApi;
public SpringAIWeatherService(ChatClient chatClient,
WeatherApiClient weatherApi) {
this.chatClient = chatClient;
this.weatherApi = weatherApi;
}
public String getWeatherResponse(String city) {
// 步骤1:先调用天气API
WeatherData weather = weatherApi.fetch(city);
// 步骤2:构建提示词
String prompt = String.format(
"用自然语言描述天气: %s", weather);
// 步骤3:调用LLM
return chatClient.prompt(prompt).call().content();
}
}@Agent(description = "天气查询智能助手")
public class WeatherAgent {
@Autowired
private WeatherApiClient weatherApi;
@Action
public WeatherData fetchWeather(String city) {
return weatherApi.fetch(city);
}
@AchievesGoal(description = "查询天气成功")
@Action
public String generateReport(WeatherData weather,
OperationContext context) {
String prompt = String.format("描述天气: %s", weather);
return context.ai().withDefaultLlm().createText(prompt);
}
}一些疑问
Embabel如何接入现有微服务项目?
答:直接在Action中调用service方法
黑板上一种类型只能保存最新的数据,如果确实需要存储一种类型的多种数据,该如何存储?
在解析LLM返回的数据转换为Java强类型时,如何确保返回的字段在类中存在?
例如extractBorrowBookRequest方法中在prompt中写死了LLM需要返回的字段userId、userName、queryContent,如果 BorrowBookRequest结构有变动,prompt也要随着变动,有什么其他方法?
public record User(
String id,
String name,
List<String> borrowedBookIds
) {
}
public record BorrowBookRequest(
User user,
String queryContent
) {
}
@Action(
description = "Parse the user's borrowing query into a borrow request.",
post = {USER_HAS_QUERY},
cost = 0.7
)
public BorrowBookRequest extractBorrowBookRequest(
UserInput userInput,
OperationContext operationContext
) {
ParsedBorrowQuery parsedQuery = operationContext.ai()
.withLlm(LlmOptions.fromCriteria(ModelSelectionCriteria.getAuto()))
.createObjectIfPossible(
"""
请解析用户的借书请求。
需要返回:
- userId:如果用户明确说了用户 ID,则提取;否则为空
- userName:如果用户明确说了姓名,则提取;否则为空
- queryContent:用户想借的书名、分类、作者或其他查询关键词;如果没有借书意图则为空
用户输入:%s
""".formatted(userInput.getContent()),
ParsedBorrowQuery.class
);
String queryContent = parsedQuery == null ? userInput.getContent() : parsedQuery.queryContent();
User user = resolveUser(parsedQuery);
return new BorrowBookRequest(user, normalize(queryContent));
}