Skip to content
Embabel 文档版本 0.3.0-SNAPSHOT 源快照 17782769 更新于 2026-09-29

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:把 Embabel LlmOptions 转换为 Spring AI ChatOptions
  • 知识截止日期(如有)
  • 任何额外的 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 监听事件,你也可以很轻松地创建自己定制的日志。

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。