構造化出力コンバーター

02.05.2024 以降、古い 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 インターフェースを使用すると、出力を Java クラスにマッピングしたり、テキストベースの AI モデル出力から値の配列をマッピングしたりするなど、構造化された出力を取得できます。インターフェース定義は次のとおりです。

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

}

Spring Converter<String, T> (Javadoc) インターフェースと 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 Class Hierarchy
  • AbstractConversionServiceOutputConverter<T> - LLM 出力を目的の形式に変換するための事前構成済みの GenericConversionService (Javadoc) を提供します。デフォルトの FormatProvider 実装は提供されていません。

  • AbstractMessageOutputConverter<T> - LLM 出力を目的の形式に変換するための事前構成済みの MessageConverter (Javadoc) を提供します。デフォルトの FormatProvider 実装は提供されていません。

  • BeanOutputConverter<T> - 指定された Java クラス (例: Bean) または ParameterizedTypeReference (Javadoc) で構成されたこのコンバーターは、指定された Java クラスから派生した DRAFT_2020_12JSON Schema に準拠した JSON レスポンスを生成するように AI モデルに指示する FormatProvider 実装を使用します。その後、ObjectMapper を使用して JSON 出力をターゲットクラスの Java オブジェクトインスタンスに逆直列化します。

  • MapOutputConverter - AI モデルが RFC8259 準拠の JSON レスポンスを生成するようにガイドする FormatProvider 実装により、AbstractMessageOutputConverter の機能が拡張されます。さらに、提供されている 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 = beanOutputConverter.getFormat();

String actor = "Tom Hanks";

String template = """
        Generate the filmography of 5 movies for {actor}.
        {format}
        """;

Generation generation = chatModel.call(
    new Prompt(new PromptTemplate(template, Map.of("actor", actor, "format", format)).createMessage())).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 = chatModel.call(prompt).getResult();

List<ActorsFilms> actorsFilms = outputConverter.convert(generation.getOutput().getContent());

マップ出力コンバーター

次のスニペットは、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 template = """
        Provide me a List of {subject}
        {format}
        """;
PromptTemplate promptTemplate = new PromptTemplate(template,
        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 = chatModel.call(prompt).getResult();

Map<String, Object> result = mapOutputConverter.convert(generation.getOutput().getContent());

リスト出力コンバーター

次のスニペットは、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 template = """
        List five {subject}
        {format}
        """;
PromptTemplate promptTemplate = new PromptTemplate(template,
        Map.of("subject", "ice cream flavors", "format", format));
Prompt prompt = new Prompt(promptTemplate.createMessage());
Generation generation = this.chatModel.call(prompt).getResult();

List<String> list = listOutputConverter.convert(generation.getOutput().getContent());

Build-in JSON mode

Some AI Models provide dedicated configuration options to generate structured (usually JSON) output.

  • OpenAI - provides a spring.ai.openai.chat.options.responseFormat options specifying the format that the model must output. Setting to { "type": "json_object" } enables JSON mode, which guarantees the message the model generates is valid JSON.

  • Azure OpenAI - provides a spring.ai.azure.openai.chat.options.responseFormat options specifying the format that the model must output. Setting to { "type": "json_object" } enables JSON mode, which guarantees the message the model generates is valid JSON.

  • Ollama - provides a spring.ai.ollama.chat.options.format option to specify the format to return a response in. Currently the only accepted value is json.

  • Mistral AI - provides a spring.ai.mistralai.chat.options.responseFormat option to specify the format to return a response in. Setting to { "type": "json_object" } enables JSON mode, which guarantees the message the model generates is valid JSON.