結構化輸出轉換器

自 2024 年 5 月 2 日起,舊的 OutputParserBeanOutputParserListOutputParserMapOutputParser 類已被棄用,取而代之的是新的 StructuredOutputConverterBeanOutputConverterListOutputConverterMapOutputConverter 實現。後者可以即時替換前者,並提供相同的功能。此次更改的主要原因是命名,因為這裡並沒有進行任何解析,同時也是為了與 Spring 的 org.springframework.core.convert.converter 包對齊,從而帶來一些改進的功能。

LLM 生成結構化輸出的能力對於依賴可靠解析輸出值的下游應用至關重要。開發者希望能夠快速將 AI 模型的結果轉換為可傳遞給其他應用函式和方法的資料型別,例如 JSON、XML 或 Java 類。

Spring AI 的 Structured Output Converters 有助於將 LLM 輸出轉換為結構化格式。如下圖所示,此方法圍繞 LLM 文字補全端點執行

Structured Output Converter Architecture

使用通用補全 API 從大型語言模型 (LLM) 生成結構化輸出需要仔細處理輸入和輸出。結構化輸出轉換器在 LLM 呼叫之前和之後都扮演著關鍵角色,確保實現期望的輸出結構。

在 LLM 呼叫之前,轉換器會將格式指令附加到提示詞中,為模型生成期望的輸出結構提供明確指導。這些指令充當藍圖,塑造模型的響應以符合指定的格式。

在 LLM 呼叫之後,轉換器獲取模型的輸出文字並將其轉換為結構化型別的例項。此轉換過程涉及解析原始文字輸出並將其對映到相應的結構化資料表示,例如 JSON、XML 或領域特定資料結構。

StructuredOutputConverter 是盡最大努力將模型輸出轉換為結構化輸出。AI 模型不保證返回所請求的結構化輸出。模型可能無法理解提示詞或無法按照請求生成結構化輸出。考慮實現驗證機制以確保模型輸出符合預期。
StructuredOutputConverter 不用於 LLM 工具呼叫,因為此功能預設提供結構化輸出。

結構化輸出 API

StructuredOutputConverter 介面允許您從基於文字的 AI 模型輸出中獲取結構化輸出,例如將輸出對映到 Java 類或值陣列。介面定義如下:

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}
        """; // 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 提供了 AbstractConversionServiceOutputConverterAbstractMessageOutputConverterBeanOutputConverterMapOutputConverterListOutputConverter 實現

Structured Output Class Hierarchy
  • AbstractConversionServiceOutputConverter<T> - 提供一個預配置的 GenericConversionService,用於將 LLM 輸出轉換為所需格式。未提供預設的 FormatProvider 實現。

  • AbstractMessageOutputConverter<T> - 提供一個預配置的 MessageConverter,用於將 LLM 輸出轉換為所需格式。未提供預設的 FormatProvider 實現。

  • 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

使用轉換器

以下章節提供了關於如何使用可用轉換器生成結構化輸出的指南。

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().getText());

生成 Schema 中的屬性排序

BeanOutputConverter 透過 @JsonPropertyOrder 註解支援在生成的 JSON schema 中進行自定義屬性排序。無論屬性在類或記錄中的宣告順序如何,此註解都允許您指定屬性應出現在 schema 中的確切順序。

例如,為了確保 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().getText());

Map 輸出轉換器

以下程式碼片段展示瞭如何使用 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());

List 輸出轉換器

以下程式碼片段展示瞭如何使用 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 模式

一些 AI 模型提供專門的配置選項來生成結構化(通常是 JSON)輸出。

  • OpenAI 結構化輸出 可以確保您的模型生成嚴格符合您提供的 JSON Schema 的響應。您可以選擇 JSON_OBJECT(保證模型生成的訊息是有效的 JSON)或帶有提供的 schema 的 JSON_SCHEMA(保證模型生成的響應與您提供的 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。