Spring AI - 結構化輸出

工程 | Christian Tzolov | 2024年5月09日 | ...

更新:(2024年6月4日) 新增了使用新的流暢 ChatClient API 進行結構化輸出的程式碼片段。

更新:(2024年5月17日) 增加了 BeanOutputConverter 的 泛型類型 支援。

科學研究事物的一塊塊和一點點,並假定其連續性;藝術只研究事物的連續性,並假定其一塊塊和一點點。- Robert M. Pirsig

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

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

structured-output-architecture

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

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

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

請注意,不能保證 AI 模型會按照請求返回結構化輸出。 它可能不理解提示,或者可能無法產生請求的結構化輸出。


提示: 如果您不喜歡深入研究 API 的細節,可以跳過下一段,直接跳到使用轉換器部分。


1. 結構化輸出 API

StructuredOutputConverter 介面定義如下

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

它使用目標結構化類型 T 進行參數化,結合了 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(
      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-hierarchy4

  • AbstractConversionServiceOutputConverter<T> - 提供一個預先配置的 GenericConversionService,用於將 LLM 輸出轉換為所需的格式。未提供預設的 FormatProvider 實作。
  • AbstractMessageOutputConverter<T> - 提供一個預先配置的 MessageConverter,用於將 LLM 輸出轉換為所需的格式。未提供預設的 FormatProvider 實作。
  • BeanOutputConverter - 使用指定的 Java 類別(例如 Bean)或 ParameterizedTypeReference 進行配置,此轉換器採用 FormatProvider 實作,該實作指示 AI 模型產生符合從指定 Java 類別衍生的 DRAFT_2020_12JSON Schema 的 JSON 響應。隨後,它使用 ObjectMapper 將 JSON 輸出反序列化為目標類別的 Java 物件實例。
  • MapOutputConverter 使用 FormatProvider 實作擴展了 AbstractMessageOutputConverter 的功能,該實作引導 AI 模型產生符合 RFC8259 的 JSON 響應。此外,它還包含一個轉換器實作,該實作使用提供的 MessageConverter 將 JSON 有效負載轉換為 java.util.Map<String, Object> 實例。
  • ListOutputConverter 擴展了 AbstractConversionServiceOutputConverter,並包含一個針對逗號分隔清單輸出量身定制的 FormatProvider 實作。轉換器實作採用提供的 ConversionService 將模型文字輸出轉換為 java.util.List

2. 使用轉換器

以下各節提供有關使用可用轉換器來產生結構化輸出的詳細指導。 原始碼可在 spring-ai-structured-output-demo 儲存庫中找到。

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

String userInputTemplate = """
   Generate the filmography of 5 movies for {actor}.
   {format}
   """;
BeanOutputConverter<ActorsFilms> beanOutputConverter = new BeanOutputConverter<>(ActorsFilms.class);
String format = beanOutputConverter.getFormat();
String actor = "Tom Hanks";
Prompt prompt = new Prompt(
   new PromptTemplate(userInputTemplate, Map.of("actor", actor, "format", format)).createMessage());
Generation generation = chatClient.call(prompt).getResult();
ActorsFilms actorsFilms = beanOutputConverter.convert(generation.getOutput().getContent());

支援泛型 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 = outputConverter.getFormat();
String template = """
Generate the filmography of 5 movies for Tom Hanks and Bill Murray.
{format}
""";
Prompt prompt = new Prompt(new PromptTemplate(template, Map.of("format", format)).createMessage());
Generation generation = chatClient.call(prompt).getResult();
List<ActorsFilms> actorsFilms = outputConverter.convert(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 = mapOutputConverter.getFormat();
String userInputTemplate = """
   Provide me a List of {subject}
   {format}
   """;
PromptTemplate promptTemplate = new PromptTemplate(userInputTemplate,
   Map.of("subject", "an array of numbers from 1 to 9 under they key name 'numbers'", "format", format));
Prompt prompt = new Prompt(promptTemplate.createMessage());
Generation generation = chatClient.call(prompt).getResult();
Map<String, Object> result = mapOutputConverter.convert(generation.getOutput().getContent());

列表輸出轉換器 (List Output Converter)

以下程式碼片段展示了如何使用 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 = listOutputConverter.getFormat();
String userInputTemplate = """
   List five {subject}
   {format}
   """;
PromptTemplate promptTemplate = new PromptTemplate(userInputTemplate,   
     Map.of("subject", "ice cream flavors", "format", format));
Prompt prompt = new Prompt(promptTemplate.createMessage());
Generation generation = this.chatClient.call(prompt).getResult();
List<String> list = listOutputConverter.convert(generation.getOutput().getContent());

3. 參考文獻

4. 結論

LLM 產生結構化輸出的能力,讓開發者能夠無縫整合下游應用程式。 Structured Output Converters 促進了這個過程,確保模型輸出能可靠地解析成結構化的格式,例如 JSON 或 Java Classes。

本文提供了轉換器 (converter) 的實用範例,例如 BeanOutputConverterMapOutputConverterListOutputConverter,展示了針對不同資料類型產生結構化輸出。

總之,Spring AI 的結構化輸出轉換器為尋求利用 LLM 的強大功能,同時確保其應用程式透過結構化輸出格式的相容性和可靠性的開發人員,提供了一個強大的解決方案。

取得 Spring 電子報

持續關注 Spring 電子報

訂閱

領先一步

VMware 提供培訓和認證,以加速您的進展。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

了解更多

即將到來的活動

查看 Spring 社群中所有即將到來的活動。

查看全部