Spring AI - 結構化輸出

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

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

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

科學處理的是事物的片段和點滴,前提是其連續性;藝術只處理事物的連續性,前提是其片段和點滴。—— 羅伯特·M·波西格

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

Spring AI 的 Structured Output Converters(結構化輸出轉換器)有助於將 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 模型生成符合 DRAFT_2020_12 規範的 JSON 響應,該規範源自指定的 Java 類的 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

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 輸出轉換器

以下片段展示瞭如何使用 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 類等結構化格式。

本文提供了 BeanOutputConverterMapOutputConverterListOutputConverter 等轉換器的實際使用示例,展示瞭如何為不同資料型別生成結構化輸出。

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

獲取 Spring 資訊

訂閱 Spring 資訊,保持連線

訂閱

領先一步

VMware 提供培訓和認證,助您加速前進。

瞭解更多

獲取支援

Tanzu Spring 透過一項簡單的訂閱提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視全部