73 0

08_Spring AI 干货笔记之结构化输出 [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

小学生

14%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
40 点
帖子
3
精华
0
在线时间
0 小时
注册时间
2018-1-4
最后登录
2018-1-4

楼主
昵称昵称昵称 发表于 2025-12-1 14:07:26 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

结构化输出转换器概述

自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 前后,StructuredOutputConverter 发挥着关键作用,确保最终获得符合预期结构的输出结果。

  • 调用前处理:转换器会自动将格式化指令附加到原始提示词末尾,为模型提供明确的输出结构指引。这些指令如同蓝图,引导模型按照指定格式组织响应内容。
  • 调用后处理:当模型返回文本后,转换器会接管输出,将其解析并映射为目标结构化类型实例,如 Java 类、集合或键值对结构。

关于输出可靠性的说明

尽管 StructuredOutputConverter 努力确保输出的结构一致性,但无法完全保证模型始终遵循指令返回合规格式。在某些情况下,模型可能未能理解提示要求,或因训练偏差导致输出偏离预期结构。因此,建议在实际应用中加入额外的验证机制,以确认模型输出的有效性和完整性。

需要注意的是,StructuredOutputConverter 不适用于 LLM 工具调用场景,因为在工具调用模式下,系统本身已默认支持结构化输出,无需额外转换处理。

结构化输出 API 设计

核心接口 StructuredOutputConverter<T> 继承自 Spring 的 Converter<String, T> 并实现了 FormatProvider 接口,定义如下:

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

其中 FormatProvider 接口用于提供格式说明:

public interface FormatProvider {
    String getFormat();
}

该接口组合使得转换器既能提供格式指南,又能执行字符串到目标类型的转换。下图展示了使用该 API 时的数据流动路径:

格式指令的应用方式

格式化指令通常通过 PromptTemplate 插入到用户输入中,常见做法是在模板中预留占位符,并在构建提示时由转换器动态填充。示例如下:

StructuredOutputConverter outputConverter = ...;
String userInputTemplate = """
... user text input ....
{format}
""";

Prompt prompt = new Prompt(
    PromptTemplate.builder()
        .template(this.userInputTemplate)
        .variables(Map.of(..., "format", this.outputConverter.getFormat()))
        .build().createMessage()
);

上述代码中,“format” 占位符会被实际的格式要求替换,从而确保模型接收到完整的结构化输出指令。典型的指令可能包含以下内容:

  • 你的回复必须采用 JSON 格式。
  • JSON 数据结构应与以下 Java 类匹配:java.util.HashMap。
  • 不要包含任何解释性文字,仅输出符合 RFC8259 标准的 JSON 内容,严格遵循指定格式。

通过这种方式,系统能够在不修改模型的前提下,有效引导其生成可被程序直接解析的结构化响应。

Converter<String, T> 的作用是将模型生成的文本输出转换为指定类型 T 的实例。

2.1 可用的转换器实现

Spring AI 当前提供了以下几种 OutputConverter 的具体实现:AbstractConversionServiceOutputConverter、AbstractMessageOutputConverter、BeanOutputConverter、MapOutputConverter 以及 ListOutputConverter。

AbstractConversionServiceOutputConverter:内置了一个已预配置的 GenericConversionService,可用于将大语言模型(LLM)的输出转换为目标格式。该类不提供默认的 FormatProvider 实现。

AbstractMessageOutputConverter:配备了预设的 MessageConverter,支持将 LLM 输出转化为所需结构。同样,此类也不包含默认的 FormatProvider 实现。

BeanOutputConverter:可通过指定一个 Java 类(如 POJO 或 record)或 ParameterizedTypeReference 进行初始化。该转换器会使用一个 FormatProvider 实现,指示 AI 模型生成符合从目标 Java 类推导出的 DRAFT_2020_12 JSON Schema 格式的响应内容,并借助 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 获取某位演员的参演电影列表。

首先定义一个用于承载演员及其作品信息的记录类:

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

接着可以采用高级别的流式 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 = beanOutputConverter.getFormat();
String actor = "Tom Hanks";
String template = """
Generate the filmography of 5 movies for {actor}.
{format}
""";
Generation generation = chatModel.call(
    PromptTemplate.builder()
        .template(template)
        .variables(Map.of("actor", actor, "format", format))
        .build()
        .create()
).getResult();

3.2 属性顺序的生成模式控制

在生成 JSON 模式时,可通过使用 @JsonPropertyOrder 注解决定属性的输出顺序。该注解能够覆盖类或记录中原始声明的字段顺序,确保指定属性按预设次序出现在最终的结构中。

例如,若希望在 ActorsFilms 记录中优先显示 actor 字段,随后才是 movies 列表,则可如下定义:

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

此功能同时适用于普通 Java 类和 record 类型,提供更强的结构控制能力。



3.3 复杂泛型类型的处理

对于包含泛型的复杂类型结构,推荐使用 ParameterizedTypeReference 来准确描述目标类型。比如需要获取多位演员及其各自五部代表作的列表时,可以采用高级 API 方式:

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 接口实现相同逻辑:

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

对应的低层级实现方式如下:

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("format", this.format))
    .build()
    .create();



以下代码示例演示了如何通过 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 = 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());

对于需要将输出解析为特定结构的情况,还可以使用 MapOutputConverter 来处理键值对形式的结果。例如,将一组数字 1 到 9 映射到名为 'numbers' 的键下:

Map<String, Object> result = ChatClient.create(chatModel)
.prompt(prompt -> prompt.text("Return a {subject} in the specified {format}")
.param("subject", "an array of numbers from 1 to 9 under the key name 'numbers'")
.param("format", this.format))
.call()
.entity(new MapOutputConverter(new DefaultConversionService()));

或者使用低级别接口进行构建:

MapOutputConverter mapOutputConverter = new MapOutputConverter(new DefaultConversionService());
String format = this.mapOutputConverter.getFormat();
String template = """
Return a {subject} in the specified {format}
""";
Prompt prompt = PromptTemplate.builder()
.template(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());

支持的 AI 模型

以下 AI 模型已通过验证,能够支持 List、Map 以及 Bean 类型的结构化输出功能。

内置 JSON Schema 支持

部分 AI 模型提供了专用配置项,用于生成符合特定格式的结构化响应,通常以 JSON 形式呈现。

OpenAI 结构化输出
可确保模型返回严格遵循指定 JSON 模式的响应。可通过 spring.ai.openai.chat.options.responseFormat 配置选项,在以下两种模式中选择:
- JSON_OBJECT:保证输出为合法的 JSON 格式。
- JSON_SCHEMA:提供具体 schema,并确保模型返回内容与该 schema 完全匹配。

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

Ollama
支持通过 spring.ai.ollama.chat.options.format 指定响应格式。目前唯一接受的值为 json,用于要求模型返回 JSON 结构的数据。

Mistral AI
提供 spring.ai.mistralai.chat.options.responseFormat 选项来控制响应格式。将其设为 { "type": "json_object" } 可开启 JSON 模式,确保模型输出为有效的 JSON 内容。

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:Spring Pring RING 结构化 ING

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
扫码
拉您进交流群
GMT+8, 2026-2-20 16:53