结构化输出转换器

LLM生成结构化输出的能力对于依赖于可靠解析输出值的下游应用至关重要。 开发者希望快速将AI模型的结果转换为JSON、XML或Java类等数据类型,以便能够传递给其他应用程序函数和方法。spring-doc.cadn.net.cn

Spring AI Structured Output Converters 助力将LLM输出转换为结构化格式。 如下图所示,此方法围绕LLM文本补全端点运作:spring-doc.cadn.net.cn

Structured Output Converter Architecture

从大型语言模型(LLMs)通过通用完成API生成结构化输出需要对输入和输出进行仔细处理。结构化输出转换器在LLM调用前后扮演着关键角色,确保达到期望的输出结构。spring-doc.cadn.net.cn

在LLM调用之前,转换器会向提示中追加格式指令,为模型提供明确的指导以生成期望的输出结构。这些指令充当蓝图,塑造模型的响应,使其符合指定的格式。spring-doc.cadn.net.cn

随着更多AI模型原生支持结构化输出,您可以利用AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT中的原生结构化输出功能来发挥这一优势。这种方法直接使用生成的JSON模式与模型的原生结构化输出API配合,省去了预提示格式化指令的需要,并提供了更可靠的结果。

LLM调用后,转换器将模型的输出文本转化为结构化类型的实例。此转化过程包括解析原始文本输出,并将其映射到相应的结构化数据表示形式,如JSON、XML或领域特定的数据结构。spring-doc.cadn.net.cn

数字StructuredOutputConverter表示尽最大努力将模型输出转换为结构化输出。 AI模型不保证按请求返回结构化输出。 模型可能无法理解提示或无法按请求生成结构化输出。 请考虑实现验证机制以确保模型输出符合预期。
数字StructuredOutputConverter未用于LLM工具调用,因为此功能默认内置提供结构化输出。

结构化输出API

StructuredOutputConverter 接口使您能够获取结构化输出,例如将输出映射到Java类或从基于文本的AI模型输出中提取的值数组。 接口定义为:spring-doc.cadn.net.cn

public interface StructuredOutputConverter<T> extends Converter<String, T>, FormatProvider {

}

它结合了Spring的Converter<String, T>接口和FormatProvider接口spring-doc.cadn.net.cn

public interface FormatProvider {
	String getFormat();
}

下图展示了使用结构化输出API时的数据流。spring-doc.cadn.net.cn

Structured Output API

数字 FormatProvider 为AI模型提供了特定的格式化指南,使其生成的文本输出能够通过 Converter 转换为指定的目标类型 T。以下是一个此类格式化指令的例子:spring-doc.cadn.net.cn

  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类附加在用户输入的末尾,例如这样:spring-doc.cadn.net.cn

    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()
    );

转换器<String, T>负责将模型中的输出文本转换为指定类型T的实例。spring-doc.cadn.net.cn

可用转换器

目前,Spring AI提供了 AbstractConversionServiceOutputConverter, AbstractMessageOutputConverter, BeanOutputConverter, MapOutputConverterListOutputConverter 种实现方式:spring-doc.cadn.net.cn

Structured Output Class Hierarchy
  • AbstractConversionServiceOutputConverter<T> - 提供了一个预配置的通用转换服务,用于将LLM输出转换为所需格式。没有提供默认的FormatProvider实现。spring-doc.cadn.net.cn

  • AbstractMessageOutputConverter<T> - 提供了一个预配置的消息转换器,用于将LLM输出转换为所需格式。没有提供默认的FormatProvider实现。spring-doc.cadn.net.cn

  • BeanOutputConverter<T> - 通过指定的Java类(例如,Bean)或参数化类型参考进行配置,此转换器使用FormatProvider实现,该实现指导AI模型生成遵循DRAFT_2020_12、从指定Java类派生的JSON Schema的JSON响应。随后,它使用ObjectMapper将JSON输出反序列化为目标类的Java对象实例。spring-doc.cadn.net.cn

  • MapOutputConverter - 扩展了AbstractMessageOutputConverter的功能,采用FormatProvider实现方式,引导AI模型生成符合RFC8259标准的JSON响应。此外,它还包含了一个转换器实现,该实现利用提供的MessageConverter将JSON负载转换为java.util.Map<String, Object>实例。spring-doc.cadn.net.cn

  • ListOutputConverter - 扩展了 AbstractConversionServiceOutputConverter 并包含了一个针对逗号分隔列表输出量身定制的 FormatProvider 实现。转换器实现利用提供的 ConversionService 将模型文本输出转换为 java.util.Listspring-doc.cadn.net.cn

使用转换器

以下各章节提供了如何使用可用的转换器生成结构化输出的指南。spring-doc.cadn.net.cn

Bean 输出转换器

以下示例展示了如何使用BeanOutputConverter来生成演员的电影作品列表。spring-doc.cadn.net.cn

表示演员影视作品的目标记录:spring-doc.cadn.net.cn

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

如何使用高级流畅的 ChatClient API 应用 BeanOutputConverter:spring-doc.cadn.net.cn

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 直接进行操作:spring-doc.cadn.net.cn

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());

生成架构中的属性排序

在生成的JSON模式中,BeanOutputConverter 支持通过 @JsonPropertyOrder 注解来自定义属性排序。 此注解使您能够指定属性在架构中出现的确切顺序,而不受其在类或记录中的声明顺序影响。spring-doc.cadn.net.cn

例如,要确保在ActorsFilms记录中属性的特定排序:spring-doc.cadn.net.cn

@JsonPropertyOrder({"actor", "movies"})
record ActorsFilms(String actor, List<String> movies) {}

此注解适用于记录(records)和常规Java类。spring-doc.cadn.net.cn

通用Bean类型

使用 ParameterizedTypeReference 构造函数来指定更复杂的目标类结构。 例如,要表示演员列表及其影视作品集:spring-doc.cadn.net.cn

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 直接进行操作:spring-doc.cadn.net.cn

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());

Map 输出转换器

以下代码片段展示了如何使用MapOutputConverter将模型输出转换为映射中的数字列表。spring-doc.cadn.net.cn

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 直接进行操作:spring-doc.cadn.net.cn

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将模型输出转换为冰淇淋口味的列表。spring-doc.cadn.net.cn

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:spring-doc.cadn.net.cn

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());

原生结构化输出

许多现代AI模型现原生支持结构化输出,与基于提示的格式化相比,这提供了更可靠的结果。Spring AI通过原生结构化输出功能支持这一特性。spring-doc.cadn.net.cn

在使用原生结构化输出时,由BeanOutputConverter生成的JSON模式直接发送到模型的结构化输出API,从而省去了在提示中需要格式指令的步骤。这种方法提供了:spring-doc.cadn.net.cn

使用原生结构化输出

要启用原生结构化输出,请使用参数 AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT:spring-doc.cadn.net.cn

ActorsFilms actorsFilms = ChatClient.create(chatModel).prompt()
    .advisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
    .user("Generate the filmography for a random actor.")
    .call()
    .entity(ActorsFilms.class);

您也可以使用 defaultAdvisors() 全局设置 ChatClient.Builderspring-doc.cadn.net.cn

@Bean
ChatClient chatClient(ChatClient.Builder builder) {
    return builder
        .defaultAdvisors(AdvisorParams.ENABLE_NATIVE_STRUCTURED_OUTPUT)
        .build();
}

支持的原生结构化输出模型

以下模型目前支持原生结构化输出:spring-doc.cadn.net.cn

一些AI模型,如OpenAI,不直接支持在顶级层次使用对象数组。在这种情况下,您可以使用Spring AI的默认结构化输出转换(不使用原生结构化输出顾问)。

内置JSON模式

一些AI模型提供了专门的配置选项,以生成结构化(通常是JSON)输出。spring-doc.cadn.net.cn

  • OpenAI 结构化输出能确保您的模型生成严格遵循所提供JSON Schema的响应。您可以在以下选项中选择:使用JSON_OBJECT保证模型生成的有效JSON消息,或者使用JSON_SCHEMA并提供一个模式,以保证模型生成的响应与您提供的模式匹配(还有spring.ai.openai.chat.options.responseFormat选项)。spring-doc.cadn.net.cn

  • Azure OpenAI - 提供了一个 spring.ai.azure.openai.chat.options.responseFormat 选项,用于指定模型必须输出的格式。设置为 { "type": "json_object" } 启用JSON模式,确保模型生成的消息是有效的JSON格式。spring-doc.cadn.net.cn

  • Ollama - 提供了一个 spring.ai.ollama.chat.options.format 选项来指定返回响应的格式。当前,唯一接受的值是 jsonspring-doc.cadn.net.cn

  • Mistral AI - 提供了一个 spring.ai.mistralai.chat.options.responseFormat 选项来指定返回响应的格式。将其设置为 { "type": "json_object" } 启用JSON模式,确保模型生成的消息是有效的JSON。此外,将其与提供的架构一起设置为 { "type": "json_schema" } 启用了原生结构化输出支持,保证模型生成的响应与您提供的架构相匹配。spring-doc.cadn.net.cn