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

25 · 使用 LLM(Working with LLMs)

Embabel 支持 Spring AI 所支持的任何 LLM。实际上,这几乎就是"任何 LLM"。

选择 LLM(Choosing an LLM)

Embabel 鼓励你为每一次 LLM 调用都思考一下模型选择,PromptRunner 接口让这件事很容易。 由于 Embabel 让你把智能体流程拆成多个动作步骤,每一步都可以用更小、更聚焦的提示词和更少的工具 —— 这意味着它可能可以用更小的 LLM 来完成。

考量因素:

  • 期望返回类型的复杂度:这通常是判断所需 LLM 质量的良好指标。 小模型很可能应付不了深层嵌套的返回结构。
  • 任务性质:不同 LLM 各有所长,请查阅相关文档。 你并不一定需要一个大而昂贵、几乎什么都在行的模型 —— 那会以你的钱包和环境为代价。
  • 所需工具调用的复杂度:简单工具调用没问题,但复杂的编排是"你需要一个强模型"的另一个信号。 (这也可能意味着:你应该用 Embabel GOAP 构建一个更精细的流程。)
  • 考虑试用跑在 Ollama 或 Docker 下的本地 LLM。

试错是你的朋友。 Embabel 让切换 LLM 很容易:先用"最便宜且可能可行"的那个,不行再换。

为小模型与本地模型调优(Tuning for Smaller and Local Models)

Embabel 的一个核心目标,是让智能体流程在全谱系 LLM 上都跑得好 —— 这样你就可以选择"最便宜、最小、或最私密"的那个能胜任的模型,而不是每次都伸手去拿前沿大模型。

较小的对话模型与前沿模型的行为不同,框架可以针对这些差异做补偿:

  • 工具调用后的静默失败:较弱的开放权重模型(例如 gpt-oss-20b、部分 Qwen 变体)在"不知道下一步怎么办"时, 会返回空白文本且不再发起工具调用。若不干预,工具循环会以空内容退出。 打开 embabel.agent.platform.toolloop.empty-response.max-retries: 1,框架会回灌一条合成的提醒消息,再给它一次机会。
  • 工具名混淆:小模型更经常用近似的名字调用工具。默认的 AutoCorrectionPolicy 会通过回灌 "你是不是想用 X?"的建议来处理;如果你的模型需要更多次尝试,调 embabel.agent.platform.toolloop.tool-not-found.max-retries。
  • 迭代余量:恢复是要花 LLM 调用的。如果你启用了重试策略,请把 embabel.agent.platform.toolloop.max-iterations 调大, 以免"需要多一轮往返"的一步耗尽预算。

这些设置默认关闭,所以既有的、使用强模型的部署行为完全不变。 当你选的模型确实受益时,请按部署打开它们。

进阶:自定义 LLM 集成(Custom LLM Integration)

如果你想在运行期使用用户自带的 key 接入标准提供方(Anthropic、OpenAI、DeepSeek、Mistral), 请见 BYOK —— 那是 BYOK 场景的推荐路径。

本节讲的是:为 Embabel 尚未支持的提供方,从零实现 LlmMessageSender。

Embabel 的工具循环是框架无关的,你可以通过实现 LlmMessageSender 接口来接入任意 LLM 提供方。适用于:

  • 你想用 Spring AI 不支持的 LLM 提供方;
  • 你需要自定义的请求/响应处理;
  • 你要接入专有的或内部的 LLM 服务。

LlmMessageSender 接口

核心抽象就是这个函数式接口:

java
@FunctionalInterface
public interface LlmMessageSender {
    LlmMessageResponse call(
            List<Message> messages,
            List<Tool> tools
    );
}

实现体只负责发起一次 LLM 推理调用并返回响应。 重要的是:它不执行工具 —— 只返回 LLM 提出的工具调用请求,工具执行由 Embabel 的 DefaultToolLoop 负责。

对工具执行还有更进阶的控制 —— 工具可以并行执行,由 ParallelToolLoop 控制。要启用它:

properties
embabel.agent.platform.toolloop.type=parallel

完整的工具循环配置项见 ToolLoopConfiguration。

工具名未找到的恢复策略(Tool-Not-Found Recovery Policy)

当 LLM 用一个不存在的名字调用工具时,行为由 ToolNotFoundPolicy 控制。内置两种策略:

  • AutoCorrectionPolicy(默认) —— 把错误回灌给 LLM 让它自我纠正。 用大小写不敏感的模糊匹配为"幻觉出来的工具名"提供纠正建议(例如 ragbot_vectorSearch → 建议 vectorSearch), 多个候选时会全部列出。连续 3 次失败后抛出 ToolNotFoundException。
  • ImmediateThrowPolicy —— 立即抛出 ToolNotFoundException。

系统级默认是 AutoCorrectionPolicy,以带 @ConditionalOnMissingBean 的 Spring bean 提供。 要全局覆盖,请定义你自己的 ToolNotFoundPolicy bean。

要按每次交互控制,用 PromptRunner 上的 withToolNotFoundPolicy():

java
promptRunner
        .withToolNotFoundPolicy(new AutoCorrectionPolicy(5))
        .creating(MyOutput.class)
        .create(messages);

也可以通过实现 ToolNotFoundPolicy 接口来自定义策略:

kotlin
class MyEditDistancePolicy : ToolNotFoundPolicy {
    override fun handle(requestedName: String, availableTools: List<Tool>): ToolNotFoundAction {
        // 自定义恢复逻辑,例如基于编辑距离的匹配
        ...
    }
}

响应类型(Response Types)

LlmMessageResponse 包含:

  • message:LLM 的响应,表示为 Embabel 的 Message;
  • textContent:响应中的文本内容;
  • usage:可选的 token 用量信息。

对于包含工具调用的响应,请返回 AssistantMessageWithToolCalls:

java
public record ToolCall(
        String id,        // 该工具调用的唯一标识
        String name,      // 要调用的工具名
        String arguments  // 工具的 JSON 参数
) {}

示例:自定义 LLM 提供方

下面是为一个假想的、基于 HTTP 的 LLM API 实现 LlmMessageSender 的例子:

java
public class MyCustomLlmMessageSender implements LlmMessageSender {

    private final HttpClient httpClient;
    private final String apiKey;
    private final String model;

    public MyCustomLlmMessageSender(HttpClient httpClient, String apiKey, String model) {
        this.httpClient = httpClient;
        this.apiKey = apiKey;
        this.model = model;
    }

    @Override
    public LlmMessageResponse call(List<Message> messages, List<Tool> tools) {
        // 把 Embabel 消息转换成你的 API 格式
        List<Map<String, Object>> apiMessages = messages.stream()
                .map(message -> Map.<String, Object>of(
                        "role", message.getRole().name().toLowerCase(),
                        "content", message.getTextContent()
                ))
                .toList();

        // 把工具定义转换成你的 API 格式
        List<Map<String, Object>> apiTools = tools.stream()
                .map(tool -> Map.<String, Object>of(
                        "name", tool.getDefinition().getName(),
                        "description", tool.getDefinition().getDescription(),
                        "parameters", tool.getDefinition().getInputSchema().jsonSchema()
                ))
                .toList();

        // 发起 API 请求(用你偏好的 HTTP 客户端)
        MyApiResponse responseBody = httpClient.post("https://api.my-llm.com/chat")
                .header("Authorization", "Bearer " + apiKey)
                .body(Map.of(
                        "model", model,
                        "messages", apiMessages,
                        "tools", apiTools.isEmpty() ? null : apiTools
                ))
                .execute(MyApiResponse.class);

        // 检查 LLM 是否请求了工具调用
        List<ToolCall> toolCalls = null;
        if (responseBody.getToolCalls() != null) {
            toolCalls = responseBody.getToolCalls().stream()
                    .map(call -> new ToolCall(
                            call.getId(),
                            call.getFunction().getName(),
                            call.getFunction().getArguments()
                    ))
                    .toList();
        }

        Message embabelMessage;
        if (toolCalls == null || toolCalls.isEmpty()) {
            embabelMessage = new AssistantMessage(
                    responseBody.getContent() != null ? responseBody.getContent() : ""
            );
        } else {
            embabelMessage = new AssistantMessageWithToolCalls(
                    responseBody.getContent() != null ? responseBody.getContent() : "",
                    toolCalls
            );
        }

        Usage usage = null;
        if (responseBody.getUsage() != null) {
            usage = new Usage(
                    responseBody.getUsage().getPromptTokens(),
                    responseBody.getUsage().getCompletionTokens()
            );
        }

        return new LlmMessageResponse(embabelMessage, responseBody.getContent(), usage);
    }
}

创建 LlmService

要让你的自定义 LLM 能通过 Embabel 的 ModelProvider 使用,请实现 LlmService 接口:

java
public class MyCustomLlmService implements LlmService<MyCustomLlmService> {

    private final String name;
    private final String provider;
    private final HttpClient httpClient;
    private final String apiKey;
    private final LocalDate knowledgeCutoffDate;
    private final List<PromptContributor> promptContributors;
    private final PricingModel pricingModel;

    public MyCustomLlmService(
            String name,
            String provider,
            HttpClient httpClient,
            String apiKey,
            LocalDate knowledgeCutoffDate,
            PricingModel pricingModel) {
        this.name = name;
        this.provider = provider;
        this.httpClient = httpClient;
        this.apiKey = apiKey;
        this.knowledgeCutoffDate = knowledgeCutoffDate;
        this.promptContributors = knowledgeCutoffDate != null
                ? List.of(new KnowledgeCutoffDate(knowledgeCutoffDate))
                : List.of();
        this.pricingModel = pricingModel;
    }

    @Override
    public String getName() { return name; }

    @Override
    public String getProvider() { return provider; }

    @Override
    public LocalDate getKnowledgeCutoffDate() { return knowledgeCutoffDate; }

    @Override
    public List<PromptContributor> getPromptContributors() { return promptContributors; }

    @Override
    public PricingModel getPricingModel() { return pricingModel; }

    @Override
    public LlmMessageSender createMessageSender(LlmOptions options) {
        return new MyCustomLlmMessageSender(
                httpClient,
                apiKey,
                options.getModel() != null ? options.getModel() : name
        );
    }

    @Override
    public MyCustomLlmService withKnowledgeCutoffDate(LocalDate date) {
        return new MyCustomLlmService(name, provider, httpClient, apiKey, date, pricingModel);
    }

    @Override
    public MyCustomLlmService withPromptContributor(PromptContributor promptContributor) {
        var newContributors = new ArrayList<>(promptContributors);
        newContributors.add(promptContributor);
        return new MyCustomLlmService(
                name, provider, httpClient, apiKey, knowledgeCutoffDate,
                newContributors, pricingModel
        );
    }
}

然后把它注册为 Spring bean:

java
@Configuration
public class MyLlmConfiguration {

    @Bean
    public LlmService<?> myCustomLlm(
            HttpClient httpClient,
            @Value("${my-llm.api-key}") String apiKey) {
        return new MyCustomLlmService(
                "my-custom-model",
                "MyProvider",
                httpClient,
                apiKey,
                LocalDate.of(2024, 12, 1),
                null
        );
    }
}

该 bean 会被自动发现,并通过 ModelProvider 可用。

模型发现与选择的工作方式

应用启动时,ConfigurableModelProvider 会从 Spring 应用上下文中收集所有 LlmService bean。 你的自定义 LLM 通过你在 LlmService 实现上设置的 name 属性被匹配。

按名称:直接用 LlmService 的 name。它适用于 @LlmCall、ai.withLlm() 与 AgenticTool.withLlm():

java
// 在声明式动作里
@LlmCall(llm = "my-custom-model")
String myAction();

// 在命令式动作里
ai.withLlm("my-custom-model")
        .create("Tell me a joke", String.class);

按角色:在配置里把角色名映射到你的模型名,然后用 # 前缀引用:

yaml
embabel:
  models:
    default-llm: my-custom-model   # ①
    llms:
      best: my-custom-model        # ②
      cheapest: my-small-model     # ③

① 设置"未显式指定模型时"使用的默认 LLM ② 把 best 角色映射到你的自定义模型 ③ 把 cheapest 角色映射到另一个模型

然后用 # 引用角色:

java
// 按角色
@LlmCall(llm = "#best")
String myAction();

// 或编程方式
ai.withLlmByRole("best")
        .create("Tell me a joke", String.class);

如果 @LlmCall 或 withLlm() 中没有指定 LLM,就会使用配置里的 default-llm。

另一种方式:使用你自己的实现

如果你需要对 LLM 操作层本身有更多控制,可以扩展 ToolLoopLlmOperations:

java
public class MyCustomLlmOperations extends ToolLoopLlmOperations {

    private final HttpClient httpClient;
    private final String apiKey;

    public MyCustomLlmOperations(
            HttpClient httpClient,
            String apiKey,
            ModelProvider modelProvider,
            ToolDecorator toolDecorator,
            Validator validator) {
        super(modelProvider, toolDecorator, validator);
        this.httpClient = httpClient;
        this.apiKey = apiKey;
    }

    @Override
    protected LlmMessageSender createMessageSender(LlmService<?> llm, LlmOptions options) {
        return new MyCustomLlmMessageSender(
                httpClient,
                apiKey,
                options.getModel() != null ? options.getModel() : "default-model"
        );
    }
}

ToolLoopLlmOperations 基类提供了若干扩展点:

  • createMessageSender():创建 LLM 通信层;
  • createOutputConverter():把 LLM 响应解析成类型化对象;
  • sanitizeStringOutput():清洗原始文本响应;
  • emitCallEvent():发出可观测性事件。

关键实现说明

  • 你的 sender 不执行工具调用。只需返回工具调用请求 —— Embabel 的工具循环负责执行与续跑。
  • 同时处理"有工具"与"无工具"两种响应。纯文本返回 AssistantMessage;LLM 想调工具时返回 AssistantMessageWithToolCalls。
  • 可用时请带上 usage 信息。这让成本追踪与可观测性成为可能。
  • 消息类型很重要。工具循环期望特定消息类型:
    • UserMessage:用户输入
    • SystemMessage:system prompt
    • AssistantMessage:LLM 文本响应
    • AssistantMessageWithToolCalls:带工具请求的 LLM 响应
    • ToolResultMessage:工具执行后返回给 LLM 的结果

进阶:自定义 Embedding 服务

正如你可以接入自定义 LLM,你也可以实现不依赖 Spring AI 的自定义 embedding 服务。适用于:

  • 你想用 Spring AI 不支持的 embedding 提供方;
  • 你需要对 embedding 做自定义的前/后处理;
  • 你要接入专有的或内部的 embedding API。

EmbeddingService 接口

EmbeddingService 接口是框架无关的。与 SpringAiEmbeddingService 不同,自定义实现不必包装 Spring AI 的 EmbeddingModel:

java
public interface EmbeddingService {
    float[] embed(String text);

    List<float[]> embed(List<String> texts);

    int getDimensions();

    String getName();

    String getProvider();
}

示例:自定义 Embedding 提供方

下面是为一个基于 HTTP 的 embedding API 实现 EmbeddingService 的例子:

java
public class MyCustomEmbeddingService implements EmbeddingService {

    private final String name;
    private final String provider;
    private final int dimensions;
    private final HttpClient httpClient;
    private final String apiKey;

    public MyCustomEmbeddingService(
            String name,
            String provider,
            int dimensions,
            HttpClient httpClient,
            String apiKey) {
        this.name = name;
        this.provider = provider;
        this.dimensions = dimensions;
        this.httpClient = httpClient;
        this.apiKey = apiKey;
    }

    @Override
    public String getName() { return name; }

    @Override
    public String getProvider() { return provider; }

    @Override
    public int getDimensions() { return dimensions; }

    @Override
    public float[] embed(String text) {
        return embed(List.of(text)).get(0);
    }

    @Override
    public List<float[]> embed(List<String> texts) {
        MyEmbeddingResponse response = httpClient
                .post("https://api.my-embeddings.com/embed")
                .header("Authorization", "Bearer " + apiKey)
                .body(Map.of("texts", texts, "model", name))
                .execute(MyEmbeddingResponse.class);
        return response.getEmbeddings();
    }
}

注册为 Spring bean

把你的自定义 embedding 服务注册为 Spring bean,它就会被自动发现:

java
@Configuration
public class MyEmbeddingConfiguration {

    @Bean
    public EmbeddingService myCustomEmbeddings(
            HttpClient httpClient,
            @Value("${my-embeddings.api-key}") String apiKey) {
        return new MyCustomEmbeddingService(
                "my-custom-embeddings",
                "MyProvider",
                384,
                httpClient,
                apiKey
        );
    }
}

发现与选择

自定义 embedding 服务遵循与 LLM 相同的发现与选择模式。

按名称 —— 用 ai.withEmbeddingService() 加你实现里的 name:

java
ai.withEmbeddingService("my-custom-embeddings")
        .embed("Hello world");

按角色 —— 在配置里把角色名映射到你的 embedding 服务:

yaml
embabel:
  models:
    default-embedding-model: my-custom-embeddings  # ①
    embedding-services:
      cheapest: my-custom-embeddings               # ②

① 设置默认 embedding 服务 ② 把 cheapest 角色映射到你的自定义 embedding 服务

进阶:Anthropic 的显式缓存(Caching)

很多提供方的缓存是内部隐式管理的,而 Anthropic 公开了用于显式 prompt 缓存控制的 API。 这让你可以针对"长提示词、工具很多、或长对话"的应用优化成本与延迟。

动机

Anthropic 的 prompt 缓存特性带来显著收益:

  • 省钱:缓存读取的成本比常规输入 token 低 90%;
  • 降延迟:缓存内容处理更快;
  • 理想场景:长 system prompt、大量工具定义、多轮对话。

没有缓存时,每次 API 调用都要从头处理整个提示词。 有了缓存,重复内容(system prompt、工具、对话历史)就能被缓存并在请求之间复用。

工作原理

Anthropic 缓存的是提示词上下文的尾部(trailing portion)。 缓存通过内容哈希的精确匹配来识别 —— 被缓存部分的任何改动都会使缓存失效。

关键概念:

  • 缓存创建(Cache creation):首次见到该内容时写入缓存,相比常规输入 token 有 25% 的溢价(5 分钟 TTL);
  • 缓存读取(Cache reads):后续内容匹配的请求从缓存读取,成本为常规输入 token 的 10%;
  • 缓存 TTL:5 分钟(默认)或 1 小时(更贵,创建成本更高);
  • 最小尺寸:较老模型为 1024 token,Claude Sonnet 4.5 及更新模型为 4096 token。

缓存策略

Embabel 通过 AnthropicCachingConfig 提供多种缓存策略。

System Prompt 缓存 —— 缓存 system prompt,供多个请求复用:

java
AnthropicCachingConfig cachingConfig = new AnthropicCachingConfig();
cachingConfig.setSystemPrompt(true);

LlmOptions options = LlmOptions.withDefaultLlm();
options = withAnthropicCaching(options, cachingConfig);

工具缓存 —— 当工具很多、或工具 schema 很大时缓存工具定义:

java
AnthropicCachingConfig cachingConfig = new AnthropicCachingConfig();
cachingConfig.setTools(true);

LlmOptions options = LlmOptions.withDefaultLlm();
options = withAnthropicCaching(options, cachingConfig);

System + 工具缓存 —— 组合两种策略:

java
AnthropicCachingConfig cachingConfig = new AnthropicCachingConfig();
cachingConfig.setSystemPrompt(true);
cachingConfig.setTools(true);

LlmOptions options = LlmOptions.withDefaultLlm();
options = withAnthropicCaching(options, cachingConfig);

对话历史缓存 —— 为长多轮对话缓存对话历史:

java
AnthropicCachingConfig cachingConfig = new AnthropicCachingConfig();
cachingConfig.setConversationHistory(true);

LlmOptions options = LlmOptions.withDefaultLlm();
options = withAnthropicCaching(options, cachingConfig);

进阶配置

按消息类型设置最小内容长度 —— 依据内容长度控制哪些消息有资格被缓存:

java
AnthropicCachingConfig cachingConfig = new AnthropicCachingConfig();
cachingConfig.setSystemPrompt(true);
cachingConfig.messageTypeMinContentLength(MessageRole.SYSTEM, 1024);
cachingConfig.messageTypeMinContentLength(MessageRole.USER, 512);

LlmOptions options = LlmOptions.withDefaultLlm();
options = withAnthropicCaching(options, cachingConfig);

按消息类型设置 TTL —— 为每种消息类型设置缓存 TTL(默认为 5 分钟):

java
AnthropicCachingConfig cachingConfig = new AnthropicCachingConfig();
cachingConfig.setSystemPrompt(true);
cachingConfig.messageTypeTtl(MessageRole.SYSTEM, AnthropicCacheTtl.ONE_HOUR);

LlmOptions options = LlmOptions.withDefaultLlm();
options = withAnthropicCaching(options, cachingConfig);

读取缓存指标

Embabel 提供了扩展方法,用于从 Usage 对象读取 Anthropic 专属的缓存指标:

java
import static com.embabel.agent.config.models.anthropic.AnthropicUsage.*;

AssistantMessage response = promptRunner.respond(messages);
Usage usage = response.getUsage();

// 检查缓存是被创建还是被读取
boolean cacheCreated = hasAnthropicCacheCreation(usage);
boolean cacheRead = hasAnthropicCacheRead(usage);

// 取 token 计数
Integer creationTokens = anthropicCacheCreationTokens(usage);
Integer readTokens = anthropicCacheReadTokens(usage);

// 取用于日志的摘要字符串
String summary = anthropicCacheSummary(usage);
// 输出示例:"cache[creation=1061, read=0]"

最佳实践

  • 缓存长而稳定的内容:不常变化的 system prompt 与工具定义是理想候选;
  • 注意最小尺寸:内容必须达到最小 token 要求(视模型而定,1024 或 4096);
  • 监控缓存指标:用缓存扩展方法跟踪命中率、验证节省效果;
  • 权衡 TTL 与成本:1 小时 TTL 创建成本更高,但对长会话更好;
  • 上线前先测试:缓存行为会随提示词结构与使用模式而变化。

参考

Anthropic prompt 缓存的完整细节见:https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching

进阶特性:原生结构化输出(Native Structured Output)

原生结构化输出让模型提供方直接强制一个 JSON Schema,而不是只依赖提示词指令与 Embabel 的对象构造。 schema 生成与对象绑定仍然由 Embabel 负责;原生路径只是一个优化与兼容特性, 仅在该次调用被显式支持且安全时才会使用。

载荷格式(Payload Format)

对 OpenAI 兼容提供方,Spring AI 会把 Embabel 的 schema 包装进 response_format 的 JSON schema 载荷里。 例如一个 MonthItem 对象可能产生这样的请求片段:

json
{
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "MonthItem",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "temperature": { "type": "integer" }
        },
        "required": ["name", "temperature"],
        "additionalProperties": false
      }
    }
  }
}

OpenAI 会在生成之前校验这个 schema。 如果 schema 对原生路径不合法,请求会在模型产出任何输出之前就失败。

各方职责(Participants Roles)

  • Embabel 生成 JSON Schema、决定该次调用是否启用原生路径,并把最终响应解析成所请求的对象;
  • Spring AI 把 Embabel 的 schema 包装成提供方专属的格式选项(例如 OpenAI 的 response_format),并序列化 HTTP 请求;
  • 提供方(例如 OpenAI)校验原生结构化输出载荷,并返回符合所接受 schema 的输出。

配置与 API

原生结构化输出会在模型元数据中声明:

yaml
native_support_defaults:
  structured_output:
    supported: true
    strategy: response_format
    strict: false
    prompt_instructions: include

对某次具体调用,通过 LlmOptions 启用:

java
PromptRunner runner = ai.withLlm(
        withNativeStructuredOutput(
                LlmOptions.fromCriteria(DefaultModelSelectionCriteria.INSTANCE),
                NativeStructuredOutputMode.ENABLED
        )
);

MonthItem result = runner.createObject(prompt, MonthItem.class);
  • NativeStructuredOutputMode.ENABLED:在模型能力与 schema 兼容性允许时,尝试原生路径;
  • NativeStructuredOutputMode.DISABLED:强制走 Embabel 的常规对象构造路径;
  • NativeStructuredOutputMode.DEFAULT:由 Embabel 根据模型能力、API 形态与 schema 兼容性自行决定。

对 Java 对象,请显式标记必填的引用字段:

java
static class MonthItem {
    @JsonProperty(required = true)
    private String name;

    @JsonProperty(required = true)
    private Integer temperature;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getTemperature() {
        return temperature;
    }

    public void setTemperature(Integer temperature) {
        this.temperature = temperature;
    }
}

限制(Limitations)

  • 必填字段:OpenAI 的原生结构化输出要求 required 包含每一个属性。 Java 的可选引用字段除非被注解(例如 @JsonProperty(required = true) 或 @NotNull),否则不会被视为必填。
  • 可选值:提供方原生的"可选"通常需要可空的必填属性(例如 type: ["string", "null"]), 而不是把该属性从 required 里去掉。
  • 对象数组:部分提供方(据 Spring AI 文档,包括 OpenAI)原生不支持对象数组。 对不支持的形状,Embabel 会回退到常规的对象构造。
  • 流式:Spring AI 目前不支持原生结构化输出的流式模式。
  • ...IfPossible API:createObjectIfPossible 及相关路径使用 MaybeReturn 语义,始终留在 Embabel 的回退对象构造路径上。
  • 提供方覆盖范围:OpenAI 兼容的 response_format 是主要受支持的路径; Anthropic 的原生结构化输出目前默认关闭,直到其语义被验证为止。

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