このバージョンはまだ開発中であり、まだ安定しているとは見なされていません。最新の安定バージョンについては、Spring AI 2.0.1 を使用してください!

構造化された出力

大規模な言語モデルは、テキスト入力・テキスト出力のシステムです。下流のコードがフィールドに基づいてルーティングしたり、値を永続化したり、結果に対してブランチを実行したりする必要が生じた瞬間、そのテキストは型付きレコードに変換されなければなりません。Structured output はこのギャップを埋めます。モデルはスキーマに準拠したテキストを生成するように制御され、アプリケーションはそれを解析して、コードベースの残りの部分が他のドメイン型と同様に扱うことができる型付きオブジェクトに変換します。

Spring AI は、.entity(…​) を介して ChatClient 流れるような API 上で構造化された出力を直接公開します。返されるデータの形状を表す Java 型を定義するだけで、Spring AI が残りの処理を行います。つまり、その型から JSON スキーマが生成され、モデルはそのスキーマに従うように指示され、レスポンスは指定した型に逆直列化されます。

このページでは、ChatClient のハイレベルなパスについて説明します。信頼性スイッチと下位レベルの API については、以下を参照してください。

入力された回答

取得したい形状に対応する Java レコードを定義します。

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

ChatClient にデータを入力するように依頼します。生のテキスト応答を返す .content() で呼び出しを終了する代わりに、.entity(…​) で終了し、ターゲット型を渡します。

ActorsFilms films = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .call()
    .entity(ActorsFilms.class);

結果として得られるのは、型付きの ActorsFilms であり、これをコードの残りの部分に渡すことができます。

films.actor();     // "Tom Hanks"
films.movies();    // ["Forrest Gump", "Cast Away", ...]

バックグラウンドでは、Spring AI は 3 つの処理を実行しました。まず、スキーマジェネレーターが ActorsFilms レコードを JSON スキーマに変換し、そのスキーマがプロンプトのシステムコンテキストに追加され、モデルの JSON 応答が型 コンバーターに渡され、レコードに解析されました。

Structured Output Basic Flow

これは Spring AI がサポートするすべてのモデルで動作します。特定のプロバイダーに依存するものではありません。

.entity(…​) は .call() -only と同じです。型付き解析には完全なレスポンスが必要なため、ストリーミングパスでは利用できません (.stream() は型付きオブジェクトではなく、テキストチャンクを返します)。これは、このページに記載されているすべてのバリアント (Class、ParameterizedTypeReference、カスタムコンバーター、信頼性スイッチの有無に関わらず) に適用されます。

デフォルトの .entity(…​) 呼び出しには保証がありません。モデルはスキーマに一致する JSON を生成するように要求されますが、強制されるわけではありません。ほとんどの場合は準拠しますが、余分なフィールドを返したり、必須フィールドを省略したり、JSON を散文で囲んだりして、パーサーがエラーをスローすることがあります。以下の 2 つのスイッチは、この問題に対処します。

総称型: リスト、地図、その先へ

.entity(Class) は具象クラス用です。ジェネリクス型 (List<ActorsFilms>、Map<String, ActorsFilms>) には ParameterizedTypeReference を使用してください。

List<ActorsFilms> films = chatClient.prompt()
    .user("Generate filmographies for three random actors.")
    .call()
    .entity(new ParameterizedTypeReference<List<ActorsFilms>>() {});

信頼性スイッチ: EntityParamSpec

すべての .entity(…​) (および .responseEntity(…​)) オーバーロードは、2 つの独立した組み合わせ可能な動作を可能にするオプションの Consumer<EntityParamSpec> を受け入れます。

不正な出力で失敗しないでください: validateSchema()

validateSchema() は自己修正型の再試行ループを有効にします。Spring AI はエンティティのスキーマに対してレスポンスを検証し、検証に失敗した場合は、特定のエラーがプロンプトに追加され、呼び出しが再発行されます (デフォルトでは最大 3 回)。

ActorsFilms films = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .call()
    .entity(ActorsFilms.class, spec -> spec.validateSchema());

再試行ループの仕組みとカスタマイズ方法については、スキーマ検証と自己修正を参照してください。

上流保証の強化: useProviderStructuredOutput()

useProviderStructuredOutput() はスキーマを API レベルの制約としてプロバイダに送信するため、プロバイダのランタイムはプロンプトの指示に頼るのではなく、準拠を強制します。

ActorsFilms films = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .call()
    .entity(ActorsFilms.class, spec -> spec.useProviderStructuredOutput());

This requires the underlying model to support native structured output. See プロバイダーネイティブ構造化出力 for supported providers and limitations.

Combining Both

The two switches solve different problems and compose naturally:

ActorsFilms films = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .call()
    .entity(ActorsFilms.class, spec -> spec
        .useProviderStructuredOutput()
        .validateSchema());

useProviderStructuredOutput() minimizes the chance of malformed output by constraining the model at the API level. validateSchema() catches the residual cases — provider edge cases, reasoning-model quirks — and corrects them automatically. Reach for both when downstream code cannot tolerate shape drift.

Getting the Full Response

.entity(…​) returns only the parsed object. If you also need the underlying ChatResponse — for token usage, observability metadata, or anything beyond the entity — use .responseEntity(…​):

ResponseEntity<ChatResponse, ActorsFilms> result = chatClient.prompt()
    .user("Generate the filmography for a random actor.")
    .call()
    .responseEntity(ActorsFilms.class);

ActorsFilms films = result.entity();
ChatResponse raw = result.response();
long totalTokens = raw.getMetadata().getUsage().getTotalTokens();

It has the same overload set as .entity(…​) — Class, ParameterizedTypeReference, custom StructuredOutputConverter, and the EntityParamSpec consumer all apply.

Custom and Non-JSON Output

When the built-in JSON parsing is not enough — the model wraps JSON in markdown fences, or you need a non-JSON format such as YAML or CSV — pass your own StructuredOutputConverter<T> to .entity(…​). See 出力コンバーター。

虎の巻

You need 使用

デフォルト — works on every provider

.entity(Type.class)

Generic types like List<T>, Map<K,V>

.entity(new ParameterizedTypeReference<…​>() {})

Don’t fail on malformed output

.entity(Type.class, spec → spec.validateSchema())

Stronger upstream guarantees from the provider

.entity(Type.class, spec → spec.useProviderStructuredOutput())

両方 — request constraint + response retry

.entity(Type.class, spec → spec.useProviderStructuredOutput().validateSchema())

Token usage / metadata alongside the entity

.responseEntity(…​) (same overloads)

Model wraps JSON in markdown fences, or non-JSON format

Implement StructuredOutputConverter<T> and pass it to .entity(…​)

Streaming responses

未サポート — .entity(…​) is .call() -only; .stream() returns text chunks, not typed objects

Structured output is a best effort to convert the model output into a structured type. The model is not guaranteed to return the requested structure. Use validateSchema() and/or useProviderStructuredOutput() when correctness matters.
StructuredOutputConverter は LLM ツール呼び出しでは使用されません。この機能は本質的にデフォルトで構造化された出力を提供するためです。