08_Spring AI 干货笔记之结构化输出

一、结构化输出转换器

截至 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 文本补全端点运行:

使用通用补全 API 从大语言模型生成结构化输出需要仔细处理输入和输出。结构化输出转换器在 LLM 调用前后发挥着关键作用,确保获得所需的输出结构。

在 LLM 调用之前,转换器将格式指令附加到提示词中,为模型生成所需的输出结构提供明确的指导。这些指令充当蓝图,塑造模型的响应以符合指定的格式。

在 LLM 调用之后,转换器获取模型的输出文本并将其转换为结构化类型的实例。此转换过程涉及解析原始文本输出并将其映射到相应的结构化数据表示,例如 JSON、XML 或特定领域的数据结构。

StructuredOutputConverter 会尽力将模型输出转换为结构化输出。但不能保证 AI 模型会按请求返回结构化输出。模型可能无法理解提示词或无法按要求生成结构化输出。请考虑实施验证机制以确保模型输出符合预期。
StructuredOutputConverter 不用于 LLM 工具调用,因为该功能默认本身就提供结构化输出。

二、结构化输出 API

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

java 复制代码
public interface StructuredOutputConverter<T> extends Converter<String, T>, FormatProvider {

}

它结合了 Spring 的 Converter<String, T> 接口和 FormatProvider 接口:

java 复制代码
public interface FormatProvider {
	String getFormat();
}

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

FormatProvider 向 AI 模型提供特定的格式化指南,使其能够生成可以转换为指定目标类型 T 的文本输出,这是通过 Converter 实现的。以下是此类格式化指令的示例:

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 附加到用户输入的末尾,如下所示:

java 复制代码
    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())) // 用转换器的格式替换 "format" 占位符。
						.build().createMessage()
    );

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

2.1 可用转换器

目前,Spring AI 提供了 AbstractConversionServiceOutputConverter、AbstractMessageOutputConverter、BeanOutputConverter、MapOutputConverter 和 ListOutputConverter 实现:

  • AbstractConversionServiceOutputConverter - 提供一个预配置的 GenericConversionService,用于将 LLM 输出转换为所需格式。不提供默认的 FormatProvider 实现。

  • AbstractMessageOutputConverter - 提供一个预配置的 MessageConverter,用于将 LLM 输出转换为所需格式。不提供默认的 FormatProvider 实现。

  • BeanOutputConverter - 使用指定的 Java 类(例如,Bean)或 ParameterizedTypeReference 进行配置,该转换器采用一个 FormatProvider 实现,该实现指示 AI 模型生成符合从指定 Java 类派生的 DRAFT_2020_12 JSON Schema 的 JSON 响应。随后,它利用 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 生成演员的电影作品列表。

代表演员电影作品的目标记录:

java 复制代码
record ActorsFilms(String actor, List<String> movies) {
}

以下是使用高级流式 ChatClient API 应用 BeanOutputConverter 的方法:

java 复制代码
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:

java 复制代码
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());

3.2 生成模式中的属性顺序

BeanOutputConverter 通过 @JsonPropertyOrder 注解支持在生成的 JSON 模式中自定义属性顺序。此注解允许您指定属性在模式中出现的确切顺序,而不管它们在类或记录中的声明顺序如何。

例如,为了确保 ActorsFilms 记录中属性的特定顺序:

java 复制代码
@JsonPropertyOrder({"actor", "movies"})
record ActorsFilms(String actor, List<String> movies) {}

此注解适用于记录和常规 Java 类。

3.3 泛型 Bean 类型

使用 ParameterizedTypeReference 构造函数来指定更复杂的目标类结构。例如,要表示演员及其电影作品的列表:

java 复制代码
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:

java 复制代码
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 Map 输出转换器

以下代码片段展示了如何使用 MapOutputConverter 将模型输出转换为 Map 中的数字列表。

java 复制代码
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:

java 复制代码
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());

3.5 List 输出转换器

以下代码片段展示了如何使用 ListOutputConverter 将模型输出转换为冰淇淋口味列表。

java 复制代码
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:

java 复制代码
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 模型

以下 AI 模型已经过测试,支持 List、Map 和 Bean 结构化输出。

五、内置 JSON 模式

一些 AI 模型提供了专用的配置选项来生成结构化(通常是 JSON)输出。

  • OpenAI 结构化输出 可以确保您的模型生成严格符合您提供的 JSON 模式的响应。您可以在保证模型生成的消息是有效 JSON 的 JSON_OBJECT 与提供模式并保证模型生成与您提供的模式匹配的响应的 JSON_SCHEMA 之间进行选择(通过spring.ai.openai.chat.options.responseFormat 选项配置)。

  • 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。

相关推荐
轮到我狗叫了1 小时前
Contrastive pseudo learning for openworld deepfake attribution 超细致论文笔记,第一次读论文
人工智能
LitchiCheng1 小时前
Mujoco 检验 KDL 和 Pinocchio 运动学 FK 是否一致
人工智能·python
2的n次方_1 小时前
openGauss压力测试:性能、稳定性与AI能力的全面探索
数据库·人工智能·压力测试
云雾J视界1 小时前
当AI能写代码时,顶级工程师在做什么?大模型时代的系统架构思维重塑
人工智能·系统架构·思维重塑·能力边界·能力重构·系统定义
TechWJ1 小时前
Rokid AR眼镜智能提词器开发实战:从SDK集成到AI自动跟踪
人工智能·ai·ar·ar眼镜
帮帮志1 小时前
05【AI大模型对话/创建项目】通过pycharm创建大模型项目,关联Anaconda环境
ide·人工智能·python·语言模型·pycharm
海边夕阳20061 小时前
【每天一个AI小知识】:什么是目标检测?
人工智能·python·深度学习·目标检测·机器学习·计算机视觉·目标跟踪
明月照山海-1 小时前
机器学习周报二十四
人工智能·机器学习·计算机视觉
忆湫淮1 小时前
ENVI 5.6 利用现场标准校准板计算地表反射率具体步骤
大数据·人工智能·算法