结构化输出转换器

本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。
截至 2024 年 5 月 2 日,旧版 OutputParserBeanOutputParserListOutputParserMapOutputParser 类已弃用,推荐改用新版 StructuredOutputConverterBeanOutputConverterListOutputConverterMapOutputConverter 实现。后者可直接替换前者并提供相同功能,此次变更主要出于命名规范化考虑(实际并无解析操作),同时也为对齐 Spring 的 org.springframework.core.convert.converter 包并引入增强功能。

大语言模型生成结构化输出的能力对依赖可靠解析结果的下游应用至关重要。开发者希望快速将 AI 模型结果转换为 JSON、XML 或 Java Class 等数据类型,以便传递给其他应用函数和方法。

Spring AI 结构化输出转换器(Structured Output Converter)帮助将 LLM 输出转为结构化格式。如下图所示,该方案围绕 LLM 文本补全端点运作:

Structured Output Converter Architecture

使用通用补全 API 从大语言模型(LLM)生成结构化输出需谨慎处理输入输出。结构化输出转换器在 LLM 调用前后起关键作用,确保获得预期输出结构。

在 LLM 调用前,转换器向提示词追加格式指令,为模型生成预期输出结构提供明确指导。这些指令作为蓝图,引导模型响应符合指定格式。

LLM 调用后,转换器(Converter)将模型的原始文本输出转换为结构化类型实例。该转换过程包括解析原始文本输出,并将其映射 为JSON、XML 或领域特定数据结构等对应的结构化数据表示。

StructuredOutputConverter 会尽力将模型输出转换为结构化格式,但 AI 模型并不保证按请求返回结构化输出(可能无法理解提示或生成所需结构)。建议实现验证机制以确保模型输出符合预期。
StructuredOutputConverter 不用于 LLM 工具调用 功能,因此特性默认已提供结构化输出。

结构化输出 API

StructuredOutputConverter 接口允许从基于文本的 AI 模型输出中获取结构化结果,例如映射到 Java Class 或值数组。其接口定义为:

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

}

该接口结合了 Spring 的 Converter<String, T>FormatProvider 接口。

public interface FormatProvider {
	String getFormat();
}

下图展示了使用结构化输出 API 时的数据流:

Structured Output 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(
       new PromptTemplate(
			   this.userInputTemplate,
          Map.of(..., "format", outputConverter.getFormat()) // 将 "format" 占位符替换为转换器的格式指令。
       ).createMessage());

Converter<String, T> 负责将模型的文本输出转换为指定类型 T 的实例。

可用的转换器

目前 Spring AI 提供以下实现:AbstractConversionServiceOutputConverterAbstractMessageOutputConverter、BeanOutputConverterMapOutputConverter和ListOutputConverter

结构化输出类体系结构
  • AbstractConversionServiceOutputConverter<T> - 提供预配置的 GenericConversionService 用于将 LLM 输出转换为目标格式,未提供默认 FormatProvider 实现。

  • AbstractMessageOutputConverter<T> - 提供预配置的 MessageConverter 用于将 LLM 输出转换为目标格式,未提供默认 FormatProvider 实现。

  • BeanOutputConverter<T> - BeanOutputConverter<T>:通过配置 Java 类或 ParameterizedTypeReference,该转换器(Converter)使用 FormatProvider 实现指导 AI 模型生成符合 DRAFT_2020_12JSON Schema 的响应(基于指定 Java 类生成),随后用 ObjectMapper 将 JSON 输出反序列化为目标类的 Java 对象实例。

  • MapOutputConverter - 继承 AbstractMessageOutputConverter 的功能,通过 FormatProvider 实现引导 AI 模型生成符合 RFC8259 标准的 JSON 响应,并利用提供的 MessageConverter 将 JSON Payload 转换为 java.util.Map<String, Object> 实例。

  • ListOutputConverter - 继承 AbstractConversionServiceOutputConverter,包含专为逗号分隔列表输出定制的 FormatProvider 实现。该转换器利用提供的 ConversionService 将模型文本输出转换为 java.util.List

使用转换器

以下章节提供使用现有转换器生成结构化输出的指南。

BeanOutputConverter

以下示例展示如何使用 BeanOutputConverter 生成演员作品表:

表示演员作品表的目标 record 类型:

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(
    new PromptTemplate(this.template, Map.of("actor", this.actor, "format", this.format)).create()).getResult();

ActorsFilms actorsFilms = this.beanOutputConverter.convert(this.generation.getOutput().getText());

生成模式中的属性顺序

BeanOutputConverter 通过 @JsonPropertyOrder 注解支持自定义 JSON Schema 中的属性顺序,该注解允许指定属性在模式中的精确出现顺序(无视 classrecord 中的声明顺序)。

例如,确保 ActorsFilms record 中的属性特定顺序:

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

该注解同时适用于 record 和常规 Java class

泛型 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 = new PromptTemplate(this.template, Map.of("format", this.format)).create();

Generation generation = chatModel.call(this.prompt).getResult();

List<ActorsFilms> actorsFilms = this.outputConverter.convert(this.generation.getOutput().getText());

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

或直接使用底层 ChatModel API:

MapOutputConverter mapOutputConverter = new MapOutputConverter();

String format = this.mapOutputConverter.getFormat();
String template = """
        Provide me a List of {subject}
        {format}
        """;

Prompt prompt = new PromptTemplate(this.template,
        Map.of("subject", "an array of numbers from 1 to 9 under they key name 'numbers'", "format", this.format)).create();

Generation generation = chatModel.call(this.prompt).getResult();

Map<String, Object> result = this.mapOutputConverter.convert(this.generation.getOutput().getText());

ListOutputConverter

以下片段展示如何使用 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 = new PromptTemplate(this.template,
        Map.of("subject", "ice cream flavors", "format", this.format)).create();

Generation generation = this.chatModel.call(this.prompt).getResult();

List<String> list = this.listOutputConverter.convert(this.generation.getOutput().getText());

内置 JSON Schema

部分 AI 模型提供专用配置选项以生成结构化(通常为 JSON)输出。

  • OpenAI 结构化输出 确保模型生成严格符合所提供 JSON Schema 的响应。可选模式包括:保证生成有效 JSON 的 JSON_OBJECT,或通过 spring.ai.openai.chat.options.responseFormat 配置、确保响应匹配指定 schema 的 JSON_SCHEMA

  • Azure OpenAI 提 供 spring.ai.azure.openai.chat.options.responseFormat 选项指定模型输出格式。设置为 { "type": "json_object" } 可启用 JSON 模式,确保模型生成的消息为有效 JSON。

  • Ollama 提供 spring.ai.ollama.chat.options.format 选项指定响应格式,当前仅接受 JSON 值。

  • Mistral AI 提供 spring.ai.mistralai.chat.options.responseFormat 选项指定响应格式,设置为 { "type": "json_object" } 可启用 JSON 模式,确保模型生成的消息为有效 JSON。