输出转换器
高层 .entity(…) API 建立在 StructuredOutputConverter 抽象之上。大多数应用程序不会直接接触到它。当你需要以下情况时,再考虑使用这个更底层的 API:
-
解析内置转换器拒绝的输出(例如,包裹在 Markdown 代码围栏中的 JSON);
-
生成非 JSON 格式,如 YAML 或 CSV;
-
直接使用转换器作用于低级别的 ChatModel API。
Spring AI Structured Output Converters 将 LLM 的输出转换为结构化格式。如下图所示,该方法围绕 LLM 文本补全接口进行工作:

结构化输出转换器在调用 LLM 之前和之后发挥作用。在调用之前,转换器会在提示词中追加格式说明,以引导模型生成期望的输出结构。在调用之后,转换器会解析模型的文本输出,并将其映射为结构化类型的实例。
StructuredOutputConverter 旨在尽力将模型输出转换为结构化输出。AI 模型不保证返回请求的结构化输出。建议结合模式验证使用,以确保模型输出符合预期。
StructuredOutputConverter 不用于 LLM Tool Calling,因为该功能默认本身就提供结构化输出。
结构化输出 API
StructuredOutputConverter 接口允许你获取结构化输出,例如将基于文本的 AI 模型输出映射为 Java 类或值数组。接口定义如下:
public interface StructuredOutputConverter<T> extends Converter<String, T>, FormatProvider {
/**
* Returns the JSON schema for the structured output of an LLM call,
* or NO_JSON_SCHEMA ("") if not available.
*/
default String getJsonSchema() {
return NO_JSON_SCHEMA;
}
}
它结合了 Spring 的 Converter<String, T> 接口和 FormatProvider 接口:
public interface FormatProvider {
String getFormat();
}
下图展示了使用结构化输出 API 时的数据流。

FormatProvider 为 AI 模型提供特定的格式指导,使其能够生成可通过 Converter 转换为指定目标类型 T 的文本输出。以下是此类格式指令的示例:
Your response should be in JSON format.
The data structure for the JSON should match this Java class: java.util.HashMap
Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation.
格式说明通常通过 PromptTemplate 追加到用户输入的末尾,如下所示:
StructuredOutputConverter outputConverter = ...
String userInputTemplate = """
... user text input ....
{format}
"""; // user input with a "format" placeholder.
Prompt prompt = new Prompt(
PromptTemplate.builder()
.template(this.userInputTemplate)
.variables(Map.of(..., "format", this.outputConverter.getFormat())) // replace the "format" placeholder with the converter's format.
.build().createMessage()
);
Converter<String, T> 负责将模型的输出文本转换为指定类型 T 的实例。
getJsonSchema() 的作用
getJsonSchema() 是在 2.0 版本中作为 StructuredOutputConverter 的默认方法添加的,它是转换器参与 useProviderStructuredOutput() 和 validateSchema() 的桥梁。实现该方法以返回你的 schema(通常是通过委托给 BeanOutputConverter),这两个开关即可生效;若保持默认实现,这两个开关对该转换器将成为无效操作(no-ops)。
可用转换器
Spring AI 提供了 AbstractConversionServiceOutputConverter、AbstractMessageOutputConverter、BeanOutputConverter、MapOutputConverter 和 ListOutputConverter 实现:

-
AbstractConversionServiceOutputConverter<T>- 提供一个预配置的 GenericConversionService,用于将 LLM 输出转换为所需格式。未提供默认的FormatProvider实现。 -
AbstractMessageOutputConverter<T>- 提供一个预配置的 MessageConverter,用于将 LLM 输出转换为所需格式。未提供默认的FormatProvider实现。 -
BeanOutputConverter<T>- 通过指定的 Java 类(例如 Bean)或 ParameterizedTypeReference 进行配置。此转换器采用一个FormatProvider实现,引导 AI 模型生成符合基于指定 Java 类生成的DRAFT_2020_12JSON Schema 的 JSON 响应。随后,它利用JsonMapper将 JSON 输出反序列化为目标类的 Java 对象实例。 -
MapOutputConverter- 扩展了AbstractMessageOutputConverter的功能,包含一个引导 AI 模型生成符合 RFC8259 标准的 JSON 响应的FormatProvider实现。此外,它还包含一个转换器实现,利用提供的MessageConverter将 JSON 负载转换为java.util.Map<String, Object>实例。 -
ListOutputConverter- 扩展了AbstractConversionServiceOutputConverter,并包含一个针对逗号分隔列表输出定制的FormatProvider实现。该转换器实现使用提供的ConversionService将模型文本输出转换为java.util.List。
使用转换器
以下各章节展示了如何使用可用的转换器生成结构化输出。每种方式均分别展示了使用高级 ChatClient API 和低级 ChatModel API 的实现。
Bean Output Converter
以下示例展示了如何使用 BeanOutputConverter 为演员生成片目。
表示该演员电影作品的目标记录:
record ActorsFilms(String actor, List<String> movies) {
}
以下是如何使用高级、流式(fluent)的 ChatClient API 应用 BeanOutputConverter 的方法:
ActorsFilms actorsFilms = ChatClient.create(chatModel).prompt()
.user(u -> u.text("Generate the filmography of 5 movies for {actor}.")
.param("actor", "Tom Hanks"))
.call()
.entity(ActorsFilms.class);
或直接使用底层的 ChatModel API:
BeanOutputConverter<ActorsFilms> beanOutputConverter =
new BeanOutputConverter<>(ActorsFilms.class);
String format = this.beanOutputConverter.getFormat();
String actor = "Tom Hanks";
String template = """
Generate the filmography of 5 movies for {actor}.
{format}
""";
Generation generation = chatModel.call(
PromptTemplate.builder().template(this.template).variables(Map.of("actor", this.actor, "format", this.format)).build().create()).getResult();
ActorsFilms actorsFilms = this.beanOutputConverter.convert(this.generation.getOutput().getText());
生成模式中的属性顺序
BeanOutputConverter 通过 @JsonPropertyOrder 注解支持在生成的 JSON schema 中自定义属性顺序。该注解允许你指定属性在 schema 中出现的精确顺序,无论它们在类或记录中的声明顺序如何。
例如,为确保 ActorsFilms 记录中的属性具有特定的顺序:
@JsonPropertyOrder({"actor", "movies"})
record ActorsFilms(String actor, List<String> movies) {}
此注解既适用于记录,也适用于常规 Java 类。
通用 Bean 类型
使用 ParameterizedTypeReference 构造函数来指定更复杂的目标类结构。例如,表示演员列表及其作品:
List<ActorsFilms> actorsFilms = ChatClient.create(chatModel).prompt()
.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
.call()
.entity(new ParameterizedTypeReference<List<ActorsFilms>>() {});
或者直接使用低级别的 ChatModel API:
BeanOutputConverter<List<ActorsFilms>> outputConverter = new BeanOutputConverter<>(
new ParameterizedTypeReference<List<ActorsFilms>>() { });
String format = this.outputConverter.getFormat();
String template = """
Generate the filmography of 5 movies for Tom Hanks and Bill Murray.
{format}
""";
Prompt prompt = PromptTemplate.builder().template(this.template).variables(Map.of("format", this.format)).build().create();
Generation generation = chatModel.call(this.prompt).getResult();
List<ActorsFilms> actorsFilms = this.outputConverter.convert(this.generation.getOutput().getText());
映射输出转换器
以下代码示例展示了如何使用 MapOutputConverter 将模型输出转换为一个包含数字列表的 map。
Map<String, Object> result = ChatClient.create(chatModel).prompt()
.user(u -> u.text("Provide me a List of {subject}")
.param("subject", "an array of numbers from 1 to 9 under they key name 'numbers'"))
.call()
.entity(new ParameterizedTypeReference<Map<String, Object>>() {});
或直接使用低级别 ChatModel API:
MapOutputConverter mapOutputConverter = new MapOutputConverter();
String format = this.mapOutputConverter.getFormat();
String template = """
Provide me a List of {subject}
{format}
""";
Prompt prompt = PromptTemplate.builder().template(this.template)
.variables(Map.of("subject", "an array of numbers from 1 to 9 under they key name 'numbers'", "format", this.format)).build().create();
Generation generation = chatModel.call(this.prompt).getResult();
Map<String, Object> result = this.mapOutputConverter.convert(this.generation.getOutput().getText());
列表输出转换器
以下代码片段展示了如何使用 ListOutputConverter 将模型输出转换为冰淇淋口味的列表。
List<String> flavors = ChatClient.create(chatModel).prompt()
.user(u -> u.text("List five {subject}")
.param("subject", "ice cream flavors"))
.call()
.entity(new ListOutputConverter(new DefaultConversionService()));
或直接使用低层 ChatModel API:
ListOutputConverter listOutputConverter = new ListOutputConverter(new DefaultConversionService());
String format = this.listOutputConverter.getFormat();
String template = """
List five {subject}
{format}
""";
Prompt prompt = PromptTemplate.builder().template(this.template).variables(Map.of("subject", "ice cream flavors", "format", this.format)).build().create();
Generation generation = this.chatModel.call(this.prompt).getResult();
List<String> list = this.listOutputConverter.convert(this.generation.getOutput().getText());
自定义转换器
内置的 BeanOutputConverter 很严格:它要求模型的响应必须是可解析的 JSON,没有任何例外。但模型经常会在 JSON 外包裹 Markdown 代码围栏:
Here's the filmography:
```json
{ "actor": "Tom Hanks", "movies": ["Forrest Gump", "Cast Away"] }
```
BeanOutputConverter 会在 “Here’s” 的第一个 H 处抛出异常。常见的修复方式是使用自定义转换器,先去除围栏并提取 JSON,然后再委托给默认解析器:
public class LenientJsonOutputConverter<T> implements StructuredOutputConverter<T> {
private static final Pattern FENCE = Pattern.compile("```(?:json)?\\s*([\\s\\S]*?)```");
private final BeanOutputConverter<T> delegate;
public LenientJsonOutputConverter(Class<T> targetType) {
this.delegate = new BeanOutputConverter<>(targetType);
}
@Override public String getFormat() { return delegate.getFormat(); }
@Override public String getJsonSchema() { return delegate.getJsonSchema(); }
@Override
public T convert(String source) {
var matcher = FENCE.matcher(source);
String json = matcher.find() ? matcher.group(1).trim() : source.trim();
return delegate.convert(json);
}
}
将其传递给 .entity(…),而不是传递给 Class:
ActorsFilms films = chatClient.prompt()
.user("Generate the filmography for a random actor.")
.call()
.entity(new LenientJsonOutputConverter<>(ActorsFilms.class));
由于该转换器将 getJsonSchema() 委托给底层的 BeanOutputConverter,因此两个可靠性开关均正常工作 —— validateSchema() 和 useProviderStructuredOutput() 针对默认转换器所使用的同一模式进行操作。参见 getJsonSchema() 的作用。
非 JSON 格式
对于超出 JSON 覆盖范围的数据格式 —— 用于配置生成的 YAML 和用于数据提取的 CSV —— 请从头实现 StructuredOutputConverter:编写你自己的 getFormat() 提示词(prompt)以及你自己的 convert(…) 解析器。保持 getJsonSchema() 为默认值,此时两个可靠性开关(reliability switches)均处于禁用状态 —— 基于提示词(prompt-based)的路径将以与内置格式相同的方式运行。