结构化输出转换器概述
自2024年5月2日起,原有的 OutputParser 及其相关实现类(如 BeanOutputParser、ListOutputParser 和 MapOutputParser)已被正式弃用。取而代之的是新引入的 StructuredOutputConverter 体系,包括 BeanOutputConverter、ListOutputConverter 和 MapOutputConverter。这些新组件是原有类的直接替代方案,功能保持一致,但命名更加准确。
此次变更不仅是为了与 Spring 框架中的 org.springframework.core.convert.converter 包命名规范统一,也旨在提升语义清晰度——因为原“解析”一词容易引起误解,实际上该过程更偏向于“转换”而非“解析”。同时,新的实现还带来了部分功能增强和使用上的优化。
结构化输出的重要性
大语言模型(LLM)生成结构化输出的能力对于依赖稳定数据格式的下游应用至关重要。开发者通常希望将AI模型返回的文本结果快速转化为标准数据格式,例如 JSON、XML 或 Java 对象,以便无缝集成到后续业务逻辑中。
Spring AI 提供的结构化输出转换器正是为此设计,它能够高效地将非结构化的文本响应转换为预定义的数据结构。如下图所示,整个流程围绕 LLM 的文本补全接口进行:
转换器的工作机制
在调用 LLM 前后,StructuredOutputConverter 发挥着关键作用,确保最终获得符合预期结构的输出结果。
- 调用前处理:转换器会自动将格式化指令附加到原始提示词末尾,为模型提供明确的输出结构指引。这些指令如同蓝图,引导模型按照指定格式组织响应内容。
- 调用后处理:当模型返回文本后,转换器会接管输出,将其解析并映射为目标结构化类型实例,如 Java 类、集合或键值对结构。
关于输出可靠性的说明
尽管 StructuredOutputConverter 努力确保输出的结构一致性,但无法完全保证模型始终遵循指令返回合规格式。在某些情况下,模型可能未能理解提示要求,或因训练偏差导致输出偏离预期结构。因此,建议在实际应用中加入额外的验证机制,以确认模型输出的有效性和完整性。
需要注意的是,StructuredOutputConverter 不适用于 LLM 工具调用场景,因为在工具调用模式下,系统本身已默认支持结构化输出,无需额外转换处理。
结构化输出 API 设计
核心接口 StructuredOutputConverter<T> 继承自 Spring 的 Converter<String, T> 并实现了 FormatProvider 接口,定义如下:
public interface StructuredOutputConverter<T> extends Converter<String, T>, FormatProvider {
}
其中 FormatProvider 接口用于提供格式说明:
public interface FormatProvider {
String getFormat();
}
该接口组合使得转换器既能提供格式指南,又能执行字符串到目标类型的转换。下图展示了使用该 API 时的数据流动路径:
格式指令的应用方式
格式化指令通常通过 PromptTemplate 插入到用户输入中,常见做法是在模板中预留占位符,并在构建提示时由转换器动态填充。示例如下:
StructuredOutputConverter outputConverter = ...;
String userInputTemplate = """
... user text input ....
{format}
""";
Prompt prompt = new Prompt(
PromptTemplate.builder()
.template(this.userInputTemplate)
.variables(Map.of(..., "format", this.outputConverter.getFormat()))
.build().createMessage()
);
上述代码中,“format” 占位符会被实际的格式要求替换,从而确保模型接收到完整的结构化输出指令。典型的指令可能包含以下内容:
- 你的回复必须采用 JSON 格式。
- JSON 数据结构应与以下 Java 类匹配:java.util.HashMap。
- 不要包含任何解释性文字,仅输出符合 RFC8259 标准的 JSON 内容,严格遵循指定格式。
通过这种方式,系统能够在不修改模型的前提下,有效引导其生成可被程序直接解析的结构化响应。
Converter<String, T> 的作用是将模型生成的文本输出转换为指定类型 T 的实例。
2.1 可用的转换器实现
Spring AI 当前提供了以下几种 OutputConverter 的具体实现:AbstractConversionServiceOutputConverter、AbstractMessageOutputConverter、BeanOutputConverter、MapOutputConverter 以及 ListOutputConverter。
AbstractConversionServiceOutputConverter:内置了一个已预配置的 GenericConversionService,可用于将大语言模型(LLM)的输出转换为目标格式。该类不提供默认的 FormatProvider 实现。
AbstractMessageOutputConverter:配备了预设的 MessageConverter,支持将 LLM 输出转化为所需结构。同样,此类也不包含默认的 FormatProvider 实现。
BeanOutputConverter:可通过指定一个 Java 类(如 POJO 或 record)或 ParameterizedTypeReference 进行初始化。该转换器会使用一个 FormatProvider 实现,指示 AI 模型生成符合从目标 Java 类推导出的 DRAFT_2020_12 JSON Schema 格式的响应内容,并借助 ObjectMapper 将返回的 JSON 数据反序列化为对应的 Java 对象实例。
MapOutputConverter:继承自 AbstractMessageOutputConverter,具备一个 FormatProvider 实现,用于引导 AI 模型输出符合 RFC8259 标准的 JSON 响应。同时,其内部实现了基于 MessageConverter 的机制,可将 JSON 载荷转换成 java.util.Map<String, Object> 类型的对象。
ListOutputConverter:扩展了 AbstractConversionServiceOutputConverter,内置针对逗号分隔列表输出场景的 FormatProvider 实现。在转换阶段,利用所配置的 ConversionService 将模型返回的纯文本解析为 java.util.List 实例。
三、转换器的实际应用
本节介绍如何使用上述各类转换器来获取结构化的模型输出结果。
3.1 Bean 输出转换器的使用示例
以下案例演示了如何通过 BeanOutputConverter 获取某位演员的参演电影列表。
首先定义一个用于承载演员及其作品信息的记录类:
record ActorsFilms(String actor, List<String> movies) { }
接着可以采用高级别的流式 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 = beanOutputConverter.getFormat();
String actor = "Tom Hanks";
String template = """
Generate the filmography of 5 movies for {actor}.
{format}
""";
Generation generation = chatModel.call(
PromptTemplate.builder()
.template(template)
.variables(Map.of("actor", actor, "format", format))
.build()
.create()
).getResult();
3.2 属性顺序的生成模式控制
在生成 JSON 模式时,可通过使用 @JsonPropertyOrder 注解决定属性的输出顺序。该注解能够覆盖类或记录中原始声明的字段顺序,确保指定属性按预设次序出现在最终的结构中。
例如,若希望在 ActorsFilms 记录中优先显示 actor 字段,随后才是 movies 列表,则可如下定义:
@JsonPropertyOrder({"actor", "movies"})
record ActorsFilms(String actor, List<String> movies) {}
此功能同时适用于普通 Java 类和 record 类型,提供更强的结构控制能力。
3.3 复杂泛型类型的处理
对于包含泛型的复杂类型结构,推荐使用 ParameterizedTypeReference 来准确描述目标类型。比如需要获取多位演员及其各自五部代表作的列表时,可以采用高级 API 方式:
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 接口实现相同逻辑:
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());
3.4 使用 MapOutputConverter 进行映射转换
当输出结果为键值结构而非固定对象时,可选用 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>>() {});
对应的低层级实现方式如下:
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("format", this.format))
.build()
.create();
以下代码示例演示了如何通过 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());
对于需要将输出解析为特定结构的情况,还可以使用 MapOutputConverter 来处理键值对形式的结果。例如,将一组数字 1 到 9 映射到名为 'numbers' 的键下:
Map<String, Object> result = ChatClient.create(chatModel)
.prompt(prompt -> prompt.text("Return a {subject} in the specified {format}")
.param("subject", "an array of numbers from 1 to 9 under the key name 'numbers'")
.param("format", this.format))
.call()
.entity(new MapOutputConverter(new DefaultConversionService()));
或者使用低级别接口进行构建:
MapOutputConverter mapOutputConverter = new MapOutputConverter(new DefaultConversionService());
String format = this.mapOutputConverter.getFormat();
String template = """
Return a {subject} in the specified {format}
""";
Prompt prompt = PromptTemplate.builder()
.template(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());
支持的 AI 模型
以下 AI 模型已通过验证,能够支持 List、Map 以及 Bean 类型的结构化输出功能。
内置 JSON Schema 支持
部分 AI 模型提供了专用配置项,用于生成符合特定格式的结构化响应,通常以 JSON 形式呈现。
OpenAI 结构化输出
可确保模型返回严格遵循指定 JSON 模式的响应。可通过 spring.ai.openai.chat.options.responseFormat 配置选项,在以下两种模式中选择:
- JSON_OBJECT:保证输出为合法的 JSON 格式。
- JSON_SCHEMA:提供具体 schema,并确保模型返回内容与该 schema 完全匹配。
Azure OpenAI
提供 spring.ai.azure.openai.chat.options.responseFormat 参数用于设定输出格式。设置为 { "type": "json_object" } 即可启用 JSON 模式,确保生成的消息为有效 JSON。
Ollama
支持通过 spring.ai.ollama.chat.options.format 指定响应格式。目前唯一接受的值为 json,用于要求模型返回 JSON 结构的数据。
Mistral AI
提供 spring.ai.mistralai.chat.options.responseFormat 选项来控制响应格式。将其设为 { "type": "json_object" } 可开启 JSON 模式,确保模型输出为有效的 JSON 内容。


雷达卡


京公网安备 11010802022788号







