Spring AI - 結構化輸出

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

更新:(2024年04月06日) 添加了使用結構化輸出和新的、流暢的 ChatClient API 的程式碼片段。

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

科學的工作方式是處理事物零散的部分和片段,並假定其連續性;而藝術則只關注事物的連續性,並假定其零散的部分和片段。- 羅伯特·M·波西格

大型語言模型 (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 模型生成符合 DRAFT_2020_12(源自指定 Java 類的 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

2. 使用轉換器

後續章節將提供有關如何使用可用轉換器生成結構化輸出的詳細指南。原始碼可在 spring-ai-structured-output-demo 儲存庫中找到。

Bean 輸出轉換器

以下示例展示瞭如何使用 BeanOutputConverter 生成演員的電影作品列表。

表示演員電影作品的目標記錄

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

以下是利用 BeanOutputConverter 和新的、流暢的 ChatClient API 的方法:

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 生成結構化輸出的能力使使用者能夠與下游應用程式無縫整合。結構化輸出轉換器 促進了這一過程,確保模型輸出能夠可靠地解析為 JSON 或 Java 類等結構化格式。

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

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

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

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

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

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

檢視所有