结构化输出转换器
截至 2024 年 5 月 2 日,旧的 OutputParser
、BeanOutputParser
、ListOutputParser
和 MapOutputParser
类已被弃用,取而代之的是新的 StructuredOutputConverter
、BeanOutputConverter
、ListOutputConverter
和 MapOutputConverter
实现。后者是前者的直接替代品,并提供相同的功能。此次更改的主要原因是命名问题,因为实际上并没有进行任何解析操作,同时也与 Spring 的 org.springframework.core.convert.converter
包保持一致,带来了一些改进的功能。
LLM 生成结构化输出的能力对于依赖可靠解析输出值的下游应用程序非常重要。开发人员希望快速将 AI 模型的结果转换为数据类型,例如 JSON、XML 或 Java 类,以便传递给其他应用程序函数和方法。
Spring AI 的 Structured Output Converters
帮助将 LLM 的输出转换为结构化格式。如下图所示,这种方法围绕 LLM 文本补全端点进行操作:
使用通用完成 API 从大型语言模型(LLMs)生成结构化输出时,需要仔细处理输入和输出。结构化输出转换器在 LLM 调用前后起着至关重要的作用,确保实现所需的输出结构。
在 LLM 调用之前,转换器会将格式指令附加到提示中,为模型生成所需的输出结构提供明确的指导。这些指令充当蓝图,塑造模型的响应以符合指定的格式。
在 LLM 调用之后,转换器会获取模型的输出文本并将其转换为结构化类型的实例。这个转换过程包括解析原始文本输出并将其映射到相应的结构化数据表示,例如 JSON、XML 或领域特定的数据结构。
StructuredOutputConverter
是一种尽力将模型输出转换为结构化输出的方法。AI 模型并不保证能够按照要求返回结构化输出。模型可能无法理解提示,或者无法生成所请求的结构化输出。建议实现一个验证机制,以确保模型输出符合预期。
:::提示
StructuredOutputConverter
不用于 LLM 的 工具调用,因为该功能默认提供结构化输出。
:::
结构化输出 API
StructuredOutputConverter
接口允许你获取结构化输出,例如将输出映射到 Java 类或从基于文本的 AI 模型输出中获取值数组。该接口的定义如下:
public interface StructuredOutputConverter<T> extends Converter<String, T>, FormatProvider {
}
它结合了 Spring 的 Converter<String, T> 接口和 FormatProvider
接口。
public interface FormatProvider {
String getFormat();
}
下图展示了使用结构化输出 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}
"""; // user input with a "format" placeholder.
Prompt prompt = new Prompt(
new PromptTemplate(
this.userInputTemplate,
Map.of(..., "format", outputConverter.getFormat()) // replace the "format" placeholder with the converter's format.
).createMessage());
Converter<String, T>
负责将模型的输出文本转换为指定类型 T
的实例。
可用的转换器
目前,Spring AI 提供了 AbstractConversionServiceOutputConverter
、AbstractMessageOutputConverter
、BeanOutputConverter
、MapOutputConverter
和 ListOutputConverter
的实现:
-
AbstractConversionServiceOutputConverter<T>
- 提供了一个预配置的 GenericConversionService,用于将 LLM 输出转换为所需的格式。未提供默认的FormatProvider
实现。 -
AbstractMessageOutputConverter<T>
- 提供了一个预配置的 MessageConverter,用于将 LLM 输出转换为所需的格式。未提供默认的FormatProvider
实现。 -
BeanOutputConverter<T>
- 配置了一个指定的 Java 类(例如,Bean)或一个 ParameterizedTypeReference,该转换器使用一个FormatProvider
实现,指示 AI 模型生成符合DRAFT_2020_12
、JSON Schema
的 JSON 响应,该模式源自指定的 Java 类。随后,它使用ObjectMapper
将 JSON 输出反序列化为目标类的 Java 对象实例。 -
MapOutputConverter
- 扩展了AbstractMessageOutputConverter
的功能,包含一个FormatProvider
实现,该实现指导 AI 模型生成符合 RFC8259 的 JSON 响应。此外,它还包含一个转换器实现,该实现使用提供的MessageConverter
将 JSON 负载转换为java.util.Map<String, Object>
实例。 -
ListOutputConverter
- 扩展了AbstractConversionServiceOutputConverter
,并包含一个专门用于逗号分隔列表输出的FormatProvider
实现。该转换器实现使用提供的ConversionService
将模型文本输出转换为java.util.List
。
使用转换器
以下部分提供了如何使用可用转换器生成结构化输出的指南。
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 = 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().getContent());
生成的 Schema 中的属性顺序
BeanOutputConverter
支持通过 @JsonPropertyOrder
注解在生成的 JSON 模式中自定义属性的顺序。该注解允许你指定属性在模式中出现的精确顺序,而不管它们在类或记录中的声明顺序如何。
例如,为了确保 ActorsFilms
记录中属性的特定顺序:
@JsonPropertyOrder({"actor", "movies"})
record ActorsFilms(String actor, List<String> movies) {}
此注解适用于记录(records)和常规的 Java 类。
泛型 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().getContent());
Map 输出转换器
以下代码片段展示了如何使用 MapOutputConverter
将模型输出转换为映射中的数字列表。
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().getContent());
列表输出转换器
以下代码片段展示了如何使用 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().getContent());
支持的 AI 模型
以下 AI 模型已经过测试,支持 List、Map 和 Bean 结构化的输出。
内置 JSON 模式
一些 AI 模型提供了专门的配置选项,用于生成结构化(通常是 JSON)的输出。
-
OpenAI 结构化输出 可以确保你的模型生成严格符合你提供的 JSON Schema 的响应。你可以选择
JSON_OBJECT
,它保证模型生成的消息是有效的 JSON,或者选择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。