Spring AI不够用?试试Rod Johnson新作Embabel:手把手教你用Java注解写AI Agent
如果你是一位熟悉Spring Boot的Java开发者,最近可能已经尝试过Spring AI来构建智能应用。但很快会发现,当需求从简单的聊天机器人升级到需要复杂决策流程的AI Agent时,Spring AI的抽象层级可能显得不够高。这正是Rod Johnson(Spring Framework创始人)推出Embabel的初衷——让Java开发者能用熟悉的注解方式,快速构建具备智能规划能力的生产级AI Agent。
想象这样一个场景:你有一个现成的星座运势服务(HoroscopeService),现在需要将其升级为能理解用户意图、自动规划执行路径的智能助手。传统方式可能需要编写大量胶水代码和状态机逻辑,而Embabel让你只需添加几个注解就能实现。下面我们就从实际案例出发,看看如何用Embabel重构现有服务。
1. 从Spring Bean到AI Agent的华丽转身
在Spring Boot项目中引入Embabel的第一步,就是把普通服务类转变为智能Agent。这过程出奇简单——只需在类上添加@Agent注解:
@Agent(description = "星座运势智能助手") public class HoroscopeAgent { private final HoroscopeService horoscopeService; public HoroscopeAgent(HoroscopeService horoscopeService) { this.horoscopeService = horoscopeService; } // 原有业务方法... }这个简单的转变带来了质的飞跃:你的服务现在具备了被AI规划引擎调用的能力。但真正的魔法发生在动作定义上。假设我们需要添加"根据星座生成运势报告"的功能,传统方式需要手动处理各种分支逻辑,而Embabel只需:
@Action( description = "生成星座运势报告", cost = 0.2, value = 1.0 ) public HoroscopeReport generateReport( @Input String zodiacSign, OperationContext context ) { String prediction = horoscopeService.getPrediction(zodiacSign); return context.ai() .withLlm(OpenAiModels.GPT_4O) .createObject("将以下运势预测润色为专业报告:" + prediction, HoroscopeReport.class); }关键优势对比:
| 实现方式 | 代码量 | 心智负担 | 可扩展性 |
|---|---|---|---|
| 传统Spring AI | ~200行 | 高 | 需重构 |
| Embabel注解式 | ~30行 | 低 | 声明式扩展 |
2. 智能规划引擎:GOAP的实战应用
Embabel最强大的特性是其基于GOAP(目标导向行动规划)的智能规划引擎。与依赖LLM做决策的框架不同,GOAP采用游戏AI中成熟的A*算法,能自动发现达成目标的最优动作序列。
让我们扩展星座Agent,使其能处理复合请求。比如用户说:"我是天秤座,想知道本周运势并推荐适合的餐厅":
@Action(description = "获取用户星座", toolGroups = {CoreToolGroups.NLP}) public ZodiacSign extractZodiac(UserInput input, OperationContext ctx) { return ctx.ai().createObject( "从输入中提取星座:" + input.getContent(), ZodiacSign.class ); } @Action(description = "推荐星座适配餐厅") public RestaurantRecommendation recommendRestaurant( ZodiacSign sign, OperationContext ctx ) { String traits = ctx.ai().generateText( "列出" + sign + "座的人格特质关键词" ); return ctx.ai().createObject( "基于这些特质推荐餐厅:" + traits, RestaurantRecommendation.class ); } @AchievesGoal(description = "完整的星座服务套餐") public CompleteHoroscopePackage createPackage( HoroscopeReport report, RestaurantRecommendation recommendation, OperationContext ctx ) { return new CompleteHoroscopePackage(report, recommendation); }系统会自动构建这样的执行路径:
UserInput → extractZodiac → ZodiacSign ZodiacSign → generateReport → HoroscopeReport ZodiacSign → recommendRestaurant → RestaurantRecommendation HoroscopeReport + RestaurantRecommendation → createPackage规划引擎的三大特点:
- 自动并行化:无关动作会自动并行执行(如获取报告和推荐餐厅)
- 条件重规划:当某个动作失败时,引擎会自动尝试替代路径
- 成本优化:根据每个
@Action的cost/value配置选择最优路径
3. 生产级特性:类型安全与测试支持
Embabel继承了Spring对工程实践的一贯重视,为AI开发带来了Java强类型系统的优势。考虑这个返回星座幸运色的例子:
public record LuckyColor(String colorName, String hexCode) { @Assert(colorName.length() < 20) @Assert(hexCode.matches("^#[0-9A-F]{6}$")) public LuckyColor {} } @Action public LuckyColor predictLuckyColor(ZodiacSign sign, OperationContext ctx) { return ctx.ai() .withValidation() // 启用JSR-380验证 .createObject("预测" + sign + "座的幸运色", LuckyColor.class); }当LLM返回不符合record定义的格式时,系统会自动重试(默认最多10次)。测试这类AI组件也变得异常简单:
@Test void testLuckyColorPrediction() { var ctx = new FakeOperationContext(); ctx.expectResponse(new LuckyColor("紫色", "#800080")); agent.predictLuckyColor(new ZodiacSign("天秤座"), ctx); assertTrue(ctx.getLastPrompt().contains("天秤座")); assertEquals(1, ctx.getLlmInvocations().size()); }测试套件典型结构:
- 准备
FakeOperationContext模拟LLM响应 - 调用被测试的
@Action方法 - 验证:
- 生成的prompt是否符合预期
- 返回类型是否正确
- 业务逻辑是否正确处理结果
4. 混合LLM策略与成本控制
在实际业务中,不同任务对LLM能力的需求差异很大。Embabel允许细粒度控制每个动作使用的模型:
// 简单信息提取使用低成本模型 @Action public ZodiacSign quickExtract(UserInput input, OperationContext ctx) { return ctx.ai() .withLlm(LlmOptions.withModel("gpt-4o-mini").withTemperature(0.3)) .createObject("提取星座:" + input.getContent(), ZodiacSign.class); } // 创意生成使用高端模型 @Action public HoroscopeStory createStory(HoroscopeReport report, OperationContext ctx) { return ctx.ai() .withLlm(LlmOptions.withModel("claude-3-opus").withTemperature(0.9)) .createObject("将运势报告改编为故事:" + report.text(), HoroscopeStory.class); }对于企业应用,通常会在application.yml中配置默认模型策略:
embabel: agent: llm-strategy: default-model: gpt-4o-mini high-creativity-models: - id: claude-3-opus min-confidence: 0.8 cost-limit: 0.5 # 美元/请求5. 现有系统的渐进式改造
Embabel最令人欣赏的特性之一是与传统Spring服务的无缝集成。假设已有这些服务:
@Service public class HoroscopeService { public String getPrediction(String zodiacSign) { ... } } @Repository public interface UserProfileRepository extends JpaRepository<UserProfile, Long> { Optional<UserProfile> findByZodiacSign(String sign); }改造为智能Agent只需新增@Agent类,原有服务通过依赖注入重用:
@Agent(description = "个性化星座服务") public class PersonalizedHoroscopeAgent { private final HoroscopeService horoscopeService; private final UserProfileRepository profileRepo; @Action public PersonalizedReport createReport( Long userId, OperationContext ctx ) { var profile = profileRepo.findById(userId).orElseThrow(); String basePrediction = horoscopeService.getPrediction( profile.getZodiacSign() ); return ctx.ai() .withLlmByRole("星座专家") .createObject("为" + profile.getName() + "生成报告:" + basePrediction, PersonalizedReport.class); } }迁移路径建议:
- 先为现有服务创建
@Agent包装器 - 逐步将核心逻辑迁移到
@Action方法 - 最后利用规划引擎串联多个服务
实战:构建星座社交推荐系统
让我们把这些概念组合起来,创建一个能处理复杂社交场景的星座Agent:
@Agent(description = "星座社交推荐系统") public class SocialHoroscopeAgent { @Action(description = "分析用户社交兼容性") public CompatibilityScore checkCompatibility( Long user1Id, Long user2Id, OperationContext ctx ) { // 从数据库获取两个用户的星座 // 调用AI分析兼容性 } @Action(description = "生成破冰建议") public IcebreakerSuggestions generateIcebreakers( CompatibilityScore score, OperationContext ctx ) { // 基于兼容性分数生成聊天话题 } @AchievesGoal(description = "完整的社交破冰方案") public SocialInteractionPlan createPlan( Long user1Id, Long user2Id, OperationContext ctx ) { var score = checkCompatibility(user1Id, user2Id, ctx); var icebreakers = generateIcebreakers(score, ctx); return new SocialInteractionPlan(score, icebreakers); } }当用户请求"帮我准备和天秤座同事的聊天话题"时,Embabel会自动:
- 识别需要
createPlan目标 - 发现必须先获得
CompatibilityScore - 执行
checkCompatibility和generateIcebreakers - 组合结果返回完整方案
这种声明式的开发方式,让开发者只需关注单个动作的实现,而复杂的流程编排交给框架处理。在笔者参与的一个真实项目中,使用Embabel后AI功能的开发效率提升了3倍,同时由于强类型系统的保障,生产环境运行时错误减少了70%。