结构化输出转换器
大语言模型(LLM)生成结构化输出的能力,对于依赖可靠解析输出值的下游应用至关重要。开发者希望快速将AI模型的返回结果转换为JSON、XML或Java类等数据类型,以便传递给应用的其他函数和方法使用。
Spring AI 结构化输出转换器可帮助将LLM输出转换为结构化格式。如下图所示,该方案围绕LLM文本补全接口运行:
结构化输出转换器架构
使用通用补全API从大语言模型(LLM)生成结构化输出,需要谨慎处理输入和输出。结构化输出转换器在LLM调用前后均发挥关键作用,确保生成符合预期的输出结构。
在调用LLM之前,转换器会将格式指令追加到提示词中,为模型提供明确的生成指导。这些指令相当于蓝图,引导模型的响应符合指定格式。
随着越来越多的AI模型原生支持结构化输出,你可以通过 AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT 使用原生结构化输出功能。该方案直接将生成的JSON Schema用于模型的原生结构化输出API,无需在提示词中添加前置格式指令,结果更可靠。
在调用LLM之后,转换器接收模型的输出文本,并将其转换为结构化类型的实例。该转换过程包括解析原始文本输出,并将其映射为对应的结构化数据表示,如JSON、XML或领域专用数据结构。
注意:结构化输出转换器会尽最大努力将模型输出转换为结构化格式,但不保证AI模型一定按要求返回结构化输出。模型可能无法理解提示词,或无法按要求生成结构化输出。建议实现校验机制,确保模型输出符合预期。
注意:结构化输出转换器不适用于LLM工具调用,因为该功能默认已原生提供结构化输出。
结构化输出API
StructuredOutputConverter 接口支持从基于文本的AI模型输出中获取结构化结果,例如将输出映射为Java类或值数组。接口定义如下:
public interface StructuredOutputConverterextends Converter, FormatProvider {
}它组合了Spring的 Converter 接口和 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}
"""; // 包含"format"占位符的用户输入
Prompt prompt = new Prompt(
PromptTemplate.builder()
.template(this.userInputTemplate)
.variables(Map.of(..., "format", this.outputConverter.getFormat())) // 用转换器的格式替换占位符
.build().createMessage()
);Converter 负责将模型的输出文本转换为指定类型T的实例。
可用转换器
目前,Spring AI提供以下实现类:AbstractConversionServiceOutputConverter、AbstractMessageOutputConverter、BeanOutputConverter、MapOutputConverter 和 ListOutputConverter。
结构化输出类层级结构
AbstractConversionServiceOutputConverter:提供预配置的GenericConversionService,用于将LLM输出转换为目标格式。无默认的FormatProvider实现。AbstractMessageOutputConverter:提供预配置的MessageConverter,用于将LLM输出转换为目标格式。无默认的FormatProvider实现。BeanOutputConverter:配置指定的Java类(如Bean)或ParameterizedTypeReference,使用FormatProvider指导AI模型生成符合DRAFT_2020_12标准、基于指定Java类生成的JSON Schema的JSON响应。随后通过ObjectMapper将JSON反序列化为目标类的Java对象实例。MapOutputConverter:继承AbstractMessageOutputConverter,实现FormatProvider指导模型生成符合RFC8259标准的JSON响应,并通过MessageConverter将JSON负载转换为java.util.Map实例。ListOutputConverter:继承AbstractConversionServiceOutputConverter,实现适配逗号分隔列表输出的FormatProvider,并通过ConversionService将模型文本输出转换为java.util.List。
转换器使用方法
以下章节介绍如何使用现有转换器生成结构化输出。
Bean输出转换器
以下示例展示如何使用 BeanOutputConverter 生成演员的影视作品列表。
表示演员影视作品的目标记录类:
record ActorsFilms(String actor, Listmovies) {
}使用高阶流式 ChatClient API:
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:
BeanOutputConverterbeanOutputConverter =
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中的属性顺序,可指定属性在Schema中的显示顺序,不受类或记录中声明顺序的影响。
示例:为 ActorsFilms 记录指定属性顺序:
@JsonPropertyOrder({"actor", "movies"})
record ActorsFilms(String actor, Listmovies) {}该注解同时支持记录类和普通Java类。
泛型Bean类型
使用 ParameterizedTypeReference 构造器指定复杂的目标类结构。例如表示演员列表及其影视作品:
ListactorsFilms = ChatClient.create(chatModel).prompt()
.user("Generate the filmography of 5 movies for Tom Hanks and Bill Murray.")
.call()
.entity(new ParameterizedTypeReference>() {});或直接使用底层 ChatModel API:
BeanOutputConverter> outputConverter = new BeanOutputConverter<>(
new ParameterizedTypeReference>() { });
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();
ListactorsFilms = this.outputConverter.convert(this.generation.getOutput().getText());Map输出转换器
以下代码展示如何使用 MapOutputConverter 将模型输出转换为Map中的数字列表:
Mapresult = 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>() {});或直接使用底层 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();
Mapresult = this.mapOutputConverter.convert(this.generation.getOutput().getText());List输出转换器
以下代码展示如何使用 ListOutputConverter 将模型输出转换为冰淇淋口味列表:
Listflavors = 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();
Listlist = this.listOutputConverter.convert(this.generation.getOutput().getText());原生结构化输出
许多现代AI模型现已原生支持结构化输出,相比基于提示词的格式化方式,结果更可靠。Spring AI通过原生结构化输出功能支持该能力。
使用原生结构化输出时,BeanOutputConverter 生成的JSON Schema会直接发送到模型的结构化输出API,无需在提示词中添加格式指令。该方案具备以下优势:
更高可靠性:模型保证输出符合Schema规范
更简洁的提示词:无需追加格式指令
更好的性能:模型可内部优化结构化输出
使用原生结构化输出
启用原生结构化输出,使用 AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT 参数:
ActorsFilms actorsFilms = ChatClient.create(chatModel).prompt()
.advisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
.user("Generate the filmography for a random actor.")
.call()
.entity(ActorsFilms.class);也可通过 ChatClient.Builder 的 defaultAdvisors() 全局设置:
@Bean
ChatClient chatClient(ChatClient.Builder builder) {
return builder
.defaultAdvisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
.build();
}支持原生结构化输出的模型
当前支持原生结构化输出的模型:
OpenAI:GPT-4o及更高版本(支持JSON Schema)
Anthropic:Claude 3.5 Sonnet及更高版本
Vertex AI Gemini:Gemini 1.5 Pro及更高版本
Mistral AI:Mistral Small及更高版本(支持JSON Schema)
部分AI模型(如OpenAI)不支持顶层对象数组的原生输出。这种情况下,可使用Spring AI默认的结构化输出转换(不启用原生结构化输出顾问)。
内置JSON模式
部分AI模型提供专用配置选项生成结构化(通常为JSON)输出:
OpenAI结构化输出:确保模型生成严格符合JSON Schema的响应。可选择
JSON_OBJECT(保证生成合法JSON)或JSON_SCHEMA(保证匹配指定Schema),配置项:spring.ai.openai.chat.options.responseFormatAzure OpenAI:提供
spring.ai.azure.openai.chat.options.responseFormat指定输出格式。设置为{ "type": "json_object" }启用JSON模式Ollama:提供
spring.ai.ollama.chat.options.format指定响应格式,目前仅支持jsonMistral AI:提供
spring.ai.mistralai.chat.options.responseFormat指定输出格式。json_object启用JSON模式;json_schema启用原生结构化输出,保证匹配指定Schema