結構化輸出轉換器

截至 2024 年 2 月 5 日,舊的 OutputParserBeanOutputParserListOutputParserMapOutputParser 類別已被棄用,取而代之的是新的 StructuredOutputConverterBeanOutputConverterListOutputConverterMapOutputConverter 實作。後者是前者的直接替代品,並提供相同的功能。變更的主要原因是命名,因為實際上沒有執行任何剖析,同時也與 Spring 的 org.springframework.core.convert.converter 套件保持一致,帶來了一些改進的功能。

對於依賴可靠地剖析輸出值的下游應用程式而言,LLM 產生結構化輸出的能力非常重要。開發人員希望快速地將 AI 模型的結果轉換為資料類型,例如 JSON、XML 或 Java 類別,以便傳遞給其他應用程式函數和方法。

Spring AI 的 結構化輸出轉換器 有助於將 LLM 輸出轉換為結構化格式。如下圖所示,此方法圍繞 LLM 文字完成端點運作

Structured Output Converter Architecture

使用通用完成 API 從大型語言模型 (LLM) 生成結構化輸出,需要仔細處理輸入和輸出。結構化輸出轉換器在 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 時的資料流程。

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 模型產生符合 DRAFT_2020_12 的 JSON 響應,JSON Schema 源自指定的 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());

生成架構中的屬性排序

BeanOutputConverter 透過 @JsonPropertyOrder 註解支援在生成的 JSON 架構中自訂屬性排序。此註解允許您指定屬性應在架構中出現的確切順序,而與它們在類別或記錄中的宣告順序無關。

例如,為了確保 ActorsFilms 記錄中屬性的特定排序

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

此註解適用於記錄和常規 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());

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

內建 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。