主题
35 · 自定义 Embabel(Customizing)
添加 LLM(Adding LLMs)
你可以通过实现 LlmService 接口,把自定义 LLM 作为 Spring bean 加入。 Embabel 提供了 SpringAiLlmService,用于包装 Spring AI 的 ChatModel 实例。
使用 SpringAiLlmService
SpringAiLlmService 实现了 LlmService 接口,提供框架无关的 LLM 操作, 包括对 Embabel 工具循环与 message sender 抽象的支持。
java
@Configuration
public class LlmsConfig {
@Bean
public LlmService<?> myLlm() {
org.springframework.ai.chat.model.ChatModel chatModel = ...
return new SpringAiLlmService(
"myChatModel", // ①
"myChatModelProvider", // ②
chatModel) // ③
.withOptionsConverter(new MyLlmOptionsConverter()) // ④
.withKnowledgeCutoffDate(LocalDate.of(2025, 4, 1)); // ⑤
}
}① LLM 的名称(用于模型选择) ② 提供方名称,例如 "OpenAI" 或 "Anthropic" ③ Spring AI 的
ChatModel实例 ④ 用OptionsConverter实现把 Embabel 的LlmOptions转换成 Spring AI 的ChatOptions⑤ 如有知识截止日期,请设置它
LLM 配置项
SpringAiLlmService 支持以下配置:
- name(必需)
- provider,例如 "Mistral"(必需)
OptionsConverter:把 EmbabelLlmOptions转换为 Spring AIChatOptions- 知识截止日期(如有)
- 任何额外的
PromptContributor,会用于所有 LLM 调用。若提供了知识截止日期,会自动加入KnowledgeCutoffDate提示词贡献器 - 定价模型(如有)
一个常见需求是加入 OpenAI 兼容的 LLM。这可以通过扩展 OpenAiCompatibleModelFactory 类实现:
java
@Configuration
public class CustomOpenAiCompatibleModels extends OpenAiCompatibleModelFactory {
public CustomOpenAiCompatibleModels(
@Value("${MY_BASE_URL:#{null}}") String baseUrl,
@Value("${MY_API_KEY}") String apiKey,
ObservationRegistry observationRegistry) {
super(baseUrl, apiKey, observationRegistry);
}
@Bean
public LlmService<?> myGreatModel() {
// 调用父类方法
return openAiCompatibleLlm(
"my-great-model",
"me",
LocalDate.of(2025, 1, 1),
new PerTokenPricingModel(0.40, 1.6)
);
}
}添加 Embedding 模型
Embedding 模型同样可以作为 Embabel 的 EmbeddingService 类型 bean 加入。 用 SpringAiEmbeddingService 类包装 Spring AI 的 EmbeddingModel。
通常在一个 @Configuration 类里这样写:
java
@Configuration
public class EmbeddingModelsConfig {
@Bean
public EmbeddingService myEmbeddingModel() {
org.springframework.ai.embedding.EmbeddingModel embeddingModel = ...
return new SpringAiEmbeddingService(
"myEmbeddingModel",
"myEmbeddingModelProvider",
embeddingModel);
}
}自带密钥(Bring Your Own Key,BYOK)
默认情况下,Embabel 通过自动配置解析 LLM:你把一个或多个 API key 设为环境变量或属性(例如 ANTHROPIC_API_KEY), 相应的自动配置模块会在启动时注册一批 LlmService bean。 这是"所有用户共享一个平台级 key"场景的正确做法。
BYOK 适用于"key 在启动时未知"、或"你想即时解析出 LlmService"的场景:
- 用户自带 key —— 每个用户提供自己的 API key;应用必须校验它,并为该会话把它接入 prompt runner;
- 端到端测试 —— 在完整的 Spring 上下文之外,用一个专用测试 key 起一个真实的
LlmService; - 多租户或成本受控的应用 —— 依据租户配置或可用配额动态选择提供方。
Embabel 提供了校验 key 并返回就绪 LlmService 的工厂类,以及 detectProvider() 工具 —— 它会并发探测多个提供方,返回第一个接受该 key 的。
buildValidated()与detectProvider()只负责 key 校验。 Embabel 不存储、不记录、也不以其它方式管理 key —— 校验后的LlmService返回给调用方, 由调用方负责安全的 key 处理:仅通过 HTTPS 传输、不写明文日志、限制 key 生命周期、 并在登出或密钥轮换时吊销已缓存的服务。
构建一个已校验的服务(已知提供方)
当提供方已经明确时使用 —— 例如设置界面里每个提供方对应一个字段。
kotlin
// Anthropic
val service: LlmService<*> = AnthropicModelFactory(apiKey = userKey).buildValidated()
// OpenAI
val service: LlmService<*> = OpenAiCompatibleModelFactory.openAi(userKey).buildValidated()
// DeepSeek(OpenAI 兼容端点)
val service: LlmService<*> = OpenAiCompatibleModelFactory.deepSeek(userKey).buildValidated()
// Mistral(OpenAI 兼容端点)
val service: LlmService<*> = OpenAiCompatibleModelFactory.mistral(userKey).buildValidated()
// Gemini(OpenAI 兼容端点)
val service: LlmService<*> = OpenAiCompatibleModelFactory.gemini(userKey).buildValidated()buildValidated() 会发起一次探测调用、不重试。 成功时返回生产可用的 LlmService;失败时抛出 InvalidApiKeyException。
自动探测提供方(Auto-detecting)
当用户粘贴了一个 key 但没说提供方时使用 —— 例如一个接受任意受支持提供方 key 的注册流程。
detectProvider() 会用虚拟线程并发竞速这些候选,返回第一个校验成功的 LlmService。 探测到的提供方可通过结果上的 service.provider 获取。
kotlin
val service: LlmService<*> = detectProvider(
AnthropicModelFactory(apiKey = userKey),
OpenAiCompatibleModelFactory.openAi(userKey),
OpenAiCompatibleModelFactory.deepSeek(userKey),
OpenAiCompatibleModelFactory.mistral(userKey),
OpenAiCompatibleModelFactory.gemini(userKey),
)
val detectedProvider: String = service.provider只传一个参数也是合法的 —— 它只针对一个提供方做校验、不涉及并发。 这适合"提供方已知、但你想沿用 detectProvider 一致的错误处理"的设置流程。
kotlin
val service: LlmService<*> = detectProvider(AnthropicModelFactory(apiKey = userKey))如果所有候选都抛 InvalidApiKeyException,detectProvider 也会抛出 InvalidApiKeyException。
覆盖校验所用的模型
每个工厂都会用默认模型来校验 key(例如 OpenAI 用 gpt-4.1-mini,Anthropic 用 claude-haiku-4-5)。 如果该 key 只能访问特定的一组模型,请覆盖它:
kotlin
// OpenAI —— 用另一个模型档位做探测
OpenAiCompatibleModelFactory.openAi(userKey)
.validating(OpenAiModels.GPT_41_NANO, OpenAiModels.PROVIDER)
// Anthropic —— 在构造时设置校验模型
AnthropicModelFactory(apiKey = userKey, validationModel = AnthropicModels.CLAUDE_SONNET_4_5)支持更多提供方
任何暴露 OpenAI 兼容 HTTP API 的提供方,都可以作为 OpenAiCompatibleModelFactory.Companion 上的一行扩展函数加入:
kotlin
fun OpenAiCompatibleModelFactory.Companion.acme(apiKey: String) =
OpenAiCompatibleModelFactory.byok(
baseUrl = "https://api.acme.example.com/v1",
apiKey = apiKey,
validationModel = "acme-small", // ①
validationProvider = "Acme", // ②
)① 该提供方上最便宜的模型,用于 key 校验探测 ② 提供方名称;探测后作为
service.provider返回
该扩展函数与内置工厂一样能配合 detectProvider 使用:
kotlin
val service = detectProvider(
AnthropicModelFactory(apiKey = userKey),
OpenAiCompatibleModelFactory.openAi(userKey),
OpenAiCompatibleModelFactory.acme(userKey),
)使用已校验的服务
拿到 LlmService 之后,通过 withLlmService() 直接把它交给 PromptRunner 或 Ai:
java
LlmService<?> userLlm = ... // 来自 buildValidated() 或 detectProvider()
promptRunner
.withLlmService(userLlm)
.creating(MyOutput.class)
.create(messages);在内部,它走的是与其它所有 LLM 解析相同的模型选择路径(经由 PreResolvedModelSelectionCriteria)—— 不需要单独的解析路径。
错误处理
kotlin
try {
val service = detectProvider(
AnthropicModelFactory(apiKey = userKey),
OpenAiCompatibleModelFactory.openAi(userKey),
)
// 存储或使用 service
} catch (e: InvalidApiKeyException) {
// 返回 401 / 把错误呈现给用户
// 没有需要解包的 Spring AI 类型
}InvalidApiKeyException 位于 com.embabel.common.byok,不携带任何提供方专属的实现细节。
安全考量(Security considerations)
BYOK 工厂校验 key 并返回就绪的 LlmService —— key 的生命周期管理完全由调用方负责。
作为参考实现,Guide 项目把 key 只保存在服务端内存中(UserKeyStore)。 当一个 key 被校验通过后,客户端会收到一个 AES-256-GCM 加密的 blob —— 用一个只有服务端知道的密钥加密 —— 用于本地存储缓存。 blob 被盗也没有用,因为没有服务端的解密密钥。页面重新加载时客户端把 blob 发回,服务端解密并恢复内存中的 key。 key 从不写入磁盘或数据库。
如果你需要支持内置工厂未覆盖的提供方,请见使用 LLM。
通过 application.properties / application.yml 配置
你可以在常规的 Spring 配置文件中指定 Spring 配置、你自己的配置以及 Embabel 配置。 Profile 的使用会按预期工作。
自定义线程配置(Customizing threading)
Embabel 为 Agent 操作使用专用执行器。你可以用 agent-platform.properties 中的属性定制线程模型:
properties
# 翻转继承来的线程模型(virtual→platform,platform→virtual)
embabel.agent.platform.threading.override=false
# 当应用与 Embabel 使用相同线程模型时,共享应用的执行器
embabel.agent.platform.threading.shared=false线程模型与配置项的细节见异步模式与 Java 25。
自定义日志(Customizing logging)
你可以像在任何 Spring 应用里那样自定义日志。例如在 application.properties 中:
properties
logging.level.com.embabel.agent.a2a=DEBUG如果你的需求更复杂,也可以通过 logback-spring.xml 文件配置日志。见 Spring Boot 日志参考文档。
默认情况下,很多 Embabel 示例使用人格化日志体验,例如星球大战主题。 你可以在 application.properties 中相应调整来关闭它:
properties
embabel.agent.logging.personality=severance删除 embabel.agent.logging.personality 这个 key 即可关闭人格化日志。
由于所有日志都来自通过 AgenticEventListener 监听事件,你也可以很轻松地创建自己定制的日志。