高层API .entity(…) 底层基于 StructuredOutputConverter(结构化输出转换器)抽象实现。大多数业务应用不会直接使用该底层API。 当遇到下面几种场景,才需要直接使用这套底层API:
- 需要解析内置转换器无法处理的输出(例如被Markdown代码块包裹的JSON);
- 需要处理非JSON格式,如YAML、CSV;
- 需要直接对接底层的
ChatModelAPI。
Spring AI 的结构化输出转换器负责把大模型文本输出转换为结构化对象。如下图所示,该组件围绕大模型文本补全接口工作:

转换器会在调用大模型之前、之后分别执行逻辑:
- 调用前:向提示词追加格式要求指令,引导模型生成目标结构的输出;
- 调用后:解析模型返回的文本,映射为Java结构化类型实例。
⚠️注意: StructuredOutputConverter 属于尽力而为的转换组件,并不能保证大模型一定返回符合要求的结构化内容。建议搭配模式校验(schema validation)一起使用,保障输出符合预期。 大模型工具调用(Tool Calling)不使用该转换器,工具调用本身原生就输出结构化数据。
结构化输出 API
StructuredOutputConverter 接口,用于将大模型文本输出映射为Java类、数组等结构化对象。接口定义:
|
1 2 3 4 5 6 7 8 |
public interface StructuredOutputConverter extends Converter<String, T>, FormatProvider { /** 返回大模型调用对应的JSON Schema;若无则返回 NO_JSON_SCHEMA(空字符串) */ default String getJsonSchema() { return NO_JSON_SCHEMA; } } |
该接口同时继承Spring的 Converter<String, T> 接口与 FormatProvider 接口:
|
1 2 3 |
public interface FormatProvider { String getFormat(); } |
下图展示了使用结构化输出API时的数据流:

FormatProvider 和 Converter<String, T> 的作用分别是:
FormatProvider:提供格式约束提示文本,告诉大模型应该输出什么样式;Converter<String, T>:将原始模型输出字符串,转换为目标泛型类型T。
示例:格式指令文本示例(会被追加到Prompt)
|
1 2 3 |
你的响应必须为JSON格式。 JSON数据结构需要与Java类 java.util.HashMap 保持一致。 不要增加任何解释文字,只输出严格符合RFC8259标准的JSON,不要有任何偏离。 |
格式指令一般通过 PromptTemplate 占位符追加到用户输入末尾:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
StructuredOutputConverter outputConverter = ... String userInputTemplate = """ ... 用户输入文本 .... {format} """; // 预留format占位符 Prompt prompt = new Prompt( PromptTemplate.builder() .template(this.userInputTemplate) .variables(Map.of(..., "format", this.outputConverter.getFormat())) .build().createMessage() ); |
getJsonSchema() 的作用
该默认方法是Spring AI 2.0新增。它是桥梁,让自定义转换器可以兼容 useProviderStructuredOutput()(厂商原生结构化输出)与 validateSchema()(模式校验自修复)能力。
- 如果实现该方法并返回Schema(一般委托给
BeanOutputConverter),那么两个开关均可正常生效; - 如果保留默认空实现,则这两个能力对当前转换器不生效。
内置转换器列表
Spring AI内置实现: AbstractConversionServiceOutputConverter、AbstractMessageOutputConverter、BeanOutputConverter、MapOutputConverter、ListOutputConverter,如下图:

AbstractConversionServiceOutputConverter内置预配置的GenericConversionService,用于将大模型输出转为目标对象;不提供默认的FormatProvider实现。AbstractMessageOutputConverter内置预配置的MessageConverter;不提供默认FormatProvider实现。BeanOutputConverter【最常用】 构造时传入Java实体类/Record或者ParameterizedTypeReference; 内部FormatProvider生成基于Java类推导的DRAFT_2020_12规范JSON Schema,引导模型输出对应JSON; 使用JsonMapper把返回JSON反序列化为Java对象。MapOutputConverter继承AbstractMessageOutputConverter; 提供格式指令让模型输出标准JSON,再通过MessageConverter把JSON解析为Map<String,Object>。ListOutputConverter继承AbstractConversionServiceOutputConverter; 适配逗号分隔列表输出场景,通过ConversionService将文本转为Java List集合。
转换器的使用方式
下面示例分别演示高层ChatClient流式API、底层ChatModel原生API两种用法。
BeanOutputConverter 实体输出转换器
下面的例子展示了如何使用BeanOutputConverter。
定义目标Record:
|
1 |
record ActorsFilms(String actor, List movies) {} |
✅高层ChatClient写法(日常业务首选)
|
1 2 3 4 5 |
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写法
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
BeanOutputConverter 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()); |
生成Schema时的属性顺序
BeanOutputConverter支持注解@JsonPropertyOrder,手动指定JSON Schema字段输出顺序,不受Java类/Record内字段定义顺序影响。
|
1 2 |
@JsonPropertyOrder({"actor", "movies"}) record ActorsFilms(String actor, List movies) {} |
提示: Record与普通Java Bean都支持该注解。
泛型Bean类型
使用ParameterizedTypeReference处理复杂泛型,例如List。
高层API:
|
1 2 3 4 |
List actorsFilms = ChatClient.create(chatModel).prompt() .user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.") .call() .entity(new ParameterizedTypeReference<List>() {}); |
底层API:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
BeanOutputConverter<List> outputConverter = new BeanOutputConverter<>( new ParameterizedTypeReference<List>() { }); 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 = this.outputConverter.convert(this.generation.getOutput().getText()); |
MapOutputConverter Map输出转换器
下面的代码片段演示如何使用 MapOutputConverter,将模型输出转换为 Map 内部存放的数字列表。
高层API:
|
1 2 3 4 5 |
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>>() {}); |
底层API:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
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输出转换器
下面的代码片段演示如何使用 ListOutputConverter,将模型输出转换为冰淇淋口味的列表。
高层API:
|
1 2 3 4 5 |
List flavors = ChatClient.create(chatModel).prompt() .user(u -> u.text("List five {subject}") .param("subject", "ice cream flavors")) .call() .entity(new ListOutputConverter(new DefaultConversionService())); |
底层API:
|
1 2 3 4 5 6 7 8 9 10 |
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 list = this.listOutputConverter.convert(this.generation.getOutput().getText()); |
自定义转换器
内置BeanOutputConverter比较严格,要求返回内容纯粹可直接解析的JSON。但大模型经常会把JSON包裹在Markdown代码块内:
|
1 2 3 4 |
Here's the filmography: ```json { "actor": "Tom Hanks", "movies": ["Forrest Gump", "Cast Away"] } ``` |
这种情况原生转换器会抛出异常。可以自定义宽松转换器,剔除markdown代码围栏,提取JSON再交给原转换器解析。
示例:LenientJsonOutputConverter宽松JSON转换器:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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():
|
1 2 3 4 |
ActorsFilms films = chatClient.prompt() .user("Generate the filmography for a random actor.") .call() .entity(new LenientJsonOutputConverter<>(ActorsFilms.class)); |
注意: 该实现把getJsonSchema()委托给底层Bean转换器,所以 validateSchema()、useProviderStructuredOutput() 两个可靠性开关依旧可以正常工作。
非JSON格式处理
如果需要处理YAML、CSV等非JSON格式,需要完整实现StructuredOutputConverter接口:
- 自行编写
getFormat()返回格式提示词; - 自行编写
convert()解析逻辑; getJsonSchema()保持默认空实现。
⚠️注意: 此时厂商原生结构化输出、Schema校验重试两个能力会失效,仅使用提示词驱动模式。