Embabel 1.0 Agent Framework 完全指南:从入门到生产级实践
一、介绍
1.1 什么是 Embabel?
Embabel(读作 Em-BAY-bel,/ɛmˈbeɪbəl/)是一个专为 JVM 生态设计的 AI 智能体(Agent)框架,由 Spring 框架创始人 Rod Johnson 主导开发。2026 年 4 月,Rod Johnson 在 Microsoft JDConf 开发者大会上首次发布 Embabel。2026 年 7 月 20 日,Embabel 正式发布了 1.0.0 GA 版本,采用 Apache 2.0 协议开源。
简单来说,Embabel 是一个用于在 JVM 上编写智能体流程的框架,能够将 LLM 提示交互与代码和领域模型无缝混合。它的目标是弥合生成式 AI 的"实验性"与企业级应用的"生产级可靠性"之间的鸿沟。
1.2 为什么需要 Embabel?
在 Embabel 出现之前,Java 开发者构建 AI Agent 面临尴尬的选择:要么采用 Python 框架(如 LangChain、CrewAI)维护多语言栈,要么在 Spring AI 或 LangChain4j 之上自行构建编排逻辑。
Rod Johnson 指出:"你实际上是在用一个'九成时间有效、一成时间无效'的东西去构建系统,从效果上看,它就等于'完全没用'。"生成式 AI 是非确定性的------同样的提示每次生成结果可能都不一样。这对个人聊天无所谓,但对金融交易、订单处理、合规审计来说,不可预测就意味着不可用。
Embabel 正是在这一背景下诞生的------它要解决的是 Prompt Engineering 解决不了的问题:多步骤任务的编排、状态管理、可测试性和可解释性。
1.3 核心定位:Servlet 与 Spring MVC 的类比
Embabel 与 Spring AI 处于不同的抽象层次。项目 README 中有一个精妙的类比:
Spring AI 存在于 Servlet API 的层面,而 Embabel 更像是 Spring MVC。
原始 Servlet 能用,但每个应用都在重复解决同样的问题:解析请求参数、分发到正确的处理器、对象与 HTTP 之间的转换。Spring MVC 没有取代 Servlet,而是坐落在它之上,让开发者写一个带类型的方法签名,而不是手写解析 HttpServletRequest。
Embabel 对 Agent 做了同样的赌注:
- Spring AI 提供与模型对话的管道(相当于 Servlet)
- Embabel 提供声明层------开发者声明"这是我的目标和可用的带类型的 Action",让框架来处理编排顺序(相当于 Spring MVC)
简单说:Spring AI 是"零件箱",Embabel 是"装配图纸 + 流水线" 。
1.4 技术栈与版本信息
- 语言:使用 Kotlin 编写,但提供地道的 Java 使用体验
- 基础 :构建于 Spring AI 1.1.7 和 Spring Boot 3.5.x 之上
- JDK 基线 :Java 21+
- 许可证:Apache 2.0 开源协议
- 发布时间:1.0.0 GA 于 2026 年 7 月 20 日
二、核心概念
2.1 五大核心概念
Embabel 使用五个核心概念来建模智能体流程:
1. Agent(智能体) Agent 是一个自包含的组件,将领域逻辑、AI 能力和工具使用捆绑在一起,代表用户实现特定目标。Agent 通过 @Agent 注解标记。
2. Goal(目标) Agent 试图实现的结果。Goals 由方法上的 @AchievesGoal 注解标记。
3. Action(行动) Agent 执行的具体步骤,在带 @Action 注解的方法中定义。有些 Action 使用 LLM,有些是确定性步骤(如数据库查询、评分、验证)。
4. Condition(条件) 在执行 Action 之前或判断目标是否达成时评估的条件。每个 Action 执行后都会重新评估条件。
5. Domain Model(领域模型) 支撑整个流程的对象,为 Actions、Goals 和 Conditions 提供信息。
2.2 规划(Plan)与 OODA 循环
Embabel 最独特的地方在于其动态规划能力:
- Plan 是达成目标的一系列 Action 序列
- Plan 由系统动态制定,而非程序员硬编码
- 每个 Action 完成后系统会重新规划(Replan)
- 这形成了一个 OODA 循环(Observe-Orient-Decide-Act)
2.3 强类型与面向对象
Embabel 的一个核心原则是强类型和面向对象:
- 所有 LLM 交互都是强类型的
- 提供编译时检查、重构支持和 IDE 辅助
- 告别"魔法 Map",享受完整的重构支持
2.4 三种执行模式
Embabel 支持三种执行模式:
- 专注模式:特定智能体调用
- 封闭模式:用户意图分类后由特定智能体处理
- 开放模式:平台评估用户意图,使用所有可用资源实现目标
三、1.0.0 版本新特性
1.0.0 是 Embabel 的稳定性里程碑。主要变化包括:
| 特性 | 说明 |
|---|---|
| RAG API 稳定化 | 从实验性提升为稳定 API,可在 JVM 上构建向量支持的 Agent 而无需担心接口变动 |
| MCP 服务器健康检查 | 通过 Spring Boot Actuator 暴露 MCP 集成健康状态 |
| A2A 协议支持 | 启用 a2a Spring profile 后,Agent 可与其他支持 A2A 的服务原生通信 |
| ONNX 本地嵌入支持 | 无需外部 API 调用即可本地运行嵌入模型,对成本敏感或离线部署至关重要 |
| 扩展模型覆盖 | 新增 Z.ai GLM,更新 DeepSeek 模型名称 |
| MCP 工具错误传播 | 1.0 之前工具错误可能静默消失,现在 MCP 客户端可正确看到并处理它们 |
| 通用 Media 和 Document 支持 | 引入通用媒体和文档支持 |
重要提示 :1.0.0 版本移除了已废弃的方法,升级时请注意 API 变更。
四、用法
4.1 环境要求
- Java 21+
- Spring Boot 3.5.x(基于 Spring AI 1.1.7)
- Maven 3.9+(可选,项目包含 Maven Wrapper)
- API Key:OpenAI 或 Anthropic
4.2 项目初始化
方式一:使用 GitHub Template
访问 java-agent-template 点击 "Use this template" 按钮。
方式二:使用快速创建工具
bash
uvx --from git+https://github.com/embabel/project-creator.git project-creator
选择 Java 或 Kotlin,指定项目名和包名。
4.3 Maven 依赖配置(1.0.0)
在 pom.xml 中添加依赖:
xml
<properties>
<embabel-agent.version>1.0.0</embabel-agent.version>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-starter</artifactId>
<version>${embabel-agent.version}</version>
</dependency>
<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-starter-shell</artifactId>
<version>${embabel-agent.version}</version>
</dependency>
<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-starter-openai</artifactId>
<version>${embabel-agent.version}</version>
</dependency>
<dependency>
<groupId>com.embabel.agent</groupId>
<artifactId>embabel-agent-test</artifactId>
<version>${embabel-agent.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
4.4 配置 LLM
在 application.yml 中配置:
yaml
embabel:
models:
default-llm: claude-opus-4-20250514
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
anthropic:
api-key: ${ANTHROPIC_API_KEY}
通过环境变量传入 API Key:
bash
export OPENAI_API_KEY="your_openai_key"
export ANTHROPIC_API_KEY="your_anthropic_key"
4.5 编写第一个 Agent(完整代码)
以下是一个完整的 Agent 示例------星座新闻发现器(Star News Finder):
领域模型类:
java
package com.example.agent.model;
import com.embabel.agent.api.annotation.JsonClassDescription;
import com.embabel.agent.api.annotation.JsonPropertyDescription;
@JsonClassDescription("A person with name and star sign")
public class StarPerson {
@JsonPropertyDescription("The person's full name")
private String name;
@JsonPropertyDescription("The person's zodiac star sign, e.g., Aries, Taurus, Gemini")
private String sign;
// 构造函数、getters、setters
public StarPerson() {}
public StarPerson(String name, String sign) {
this.name = name;
this.sign = sign;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getSign() { return sign; }
public void setSign(String sign) { this.sign = sign; }
}
@JsonClassDescription("A horoscope reading for a specific star sign")
public class Horoscope {
private String sign;
private String reading;
private String date;
// 构造函数、getters、setters
public Horoscope() {}
public Horoscope(String sign, String reading, String date) {
this.sign = sign;
this.reading = reading;
this.date = date;
}
// getters and setters omitted for brevity
}
@JsonClassDescription("A news story relevant to a person's interests")
public class NewsStory {
private String title;
private String summary;
private String url;
private String source;
// 构造函数、getters、setters
}
@JsonClassDescription("A complete writeup combining horoscope and news")
public class Writeup {
private String title;
private String content;
private List<String> tags;
// 构造函数、getters、setters
}
@JsonClassDescription("User input containing the request")
public class UserInput {
private String text;
public UserInput() {}
public UserInput(String text) { this.text = text; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
}
HoroscopeService(模拟服务):
java
package com.example.agent.service;
import com.example.agent.model.Horoscope;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class HoroscopeService {
private static final Map<String, String> HOROSCOPES = Map.of(
"Aries", "Today is a day for bold decisions. Your energy is high.",
"Taurus", "Focus on stability and comfort. Good things come to those who wait.",
"Gemini", "Communication is key today. Reach out to old friends.",
"Cancer", "Trust your intuition. Family matters may need attention.",
"Leo", "Your charisma is shining. Take the lead on a new project.",
"Virgo", "Attention to detail will pay off. Organize your workspace.",
"Libra", "Balance is everything. Seek harmony in your relationships.",
"Scorpio", "Transformation is in the air. Embrace change.",
"Sagittarius", "Adventure calls! Consider a new experience.",
"Capricorn", "Discipline leads to success. Stay focused on your goals.",
"Aquarius", "Innovation is your superpower. Think outside the box.",
"Pisces", "Your creativity is flowing. Express yourself artistically."
);
public Horoscope getHoroscope(String sign) {
String reading = HOROSCOPES.getOrDefault(sign, "Today is a good day.");
return new Horoscope(sign, reading, java.time.LocalDate.now().toString());
}
}
新闻服务(模拟):
java
package com.example.agent.service;
import com.example.agent.model.NewsStory;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class NewsService {
public List<NewsStory> getRelevantNews(String sign, String name) {
// 模拟根据星座和姓名获取相关新闻
return List.of(
new NewsStory(
"Breakthrough in " + sign + " research",
"Scientists have made a significant discovery related to " + sign + " traits.",
"https://example.com/news/1",
"Science Daily"
),
new NewsStory(
name + " inspires community with " + sign + " spirit",
"Local community leaders praise the determination and courage.",
"https://example.com/news/2",
"Local News"
)
);
}
}
Agent 主类:
java
package com.example.agent;
import com.embabel.agent.api.annotation.Agent;
import com.embabel.agent.api.annotation.Action;
import com.embabel.agent.api.annotation.AchievesGoal;
import com.embabel.agent.api.OperationContext;
import com.embabel.agent.api.llm.OpenAiModels;
import com.example.agent.model.*;
import com.example.agent.service.HoroscopeService;
import com.example.agent.service.NewsService;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@Agent(description = "Find news based on a person's star sign and generate a personalized writeup")
public class StarNewsFinder {
@Autowired
private HoroscopeService horoscopeService;
@Autowired
private NewsService newsService;
@Action(description = "Extract the person's name and star sign from user input")
public StarPerson extractStarPerson(UserInput userInput, OperationContext context) {
return context.ai()
.withLlm(OpenAiModels.GPT_41)
.createObject(StarPerson.class, """
Extract the person's name and star sign from this user input.
The star sign should be one of: Aries, Taurus, Gemini, Cancer, Leo,
Virgo, Libra, Scorpio, Sagittarius, Capricorn, Aquarius, Pisces.
User input: %s
""".formatted(userInput.getText())
);
}
@Action(description = "Get today's horoscope for the given star sign")
public Horoscope getHoroscope(StarPerson person) {
return horoscopeService.getHoroscope(person.getSign());
}
@Action(description = "Fetch relevant news stories based on the person's profile")
public List<NewsStory> getRelevantNews(StarPerson person) {
return newsService.getRelevantNews(person.getSign(), person.getName());
}
@AchievesGoal(description = "Write an engaging writeup combining horoscope and news")
@Action(description = "Generate final writeup with horoscope insights and relevant news")
public Writeup generateWriteup(
StarPerson person,
Horoscope horoscope,
List<NewsStory> stories,
OperationContext context
) {
return context.ai()
.withLlm(OpenAiModels.CLAUDE_OPUS_4)
.createObject(Writeup.class, """
Create a personalized writeup for %s (%s sign).
Today's horoscope: %s
Relevant news stories: %s
Write an engaging, positive writeup that:
1. Addresses the person by name
2. Incorporates the horoscope reading
3. Connects the news stories to the person's life
4. Ends with an inspirational message
""".formatted(
person.getName(),
person.getSign(),
horoscope.getReading(),
stories.stream()
.map(s -> "- " + s.getTitle() + ": " + s.getSummary())
.reduce((a, b) -> a + "\n" + b)
.orElse("No news stories available")
)
);
}
}
Spring Boot 启动类:
java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = "com.example.agent")
public class AgentApplication {
public static void main(String[] args) {
SpringApplication.run(AgentApplication.class, args);
}
}
五、案例(完整代码)
5.1 案例一:测验生成 Agent(Quizzard)
从博客文章 URL 获取内容,自动生成多项选择题。
领域模型:
java
package com.example.quiz.model;
import com.embabel.agent.api.annotation.JsonClassDescription;
import com.embabel.agent.api.annotation.JsonPropertyDescription;
import java.util.List;
@JsonClassDescription("A blog post with URL and content")
public class BlogPost {
@JsonPropertyDescription("The URL of the blog post")
private String url;
@JsonPropertyDescription("The content of the blog post")
private String content;
@JsonPropertyDescription("The title of the blog post")
private String title;
// 构造函数、getters、setters
}
@JsonClassDescription("A multiple choice question")
public class QuizQuestion {
@JsonPropertyDescription("The question text")
private String question;
@JsonPropertyDescription("The list of options")
private List<String> options;
@JsonPropertyDescription("The correct answer index (0-based)")
private int correctAnswerIndex;
@JsonPropertyDescription("Explanation of why this is the correct answer")
private String explanation;
// 构造函数、getters、setters
}
@JsonClassDescription("A complete quiz with title and questions")
public class Quiz {
@JsonPropertyDescription("The quiz title")
private String title;
@JsonPropertyDescription("The list of questions")
private List<QuizQuestion> questions;
@JsonPropertyDescription("The number of questions")
private int questionCount;
// 构造函数、getters、setters
}
QuizAgent:
java
package com.example.quiz.agent;
import com.embabel.agent.api.annotation.Agent;
import com.embabel.agent.api.annotation.Action;
import com.embabel.agent.api.annotation.AchievesGoal;
import com.embabel.agent.api.OperationContext;
import com.embabel.agent.api.llm.OpenAiModels;
import com.example.quiz.model.BlogPost;
import com.example.quiz.model.Quiz;
import com.example.quiz.model.QuizQuestion;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Agent(description = "Generate multiple choice quizzes from blog post URLs")
public class QuizGeneratorAgent {
private final RestTemplate restTemplate = new RestTemplate();
@Action(description = "Fetch blog content from a URL")
public BlogPost fetchBlogContent(String url, OperationContext context) {
try {
// 简化示例:实际应用中需要更复杂的HTML解析
String content = restTemplate.getForObject(url, String.class);
return new BlogPost(url, content, "Blog Post from " + url);
} catch (Exception e) {
throw new RuntimeException("Failed to fetch blog content from " + url, e);
}
}
@AchievesGoal(description = "Generate a quiz with multiple choice questions from blog content")
@Action(description = "Generate quiz questions from blog content")
public Quiz generateQuiz(BlogPost blogPost, OperationContext context) {
return context.ai()
.withLlm(OpenAiModels.GPT_41)
.createObject(Quiz.class, """
Generate a multiple choice quiz based on this blog post content.
Blog Title: %s
Blog Content: %s
Requirements:
1. Create exactly 5 questions
2. Each question must have 4 options
3. Only one correct answer per question
4. Provide an explanation for each correct answer
5. Questions should test understanding, not memorization
6. Make the quiz challenging but fair
Return a Quiz object with title and list of questions.
""".formatted(blogPost.getTitle(), truncate(blogPost.getContent(), 3000))
);
}
private String truncate(String content, int maxLength) {
return content.length() > maxLength ? content.substring(0, maxLength) + "..." : content;
}
}
5.2 案例二:多 LLM 研究 Agent
在同一流程中混合使用多个 LLM------用低成本模型做筛选,用高端模型做深度分析。
java
package com.example.research.agent;
import com.embabel.agent.api.annotation.Agent;
import com.embabel.agent.api.annotation.Action;
import com.embabel.agent.api.annotation.AchievesGoal;
import com.embabel.agent.api.OperationContext;
import com.embabel.agent.api.llm.OpenAiModels;
import com.embabel.agent.api.llm.AnthropicModels;
import com.example.research.model.*;
import java.util.List;
@Agent(description = "Research assistant that uses multiple LLMs for different tasks")
public class MultiLLMResearchAgent {
@Action(description = "Initial screening with cost-effective model")
public List<ResearchPaper> initialScreening(String query, OperationContext context) {
// 使用 GPT-3.5 做初步筛选(成本低)
return context.ai()
.withLlm(OpenAiModels.GPT_35_TURBO)
.createObjectList(ResearchPaper.class, """
Given the research query: "%s"
List 10 potentially relevant research papers.
For each paper, provide: title, authors, year, abstract, and relevance score (1-10).
Return as a list of ResearchPaper objects.
""".formatted(query)
);
}
@Action(description = "Deep analysis with premium model")
public DeepAnalysis deepAnalysis(ResearchPaper paper, OperationContext context) {
// 使用 GPT-4.1 做深度分析(高质量)
return context.ai()
.withLlm(OpenAiModels.GPT_41)
.createObject(DeepAnalysis.class, """
Perform a deep analysis of this research paper:
Title: %s
Abstract: %s
Authors: %s
Provide:
1. Key contributions summary
2. Methodology analysis
3. Strengths and weaknesses
4. Potential impact on the field
5. Related work to explore
""".formatted(paper.getTitle(), paper.getAbstract(), paper.getAuthors())
);
}
@Action(description = "Synthesize findings with best-in-class model")
@AchievesGoal(description = "Produce a comprehensive research summary")
public ResearchSummary synthesizeFindings(
List<DeepAnalysis> analyses,
OperationContext context
) {
// 使用 Claude Opus 做最终综合(最佳推理能力)
return context.ai()
.withLlm(AnthropicModels.CLAUDE_OPUS_4)
.createObject(ResearchSummary.class, """
Synthesize the following research paper analyses into a comprehensive summary.
Analyses:
%s
The summary should:
1. Identify common themes across papers
2. Highlight contradictory findings
3. Suggest future research directions
4. Provide actionable insights
Return as a ResearchSummary object.
""".formatted(
analyses.stream()
.map(a -> "- " + a.getKeyContributions())
.reduce((a, b) -> a + "\n" + b)
.orElse("No analyses available")
)
);
}
}
5.3 案例三:带条件和循环的 Agent
展示 Conditions 和重复执行的用法。
java
package com.example.retry.agent;
import com.embabel.agent.api.annotation.Agent;
import com.embabel.agent.api.annotation.Action;
import com.embabel.agent.api.annotation.AchievesGoal;
import com.embabel.agent.api.annotation.Condition;
import com.embabel.agent.api.OperationContext;
import com.embabel.agent.api.llm.OpenAiModels;
import com.example.retry.model.*;
@Agent(description = "Agent that retries until quality threshold is met")
public class QualityAwareAgent {
private int attemptCount = 0;
private static final int MAX_ATTEMPTS = 3;
private static final double QUALITY_THRESHOLD = 0.8;
@Action(description = "Generate initial draft")
public Draft generateDraft(String topic, OperationContext context) {
attemptCount++;
return context.ai()
.withLlm(OpenAiModels.GPT_41)
.createObject(Draft.class, """
Write a draft on the topic: %s
Make it comprehensive and well-structured.
""".formatted(topic)
);
}
@Condition(description = "Check if draft quality is acceptable")
public boolean isDraftQualityAcceptable(Draft draft, OperationContext context) {
QualityScore score = context.ai()
.withLlm(OpenAiModels.GPT_35_TURBO)
.createObject(QualityScore.class, """
Rate the quality of this draft from 0.0 to 1.0:
%s
Consider: clarity, accuracy, structure, and completeness.
Return as a QualityScore object with a score field.
""".formatted(draft.getContent())
);
return score.getScore() >= QUALITY_THRESHOLD;
}
@Condition(description = "Check if max attempts reached")
public boolean isMaxAttemptsReached() {
return attemptCount >= MAX_ATTEMPTS;
}
@Action(description = "Improve draft based on feedback")
public Draft improveDraft(Draft draft, OperationContext context) {
return context.ai()
.withLlm(OpenAiModels.GPT_41)
.createObject(Draft.class, """
Improve this draft based on quality feedback.
Focus on clarity, accuracy, and completeness.
Original draft:
%s
Return an improved version.
""".formatted(draft.getContent())
);
}
@AchievesGoal(description = "Produce a high-quality final draft")
@Action(description = "Finalize and return the draft")
public FinalDraft finalizeDraft(Draft draft) {
return new FinalDraft(
draft.getContent(),
"Quality verified after " + attemptCount + " attempts",
java.time.Instant.now()
);
}
}
5.4 案例四:多 Agent 协作
主 Agent 将子任务委托给专门的子 Agent。
子 Agent 1 - 内容分析器:
java
package com.example.collaboration.agent;
import com.embabel.agent.api.annotation.Agent;
import com.embabel.agent.api.annotation.Action;
import com.embabel.agent.api.OperationContext;
import com.embabel.agent.api.llm.OpenAiModels;
import com.example.collaboration.model.*;
@Agent(description = "Specialized agent for content analysis")
public class ContentAnalyzerAgent {
@Action(description = "Analyze content and extract key insights")
public ContentAnalysis analyze(String content, OperationContext context) {
return context.ai()
.withLlm(OpenAiModels.GPT_41)
.createObject(ContentAnalysis.class, """
Analyze this content and extract:
1. Main topics
2. Key entities mentioned
3. Sentiment
4. Key arguments or claims
5. Suggested categories
Content: %s
""".formatted(content)
);
}
}
子 Agent 2 - 摘要生成器:
java
package com.example.collaboration.agent;
import com.embabel.agent.api.annotation.Agent;
import com.embabel.agent.api.annotation.Action;
import com.embabel.agent.api.OperationContext;
import com.embabel.agent.api.llm.AnthropicModels;
import com.example.collaboration.model.*;
@Agent(description = "Specialized agent for summarization")
public class SummarizerAgent {
@Action(description = "Generate a concise summary")
public Summary summarize(String content, OperationContext context) {
return context.ai()
.withLlm(AnthropicModels.CLAUDE_SONNET_4)
.createObject(Summary.class, """
Generate a concise, well-structured summary of this content:
%s
The summary should be:
- 3-5 paragraphs
- Capture all key points
- Maintain the original meaning
- Be suitable for executive reading
""".formatted(content)
);
}
}
主 Agent - 编排器:
java
package com.example.collaboration.agent;
import com.embabel.agent.api.annotation.Agent;
import com.embabel.agent.api.annotation.Action;
import com.embabel.agent.api.annotation.AchievesGoal;
import com.embabel.agent.api.OperationContext;
import com.embabel.agent.api.agent.AgentPlatform;
import com.example.collaboration.model.*;
import org.springframework.beans.factory.annotation.Autowired;
@Agent(description = "Orchestrator that delegates tasks to specialized sub-agents")
public class OrchestratorAgent {
@Autowired
private AgentPlatform agentPlatform;
@Action(description = "Coordinate content processing pipeline")
@AchievesGoal(description = "Produce a complete processed content package")
public ProcessedContent processContent(ContentRequest request, OperationContext context) {
// 1. 委托给 ContentAnalyzerAgent
ContentAnalysis analysis = agentPlatform
.agent(ContentAnalyzerAgent.class)
.invoke("analyze", request.getContent());
// 2. 委托给 SummarizerAgent
Summary summary = agentPlatform
.agent(SummarizerAgent.class)
.invoke("summarize", request.getContent());
// 3. 综合结果
return new ProcessedContent(
request.getId(),
analysis,
summary,
java.time.Instant.now()
);
}
}
六、原理
6.1 GOAP 规划算法
Embabel 最核心的技术是 GOAP(Goal-Oriented Action Planning,目标导向行动规划) 。
GOAP 最初用于游戏 AI(如《最后生还者》中的敌人、《星际争霸》中的单位),其核心思想是:
- 定义目标(Goal):Agent 要达成的最终状态
- 定义行动(Action):每个 Action 有前置条件(Preconditions)和效果(Effects)
- 规划:通过 A* 等搜索算法,找到从当前状态到目标状态的最优 Action 序列
Agent 的工作流程是:
- 明确当前状态和目标
- 根据所有可用的 Action 及其前置条件和效果
- 动态规划出从当前状态到目标的最优行动序列
- 每执行一步,根据新的状态重新规划
这就像导航软件------不是出发前规划好全程就再也不变了,而是每到一个路口,根据实时路况重新计算接下来的路线。
与 LLM 做规划的本质区别:
| GOAP(Embabel) | LLM 做规划 | |
|---|---|---|
| 规划者 | 确定性算法 | 大语言模型 |
| 可预测性 | ✅ 完全可预测 | ❌ 每次可能不同 |
| 可解释性 | ✅ 规划可审查 | ❌ 难以解释决策 |
| 成本 | ✅ 不消耗 Token | ❌ 每次消耗 Token |
| 幻觉风险 | ✅ 无幻觉 | ❌ 可能产生幻觉 |
| 可测试性 | ✅ 易于测试 | ❌ 难以测试 |
Rod Johnson 认为,现有 AI Agent 框架将规划责任交给 LLM,在生产环境中会产生幻觉行为、不可测试的流程,以及偶尔 Agent 决定做你从未预期且事后无法解释的事情。Embabel 的 GOAP 方法让 LLM 处理每个 Action 内的内容工作(写作、总结、分类),但从不决定下一步运行什么。
6.2 类型驱动的规划推导
Embabel 的一个精妙设计是:从方法的输入/输出类型自动推导执行计划。
在 StarNewsFinder 示例中:
generateWriteup()需要StarPerson、Horoscope和List<NewsStory>- 系统自动发现
extractStarPerson()可以产生StarPerson - 系统自动发现
getHoroscope()可以产生Horoscope - 系统自动发现
getRelevantNews()可以产生List<NewsStory> - 系统自动编排调用顺序
开发者不需要手动编写工作流定义------类型签名就是工作流。
6.3 Spring 集成原理
Embabel 深度集成 Spring 生态:
@Agent是 Spring 的 stereotype 注解------Embabel 将你的类注册为 Spring Bean,同时注册到 Agent 运行时- 获得组件扫描、依赖注入和所有现有 Spring 基础设施
- 支持 Spring AOP 装饰方法
- 利用 Spring 的持久化和事务管理
6.4 Starter 模块架构(1.0.0)
Embabel 提供多个 Starter 模块:
| Starter | 功能 |
|---|---|
embabel-agent-starter |
核心启动器:Agent 发现和注册、Agent Platform 依赖注入、进度跟踪、错误处理 |
embabel-agent-starter-shell |
交互式命令行界面、Human-in-the-loop 能力 |
embabel-agent-starter-mcpserver |
MCP 协议服务器实现 |
七、对比
7.1 Embabel vs Spring AI
| 维度 | Spring AI | Embabel |
|---|---|---|
| 层次 | 低层抽象(LLM 集成) | 高层抽象(Agent 编排) |
| 定位 | 类比 Servlet API | 类比 Spring MVC |
| 规划 | 无内置规划 | GOAP 规划器 |
| 工作流 | 硬编码链式调用 | 动态规划 + 自动编排 |
| 模型支持 | 20+ 提供商 | 继承 Spring AI 的所有支持 |
关系类比:Spring AI 是"零件箱",Embabel 是"装配图纸 + 流水线" 。
7.2 JVM AI 框架全景(1.0 时代)
| 框架 | 语言 | 核心优势 | 适用场景 |
|---|---|---|---|
| Spring AI | Java | 20+ 提供商、自动配置 | LLM 集成层 |
| LangChain4j | Java | 广度与灵活性、RAG、MCP | 通用 AI 应用 |
| Embabel 1.0 | Kotlin/Java | GOAP 规划、可解释、强类型、RAG 稳定 | 企业级 Agent 编排 |
| Koog | Kotlin | 协程、多平台(JVM + Android + iOS + WASM) | Kotlin 多平台项目 |
| Google ADK | Java | Gemini 原生、A2A 协议 | Google 生态集成 |
7.3 选择建议
- Java 开发者需要 Agent 编排 → Embabel 1.0
- 只需要调用 LLM API → Spring AI
- Kotlin 多平台项目 → Koog
- 需要最大灵活性 → LangChain4j
- Google 生态深度集成 → Google ADK
八、避坑指南
8.1 版本兼容性陷阱
坑 :Embabel 1.0.0 基于 Spring Boot 3.5.x 和 Spring AI 1.1.7,不支持 Spring Boot 4.x。
解决:使用 Spring Boot 3.5.x 版本。
坑:示例仓库使用最新 SNAPSHOT,模板仓库使用 Milestone 版本,API 可能不兼容。
解决 :统一升级 pom.xml 中的 embabel-agent.version 属性为 1.0.0。
8.2 类型设计陷阱
坑:Action 方法的输入输出类型决定了执行计划------类型设计不当会导致规划失败或执行顺序错乱。
解决:
- 每个 Action 的输入类型应该能被前置 Action 的输出满足
- 使用领域对象而非原始类型(String、int 等)
- 利用
@JsonClassDescription和@JsonPropertyDescription为 LLM 提供描述信息
8.3 MCP 工具错误处理陷阱(1.0 改进)
坑:1.0 之前,MCP 工具错误可能静默消失。
解决:升级到 1.0.0,MCP 客户端现在可以正确看到并处理工具错误。
8.4 废弃 API 陷阱
坑:1.0.0 移除了已废弃的方法。
解决:升级前检查代码中是否使用了废弃 API,参考迁移文档更新。
8.5 测试陷阱
坑:Agent 涉及 LLM 调用,传统单元测试难以覆盖。
解决:
- Agent 可以像 Spring Bean 一样进行单元测试
- 使用
embabel-agent-test依赖进行端到端测试 - 利用专门的 Prompt 测试库
九、最佳实践
9.1 领域模型优先
原则:先设计领域模型,再写 Agent。
Embabel 的核心理念是 Actions、Goals 和 Conditions 都由领域模型驱动。好的领域模型能让类型系统自动推导执行计划,IDE 提供完整的重构支持。
9.2 Action 粒度适中
原则 :Action 应该是原子性的业务步骤。
- 太粗:失去了规划的优势,变成硬编码流程
- 太细:规划开销大,执行效率低
一个好的 Action 是:一个独立的、可复用的业务能力单元。
9.3 LLM 混合策略(1.0 增强)
原则:根据任务复杂度选择合适的模型。
java
// 简单任务用低成本模型
context.ai().withLlm(OpenAiModels.GPT_35_TURBO).createObject(...)
// 复杂任务用高端模型
context.ai().withLlm(OpenAiModels.GPT_41).createObject(...)
// 最佳推理用 Claude Opus
context.ai().withLlm(AnthropicModels.CLAUDE_OPUS_4).createObject(...)
// 敏感数据用本地模型(1.0 ONNX 支持)
context.ai().withLlm(LocalModels.LLAMA).createObject(...)
1.0 版本支持通过角色别名(role aliases)配置模型------在配置中定义"best"模型用于需要强推理的步骤,"cheapest"模型用于常规步骤。
9.4 充分利用 Spring 生态
原则:Agent 是 Spring Bean,充分利用 Spring 的能力:
- 使用
@Autowired注入服务 - 使用 Spring AOP 添加横切关注点
- 利用 Spring 的 Profile 机制区分环境
9.5 Human-in-the-Loop 设计
原则:关键决策点保留人工审批。
使用 embabel-agent-starter-shell 提供的交互式命令行功能。
9.6 可观测性(1.0 增强)
原则:在生产环境中监控 Agent 的执行。
1.0 版本新增 MCP 服务器健康检查------通过 Spring Boot Actuator 暴露 MCP 集成健康状态。
9.7 RAG 生产就绪(1.0 新增)
原则:1.0 的 RAG API 已从实验性升级为稳定。
现在可以放心在生产环境构建向量支持的 Agent。
十、面试考点及解析
Q1:Embabel 和 Spring AI 有什么区别?
考点:理解框架的层次划分。
解析 :Spring AI 是 LLM 集成层,提供调用不同模型、向量存储、工具调用等基础能力。Embabel 是在 Spring AI 之上的 Agent 编排层,提供 GOAP 规划、动态 Action 编排、状态管理等高层能力。类比关系:Spring AI 是 Servlet API,Embabel 是 Spring MVC。
Q2:GOAP 是什么?为什么 Embabel 用它而不是 LLM 来做规划?
考点:理解 GOAP 的核心价值。
解析:GOAP(目标导向行动规划)是一种来自游戏 AI 的规划算法。使用 GOAP 而非 LLM 做规划的原因是:
- 确定性:规划结果可预测、可解释
- 高效:减少不必要的 LLM 调用
- 可靠:不受 LLM 幻觉影响
- 成本:规划不消耗 Token
- 可审计:企业团队需要向合规或审计解释 AI 系统时,确定性规划至关重要
Q3:Embabel 如何自动推导执行计划?
考点:理解类型驱动的编排机制。
解析 :Embabel 通过 Action 方法的输入输出类型签名 自动推导执行计划。例如,如果 Action A 输出类型为 X,Action B 需要输入类型为 X,系统会自动将 A 放在 B 之前执行。开发者不需要手动编写工作流定义,类型系统就是工作流。
Q4:Embabel 1.0 有哪些重要变化?
考点:了解版本演进。
解析:
- RAG API 从实验性升级为稳定
- MCP 服务器健康检查通过 Spring Boot Actuator 暴露
- A2A 协议支持
- ONNX 本地嵌入支持
- 扩展模型覆盖(Z.ai GLM、DeepSeek 更新)
- MCP 工具错误正确传播
- 移除废弃 API
Q5:如何在 Embabel 中实现多 Agent 协作?
考点:理解 Agent 间通信机制。
解析 :Embabel 的 Agent 可以通过 Agent Platform 相互调用。主 Agent 可以将子任务委托给专门的子 Agent。1.0 版本还支持 A2A 协议 ,启用 a2a Spring profile 后可与外部 A2A 服务通信。
Q6:Embabel 用 Kotlin 编写,对 Java 开发者友好吗?
考点:理解 Kotlin-Java 互操作性。
解析 :非常友好。Embabel 虽然用 Kotlin 编写,但提供地道的 Java 使用体验。它是一个 JVM 框架 而非 Kotlin 框架,Java 开发者永远不会看到 Kt 导入。
十一、总结
11.1 Embabel 解决了什么问题?
Embabel 解决了 Java 生态中 AI Agent 生产级落地的核心问题:
- 从"咒语"到"工程":通过确定性规划,让非确定性的 AI 在确定性的企业系统里稳定工作
- 从"硬编码"到"动态规划":用 GOAP 替代硬编码工作流
- 从"弱类型"到"强类型":所有 LLM 交互都是强类型的
- 从"单模型"到"多模型混合":1.0 版本支持更灵活的模型角色别名配置
11.2 谁应该使用 Embabel?
- ✅ 需要在 Java/Spring 应用中集成 AI Agent 的团队
- ✅ 追求生产级可靠性、可测试性的企业级项目
- ✅ 需要处理复杂多步骤工作流的场景
- ✅ 希望用 Java 而非 Python 构建 AI 应用的开发者
11.3 1.0.0 的里程碑意义
2026 年 7 月 20 日 Embabel 1.0.0 GA 的发布标志着:
- API 稳定性承诺------可以放心投入生产
- RAG、MCP、A2A 等关键功能生产就绪
- 企业级 AI Agent 在 JVM 上的成熟选项
参考地址
Embabel: hub.embabel.com/
github: github.com/embabel
Embabel 代表了一个重要的趋势:AI 能力正在从 Python 的实验场走向 JVM 的生产环境。Rod Johnson 将 Embabel 称为他自 Spring 以来最重要的项目。1.0.0 的发布为 Java 开发者提供了一个经过实战检验、生产就绪的 Agent 框架,让构建企业级 AI 应用不再是 Python 的专属特权。