Skip to content
Spring AI 文档版本 2.1.0-SNAPSHOT 源快照 03eb04f4 更新于 2026-10-16

结构化输出

大语言模型是文本进、文本出的系统。 一旦下游代码需要依据某个字段做路由、持久化某个值、或根据某个结果做分支,这段文本就必须变成一个有类型的记录。 结构化输出弥合了这一鸿沟:模型被引导产出符合某个模式的文本,应用再把它解析回一个有类型的对象,代码库的其他部分可以像对待任何其他领域类型一样对待它。

Spring AI 通过 .entity(...) 在 ChatClient 流式 API 上直接暴露结构化输出。 你为你想要的返回形状定义一个 Java 类型,其余交给 Spring AI:JSON 模式由你的类型生成、模型被指示遵循该模式、响应再被反序列化为你的类型。

本页介绍高层的 ChatClient 路径。 关于可靠性开关和更底层 API,参见:

有类型的响应

为你想要的返回形状定义一个 Java record:

java
record ActorsFilms(String actor, List<String> movies) {}

让 ChatClient 来填充它。 不要用 .content()(它返回原始文本回复)来结束调用,而是用 .entity(...) 结束并传入你的目标类型:

java
ActorsFilms films = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .call()
    .entity(ActorsFilms.class);

结果是一个有类型的 ActorsFilms,你可以把它传给代码库的其他部分:

java
films.actor();     // "Tom Hanks"
films.movies();    // ["Forrest Gump", "Cast Away", ...]

在幕后,Spring AI 做了三件事:一个模式生成器把你的 ActorsFilms record 转换为一个 JSON 模式、该模式被追加到提示词的系统上下文中、而模型的 JSON 回答则交给一个类型转换器,把它解析回你的 record。

这对 Spring AI 支持的每一个模型都有效——它不带有特定服务商的耦合。

NOTE

.entity(...) 仅适用于 .call()。 有类型的解析需要完整的响应,因此它在流式路径上不可用(.stream() 返回的是文本块,而非有类型的对象)。 这适用于本页的每一种变体——Class、ParameterizedTypeReference、自定义转换器,无论是否启用可靠性开关。

默认的 .entity(...) 调用没有任何保证。 模型是被要求产出匹配该模式的 JSON,而非被强制。 多数时候它会照做;但有时它会多返回一个字段、漏掉一个必填字段、或把 JSON 包裹在文字里,于是解析器抛出异常。 下面的两个开关正是为解决这个问题而生。

泛型类型:列表、映射及更多

.entity(Class) 用于具体类。 对于泛型类型——List<ActorsFilms>、Map<String, ActorsFilms>——请使用 ParameterizedTypeReference:

java
List<ActorsFilms> films = chatClient.prompt()
    .user("Generate filmographies for three random actors.")
    .call()
    .entity(new ParameterizedTypeReference<List<ActorsFilms>>() {});

可靠性开关:EntityParamSpec

所有 .entity(...)(以及 .responseEntity(...))重载都接受一个可选的 Consumer<EntityParamSpec>,用于启用两个相互独立、可组合的行为。

遇到格式错误的输出不失败:validateSchema()

validateSchema() 会开启一个自我纠正的重试循环:Spring AI 依据实体的模式校验响应,如果校验失败,具体的错误会被追加到提示词中并重新发起调用——默认最多 3 次尝试。

java
ActorsFilms films = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .call()
    .entity(ActorsFilms.class, spec -> spec.validateSchema());

关于重试循环如何工作以及如何定制,参见 模式校验与自我纠正。

更强的上游保证:useProviderStructuredOutput()

useProviderStructuredOutput() 把模式作为 API 层面的约束发送给服务商,从而由服务商的运行时来强制符合性,而不是依赖提示词里的指示。

java
ActorsFilms films = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .call()
    .entity(ActorsFilms.class, spec -> spec.useProviderStructuredOutput());

这要求底层模型支持原生的结构化输出。 关于受支持的服务商与限制,参见 服务商原生的结构化输出。

两者结合

这两个开关解决的是不同的问题,并且能自然地组合:

java
ActorsFilms films = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .call()
    .entity(ActorsFilms.class, spec -> spec
        .useProviderStructuredOutput()
        .validateSchema());

useProviderStructuredOutput() 通过在 API 层面约束模型,把输出格式错误的几率降到最低。 validateSchema() 则捕获剩下的情形——服务商的边缘情况、推理模型的怪癖——并自动纠正它们。 当下游代码无法容忍形状漂移时,两者都用上。

获取完整响应

.entity(...) 只返回解析后的对象。 如果你还需要底层的 ChatResponse——用于令牌用量、可观测性元数据,或实体之外的任何信息——请使用 .responseEntity(...):

java
ResponseEntity<ChatResponse, ActorsFilms> result = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .call()
    .responseEntity(ActorsFilms.class);

ActorsFilms films = result.entity();
ChatResponse raw = result.response();
long totalTokens = raw.getMetadata().getUsage().getTotalTokens();

它与 .entity(...) 拥有一组相同的重载——Class、ParameterizedTypeReference、自定义 StructuredOutputConverter,以及 EntityParamSpec 消费者均适用。

自定义与非 JSON 输出

当内置的 JSON 解析不够用时——模型把 JSON 包裹在 markdown 围栏里,或者你需要 YAML、CSV 这类非 JSON 格式——就把你自己的 StructuredOutputConverter<T> 传给 .entity(...)。 参见 输出转换器。

速查表

你需要用法
默认——适用于每一个服务商.entity(Type.class)
泛型类型如 List<T>、Map<K,V>.entity(new ParameterizedTypeReference<...>() {})
遇到格式错误的输出不失败.entity(Type.class, spec -> spec.validateSchema())
来自服务商的更强上游保证.entity(Type.class, spec -> spec.useProviderStructuredOutput())
两者都用——请求约束 + 响应重试.entity(Type.class, spec -> spec.useProviderStructuredOutput().validateSchema())
连同实体一起获取令牌用量/元数据.responseEntity(...)(同样的重载)
模型把 JSON 包裹在 markdown 围栏里,或非 JSON 格式实现 StructuredOutputConverter<T> 并把它传给 .entity(...)
流式响应不支持——.entity(...) 仅适用于 .call();.stream() 返回文本块而非有类型的对象

TIP

结构化输出是把模型输出转换为结构化类型的一种尽力而为的做法。

不保证模型一定会返回所请求的结构。 当正确性很重要时,请使用 validateSchema() 和/或 useProviderStructuredOutput()。

NOTE

StructuredOutputConverter 不用于 LLM 工具调用,因为该特性本身就默认提供结构化输出。

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