主题
02 · 概览(Overview)
作者:Rod Johnson、Alex Hein-Heifetz、Dr. Igor Dayen、Arjen Poutsma、Jasper Blues
智能体 AI(Agentic AI),指的是让大语言(以及其它多模态)模型不只是生成文本, 而是充当会推理、目标驱动的智能体:能够规划、调用工具,并让自身行为适应性地达成结果。
JVM 是承载它的一个极具吸引力的平台,因为其强类型安全为"把 LLM 驱动的行为接入真实系统"提供了护栏。 又因为大量生产应用本来就跑在 JVM 上,它自然成为嵌入 AI 的地方。
尽管智能体 AI 被过度炒作,但其中相当一部分停留在学术 demo 里、缺少实用价值。 而直接融入到既有的、企业级的 JVM 应用中,我们就能在不重写核心系统、不"拆掉工厂来装新机器"的前提下释放 AI 能力。
术语表(Glossary)
开始之前,先解释几个术语 —— 如果你是刚踏入应用型 AI 开发,这些词可能比较陌生。 本节假定你已经从最终用户的角度知道什么是大语言模型(LLM)。
如果你已经是资深的智能体 AI 工程师,可以略过本节。
Agent(智能体) Embabel 框架中的 Agent 是一个自包含的组件,把领域逻辑、AI 能力与工具使用打包在一起,代表用户去实现某个特定目标。 它内部暴露多个
@Action方法,每个方法代表智能体可以采取的离散步骤。动作依赖通常是结构化的(有时是自然语言的)输入。 输入被用来代表用户执行任务 —— 执行领域代码、调用 AI 模型,甚至把另一个 Agent 当作子流程来调用。 调用 AI 模型时,模型可以被授予工具,以扩展其能力来达成目标。 输出是一个新类型,代表对输入的某种变换;不过在执行过程中可能产生一个或多个副作用, 例如向数据库写入新记录、在电商站点下单等等。Tools(工具) 工具通过让 LLM 与外部世界交互,扩展了 LLM 的原始能力。 单靠自身,语言模型只能基于训练数据与上下文窗口生成回复,容易给出不准确或"幻觉"的答案。 工具使用的灵感来自一种叫做 ReAct(Reason + Act,推理 + 行动)的技术,而 ReAct 本身又建立在思维链(Chain of Thought)推理之上; 不过现在大多数 LLM 允许直接声明工具,不必再依赖提示词工程技巧。 当工具存在时,LLM 会解读用户请求、规划步骤,然后在一个循环中把某些任务委派给工具, 从而在"推理(需要做什么?)"与"行动(哪个工具能做?)"之间交替。 工具的好处包括:
- 通过委派给领域特定或外部系统,回答超出 LLM 训练范围的问题、完成相应的任务;
- 产生有用的副作用,例如创建数据库记录、生成可视化、预订航班,或调用系统设计者提供的任意流程。
简而言之,工具是弥合"文本预测"与"现实世界行动"之间鸿沟的一种方式,把 LLM 变成既能推理又能执行的实用智能体。 在 Embabel 中,很多工具就是绑定的领域对象。
MCP 模型上下文协议(Model Context Protocol,MCP)是一种托管与共享工具的标准方式。 普通工具通常直接接到某个 Agent 或应用里;而 MCP Server 让工具可以被不同模型与运行时发现和复用 —— 它们可以系统级注册或在运行时注册,并通过统一协议被调用。Embabel 既可消费、也可发布这类工具,用于系统集成。
DICE(Domain-Integrated Context Engineering,领域集成上下文工程) 通过把 LLM 的输入与输出都锚定在带类型的领域对象上,来增强上下文工程。 上下文不再是非类型化的提示词,而是用具备业务语义的模型来结构化,从而带来精确性、可测试性,以及与既有系统的无缝集成。 DICE 把上下文变成一种可复用、可检视、可可靠操纵的产物。
为什么我们需要一个智能体框架?
LLM 还不够聪明、不能直接解决问题吗?有了 MCP 工具,不就够了吗? LLM 的能力似乎日新月异,MCP 又能让 LLM 获得大量强力工具,使其更加强大。
但对于业务应用而言,仍然有很多理由需要一个更高层的编排技术。以下是其中最重要的几条:
- 可解释性(Explainability):解决问题时,为什么做出了这些选择?
- 可发现性(Discoverability):如何才能在每一步找到正确的工具,并确保模型不会在多个工具之间选错?
- 混合使用模型的能力:不必只依赖最大的模型,很多任务可以用本地的、更便宜的、私有的模型完成。
- 在流程任意位置注入护栏(guardrails)的能力
- 管理流程执行、引入更强韧性的能力
- 大规模下的流程组合性(composability):我们很快会看到不只是单机上运行的 Agent,而是Agent 联邦。
- 与敏感既有系统(如数据库)更安全的集成 —— 让再好的 LLM 拥有写权限都是危险的。
智能体框架把复杂任务拆成更小、更可管理的组件,从而带来更强的控制力与可预测性。
智能体框架既提供 "代码能动性"(code agency),也提供 "LLM 能动性"(LLM agency)。 NVIDIA Research 的这篇论文对此有很好的阐述。
延伸阅读:
- Embabel: A new Agent Platform For the JVM
- The Embabel Vision
Embabel 的差异化(Embabel Differentiators)
那么 Embabel 与其它智能体框架有何不同?我们相信,Embabel 智能体框架是企业内开发智能体 AI 的最佳选择。
更成熟的规划(Sophisticated Planning)
它超越了有限状态机、也超越了带嵌套的顺序执行:引入了一个真正的规划步骤,使用的是非 LLM 的 AI 算法。 这使系统能够把已知步骤以全新的顺序组合起来,完成它并未被编程去做的任务,并能对并行化等运行时行为做出决策。
更强的可扩展性与复用(Superior Extensibility and Reuse)
得益于动态规划,新增领域对象、动作、目标与条件就能扩展系统的能力, 而不需要修改 FSM 定义或既有代码。
强类型与面向对象(Strong Typing and Object Orientation)
动作、目标与条件由领域模型驱动,而领域模型可以带行为。 一切都是强类型的,提示词与手写代码可以干净地互操作。不再有魔法 Map,并能享受完整的重构支持。
平台抽象(Platform Abstraction)
编程模型与平台内部实现之间干净分离,让你可以本地运行,而在生产环境不改一行代码就能获得更高的服务质量(QoS)。
混合使用多家 LLM(LLM Mixing)
很容易构建混合使用多个 LLM 的应用,从而在可负担与高能力之间取得平衡, 让系统针对不同任务利用不同模型的长处。尤其是它便于把本地模型用于"点状"任务 —— 这对成本与隐私都很重要。
与 Spring 及 JVM 的集成(Spring and JVM Integration)
构建在 Spring 与 JVM 之上,因此很容易接入既有的企业功能与能力。例如:
- Spring 可以注入并管理 Agent,包括用 Spring AOP 装饰函数;
- 可以选用成熟的持久化与事务管理方案。
为可测试性而生(Designed for Testability)
从底层开始,单元测试与 Agent 端到端测试都很容易。
核心概念(Core Concepts)
智能体框架把任务拆成更小的独立交互,让 LLM 的使用更可预测、更聚焦。
Embabel 用以下要素来建模智能体流程:
- Actions(动作):智能体采取的步骤。它们是智能体行为的基本构件。
- Goals(目标):智能体试图达成什么。
- Conditions(条件):规划期间用于求值的条件。每执行完一个动作,条件都会被重新评估。
- Domain Model(领域模型):支撑整个流程、并为动作/目标/条件提供信息的对象。
由此,Embabel 能够生成 plan(计划):一串达成目标的动作序列。 计划由系统动态制定,而不是由程序员写死。 每个动作完成后系统都会重规划, 从而能够适应新信息、并观察上一个动作产生的效果。这实际上就是一个 OODA 循环。
应用开发者通常不需要直接处理条件与规划:大多数条件源于代码中定义的数据流, 系统据此推断前置条件与后置条件,从而(重新)评估计划。
完整示例
下面看一个完整示例,它展示了 Embabel 如何从输入/输出类型推断条件、并管理动作之间的数据流。 该示例来自 Embabel Agent Examples 仓库:
java
@Agent(description = "Find news based on a person's star sign") // ①
public class StarNewsFinder {
private final HoroscopeService horoscopeService; // ②
private final int storyCount;
public StarNewsFinder(
HoroscopeService horoscopeService, // ③
@Value("${star-news-finder.story.count:5}") int storyCount) {
this.horoscopeService = horoscopeService;
this.storyCount = storyCount;
}
@Action // ④
public StarPerson extractStarPerson(UserInput userInput, OperationContext context) { // ⑤
return context.ai()
.withLlm(OpenAiModels.GPT_41)
.createObject("""
Create a person from this user input, extracting their name and star sign:
%s""".formatted(userInput.getContent()), StarPerson.class); // ⑥
}
@Action // ⑦
public Horoscope retrieveHoroscope(StarPerson starPerson) { // ⑧
// 使用普通的注入式 Spring 服务 —— 不是 LLM
return new Horoscope(horoscopeService.dailyHoroscope(starPerson.sign())); // ⑨
}
@Action // ⑩
public RelevantNewsStories findNewsStories(
StarPerson person, Horoscope horoscope, OperationContext context) { // ⑪
var prompt = """
%s is an astrology believer with the sign %s.
Their horoscope for today is: %s
Given this, use web tools to find %d relevant news stories.
""".formatted(person.name(), person.sign(), horoscope.summary(), storyCount);
return context.ai().withDefaultLlm()
.withToolGroup(CoreToolGroups.WEB) // ⑫
.createObject(prompt, RelevantNewsStories.class);
}
@AchievesGoal(description = "Write an amusing writeup based on horoscope and news") // ⑬
@Action
public Writeup writeup(
StarPerson person, RelevantNewsStories stories, Horoscope horoscope,
OperationContext context) { // ⑭
var llm = LlmOptions.fromCriteria(ModelSelectionCriteria.getAuto())
.withTemperature(0.9); // ⑮
var storiesFormatted = stories.items().stream()
.map(s -> "- " + s.url() + ": " + s.summary())
.collect(Collectors.joining("\n"));
var prompt = """
Write something amusing for %s based on their horoscope and news stories.
Format as Markdown with links.
%s
%s
""".formatted(person.name(), horoscope.summary(), storiesFormatted); // ⑯
return context.ai().withLlm(llm).createObject(prompt, Writeup.class); // ⑰
}
}各注记含义:
- ① Agent 声明:
@Agent注解把它定义为一个能够执行多步流程的智能体。 - ② Spring 集成:就是普通的 Spring 依赖注入 —— 智能体同时使用 LLM 服务和传统的业务服务。
- ③ 服务注入:
HoroscopeService像任何 Spring bean 一样被注入 —— 智能体可以无缝混合 AI 与非 AI 操作。 - ④ 动作定义:
@Action把方法标记为智能体可以采取的步骤。每个动作代表一种能力。 - ⑤ 输入条件推断:方法签名
extractStarPerson(UserInput userInput, ...)告诉 Embabel: 前置条件是"必须有一个可用的UserInput对象"。- 所需数据:智能体需要用户输入才能继续;
- 能力:该动作可以从非结构化输入中提取出结构化数据。
- ⑥ 输出条件创建:返回
StarPerson会创建后置条件:"世界状态中现在有一个StarPerson对象"。- 数据可用性:这个输出会成为后续动作的输入;
- 类型安全:领域模型强制保障结构。
- ⑦ 非 LLM 动作:并非所有动作都使用 LLM —— 这里展示了 AI 与传统编程的混合。
- ⑧ 数据流链路:方法签名
retrieveHoroscope(StarPerson starPerson)创建了: 前置条件"必须存在一个StarPerson对象"(来自上一个动作)。- 依赖:该动作只能在
extractStarPerson完成后执行。
- 依赖:该动作只能在
- ⑨ 服务集成:使用的是注入的
horoscopeService,而不是 LLM。- 普通服务调用:该动作调用传统的 Spring 服务 —— 展示智能体如何融合 AI 与常规操作。
- ⑩ 另一个动作:该动作使用在
PromptRunner层面指定的工具。 - ⑪ 多输入依赖:该方法同时需要
StarPerson与Horoscope—— 体现了复杂的数据流编排。 - ⑫ 启用工具的 LLM:
withToolGroup(CoreToolGroups.WEB)为这次 LLM 调用加上了 Web 搜索工具,使其能检索当前的新闻。 - ⑬ 目标达成:
@AchievesGoal把它标记为终结动作,完成智能体的目标。 - ⑭ 复杂的输入要求:最后一个动作需要三种不同的数据类型,体现了精细的编排。
- ⑮ 创意配置:较高的温度(0.9)有利于产出有创意、有趣味的输出 —— 对"写段子"这类任务很合适。
- ⑯ 带数据的结构化提示词:提示词中同时包含运势摘要与格式化后的新闻(使用 XML 风格标签), 确保 LLM 拥有来自前面动作的全部上下文。
- ⑰ 最终输出:返回
Writeup,以个性化内容完成智能体的目标。
状态由框架通过进程黑板(blackboard)管理。
该示例被推断出的执行计划
仅根据类型签名,Embabel 就会为上面的示例智能体自动推断出如下执行计划:
目标:产出 Writeup(@AchievesGoal 动作的最终返回类型)
初始计划:
- 要产出
Writeup→ 需要writeup()动作 writeup()需要StarPerson、RelevantNewsStories与Horoscope- 要得到
StarPerson→ 需要extractStarPerson()动作 - 要得到
Horoscope→ 需要retrieveHoroscope()动作(需要StarPerson) - 要得到
RelevantNewsStories→ 需要findNewsStories()动作(需要StarPerson与Horoscope) extractStarPerson()需要UserInput→ 必须由用户提供
执行顺序:
UserInput → extractStarPerson() → StarPerson → retrieveHoroscope() → Horoscope
→ findNewsStories() → RelevantNewsStories → writeup() → Writeup ✅ 达成目标类型驱动流程的关键收益
- 自动编排(Automatic Orchestration):无需手动定义工作流 —— 智能体从类型依赖关系中自行推导出顺序。 这在出问题时尤其有价值:规划器可以重新评估局面,并可能找到通往目标的替代路径。
- 动态重规划(Dynamic Replanning):每个动作之后,智能体都会根据当前可用的数据对象重新评估"现在能做什么"。
- 类型安全(Type Safety):编译期保证数据在动作之间正确流动。没有魔法字符串 key。
- 灵活执行(Flexible Execution):如果多个动作都能产出所需的输入类型,智能体会依据上下文与效率来选择 (动作可以有成本 cost 与价值 value)。
这展示了 Embabel 如何把简单的方法签名转化为精细的多步智能体行为,而复杂的编排由框架自动处理。