Spring AI 系列文章:

【Spring AI 1.0.0】Spring AI 1.0.0框架快速入门(1)——Chat Client API
【Spring AI 1.0.0】Spring AI 1.0.0框架快速入门(2)——Prompt(提示词)
【Spring AI 1.0.0】Spring AI 1.0.0框架快速入门(3)——Structured Output Converter(结构化输出转换器)
【Spring AI 1.0.0】Spring AI 1.0.0框架快速入门(4)——Chat Memory(聊天记录)
【Spring AI 1.0.0】Spring AI 1.0.0框架快速入门(5)——Tool Calling(工具调用)
【Spring AI 1.0.0】Spring AI 1.0.0框架快速入门(6)——MCP Client(MCP客户端)

一、前言

结构化输出转换是指将AI模型的结果转换为可以传递给其他应用程序函数的方法的数据类型。如 JSON、XML 或 Java 类。

Spring AI 的 Structured Output Converters 帮助将 LLM 输出转换为结构化格式。 如下图所示:

在这里插入图片描述

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

StructuredOutputConverter 会尽量把 AI 模型的输出转换成结构化的格式,但不能保证 100% 成功。因为 AI 模型有时候可能看不懂你的要求,或者没办法完全按照格式生成内容。所以,最好再加个检查机制,确保输出的结果符合你的预期。

二、结构化输出 API

StructuredOutputConverter 这个接口的作用是:把 AI 模型输出的纯文本内容,自动转换成结构化的数据格式,比如:

  • 直接映射成 Java 对象(比如一个 User 类)

  • 或者转换成数组、列表等格式化的数据

它的接口定义如下:

在这里插入图片描述

它继承了 Spring Converter<String, T> 接口和 FormatProvider 接口

在这里插入图片描述
下图显示了使用结构化输出 API 时的数据流。

在这里插入图片描述
FormatProvider 的作用是 告诉 AI 模型应该按什么格式生成文本,这样输出的内容才能被顺利转换成想要的数据类型(比如 Java 对象 T)。Converter<String, T> 负责将模型的输出文本转换为指定类型 T 的实例。

可用的转换器:

目前,Spring AI 提供了 AbstractConversionServiceOutputConverterAbstractMessageOutputConverterBeanOutputConverterMapOutputConverterListOutputConverter 实现:

在这里插入图片描述

在这里插入图片描述

  • AbstractConversionServiceOutputConverter<T> :它使用 Spring 框架的 ConversionService 将文本(通常是 JSON 格式)转换成 Java 的 POJO(类、Map、List 等)。底层是由GenericConversionService来实现的;

    • 在这里插入图片描述
  • AbstractMessageOutputConverter<T> : 它用于 从 LLM 返回的 Message 对象中提取内容,并将其转换为结构化 Java 对象(如 POJO、List、Map 等)。

    • 在这里插入图片描述
  • BeanOutputConverter<T> :配置指定的 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 输出转换器

代码:GitHub

以下示例显示如何使用 BeanOutputConverter 生成演员的电影作品。

表示演员电影作品的实体:

@Data
@NoArgsConstructor
public class ActorsFilms {

    private String actor;

    private List<String> movies;

    @Override
    public String toString() {
        return "ActorsFilms{" + "actor='" + this.actor + '\'' + ", movies=" + this.movies + '}';
    }
}

下面是使用ChatClient API应用 BeanOutputConverter 的方法:

@RestController
public class ConverterController {

    @Resource(name = "zhiPuAiChatModel")
//    @Resource(name = "deepSeekChatModel")
    private ChatModel chatModel;

    @GetMapping("/converter/demo1")
    ActorsFilms demo1(@RequestParam("actor") String actor) {
        return ChatClient.create(chatModel).prompt()
                .user(u -> u.text("为 {actor} 生成 5 部电影的电影作品。")
                        .param("actor", actor))
                .call()
                .entity(ActorsFilms.class);
    }
}

使用ChatModel API应用 BeanOutputConverter 的方法:

@GetMapping("/converter/demo2")
ActorsFilms demo2(@RequestParam("actor") String actor) {
    // 1. 创建输出转换器
    BeanOutputConverter<ActorsFilms> beanOutputConverter =
            new BeanOutputConverter<>(ActorsFilms.class);
    String format = beanOutputConverter.getFormat();

    // 2. 新版创建PromptTemplate的方式
    PromptTemplate promptTemplate = new PromptTemplate(
            "为 {actor} 生成 5 部电影的电影作品。\n{format}"
    );

    // 3. 创建Prompt时传入变量
    Prompt prompt = promptTemplate.create(
            Map.of("actor", actor, "format", format)
    );

    // 4. 调用模型并转换结果
    Generation generation = chatModel.call(prompt).getResult();
    String text = generation.getOutput().getText();
    return beanOutputConverter.convert(text);
}

执行结果:

在这里插入图片描述

3.2 生成模式中的属性排序

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

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

@Data
@NoArgsConstructor
@JsonPropertyOrder({"movies", "actor"})
public class ActorsFilms {

    private String actor;

    private List<String> movies;

    @Override
    public String toString() {
        return "ActorsFilms{" + "actor='" + this.actor + '\'' + ", movies=" + this.movies + '}';
    }
}

这样movies属性就在actor前面展示

在这里插入图片描述

3.3 泛型 Bean 类型

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

下面是使用ChatClient API应用 BeanOutputConverter 的方法:

@GetMapping("/converter/demo3")
List<ActorsFilms> demo3(@RequestParam("actor1") String actor1, @RequestParam("actor2") String actor2) {
    return ChatClient.create(chatModel).prompt()
            .user(u -> u.text("为 {actor1} 和 {actor2} 生成 5 部电影的电影作品。")
                    .param("actor1", actor1).param("actor2", actor2))
            .call()
            .entity(new ParameterizedTypeReference<List<ActorsFilms>>() {
            });
}

使用ChatModel API应用 BeanOutputConverter 的方法:

@GetMapping("/converter/demo4")
List<ActorsFilms> demo4(@RequestParam("actor1") String actor1, @RequestParam("actor2") String actor2) {
    // 1. 创建输出转换器
    BeanOutputConverter<List<ActorsFilms>> outputConverter = new BeanOutputConverter<>(
            new ParameterizedTypeReference<List<ActorsFilms>>() {
            });
    String format = outputConverter.getFormat();

    // 2. 新版创建PromptTemplate的方式
    PromptTemplate promptTemplate = new PromptTemplate(
            "为 {actor1} 和 {actor2} 生成 5 部电影的电影作品。\n{format}"
    );

    // 3. 创建Prompt时传入变量
    Prompt prompt = promptTemplate.create(
            Map.of("actor1", actor1, "actor2", actor2, "format", format)
    );

    // 4. 调用模型并转换结果
    Generation generation = chatModel.call(prompt).getResult();
    return outputConverter.convert(generation.getOutput().getText());
}

执行结果:

在这里插入图片描述

3.4 Map 输出转换器

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

下面是使用ChatClient API应用 MapOutputConverter 的方法:

@GetMapping("/converter/demo5")
Map<String, Object> demo5(@RequestParam("subject") String subject) {
    return ChatClient.create(chatModel).prompt()
            .user(u -> u.text("为我提供 {subject}")
                    .param("subject", subject))
            .call()
            .entity(new ParameterizedTypeReference<Map<String, Object>>() {
            });
}

使用ChatModel API应用 MapOutputConverter 的方法:

@GetMapping("/converter/demo6")
Map<String, Object> demo6(@RequestParam("subject") String subject) {
    // 1. 创建输出转换器
    MapOutputConverter mapOutputConverter = new MapOutputConverter();
    String format = mapOutputConverter.getFormat();

    // 2. 新版创建PromptTemplate的方式
    PromptTemplate promptTemplate = new PromptTemplate(
            "为我提供 {subject}。\n{format}"
    );

    // 3. 创建Prompt时传入变量
    Prompt prompt = promptTemplate.create(
            Map.of("subject", subject, "format", format)
    );

    // 4. 调用模型并转换结果
    Generation generation = chatModel.call(prompt).getResult();
    return mapOutputConverter.convert(generation.getOutput().getText());
}

执行结果:
在这里插入图片描述

3.5 List 输出转换器

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

下面是使用ChatClient API应用 ListOutputConverter 的方法:

@GetMapping("/converter/demo7")
List<String> demo7(@RequestParam("subject") String subject) {
    return ChatClient.create(chatModel).prompt()
            .user(u -> u.text("用中文列出五种 {subject}")
                    .param("subject", subject))
            .call()
            .entity(new ListOutputConverter(new DefaultConversionService()));
}

使用ChatModel API应用 ListOutputConverter 的方法:

@GetMapping("/converter/demo8")
List<String> demo8(@RequestParam("subject") String subject) {
    // 1. 创建输出转换器
    ListOutputConverter listOutputConverter = new ListOutputConverter(new DefaultConversionService());

    String format = listOutputConverter.getFormat();

    // 2. 新版创建PromptTemplate的方式
    PromptTemplate promptTemplate = new PromptTemplate(
            "用中文列出五种 {subject}。\n{format}"
    );

    // 3. 创建Prompt时传入变量
    Prompt prompt = promptTemplate.create(
            Map.of("subject", subject, "format", format)
    );

    // 4. 调用模型并转换结果
    Generation generation = chatModel.call(prompt).getResult();
    return listOutputConverter.convert(generation.getOutput().getText());
}

执行结果:

在这里插入图片描述

结构化输出转换器(Structured Output Converter)部分内容讲完,下一篇讲聊天记录(Chat Memory)~

创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️

在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐