Spring AI 2.1.0-M1が示す、生成AIアプリケーションの次の設計
Spring AI 2.1.0-M1がMaven Centralで公開されました。今回のリリースは、Spring Boot 4.2をベースラインとする2.1系の最初のマイルストーンです。
注目すべき変更は、単なるAPI追加ではありません。生成AIアプリケーションで複雑になりつつある、次の3つの課題に対応しています。
- 推論、ツール呼び出し、テキスト、画像などを元の順序で保持する
- OpenAIの推論モデルとツールを組み合わせたエージェント処理を扱う
- 外部で計算済みのEmbeddingをVector Storeへ登録する
本稿では、これらの変更がSpring Boot/JavaでRAGやAIエージェントを開発する際に何を意味するのかを、実装例と設計上の注意点から整理します。
まず押さえたいリリースの位置付け
Spring AI 2.1.0-M1は、2.1系の最初のマイルストーンです。新APIを試せる段階である一方、GAまでに変更される可能性があります。
また、このバージョンの基盤はSpring Boot 4.2です。既存アプリケーションへ導入する場合は、Spring Bootのアップグレード影響、依存ライブラリとの互換性、CI環境のJavaバージョンなどを先に確認する必要があります。
本番環境で直ちに全面移行するというより、次のような限定された検証から始めるのが現実的です。
- 新しいOpenAI Responses APIを使うエージェント処理の試作
- Message Partsを利用したマルチモーダル会話の検証
- 外部Embeddingパイプラインからのデータ移行
- 将来の2.1系への移行を見据えたインターフェース設計
Message Partsが解決する「応答の順序が失われる」問題
これまでのSpring AIのメッセージは、基本的にテキストを中心に、ツール呼び出しやメディアなどを別のリストで扱う構造でした。この形式は単純なチャットには適していますが、現在のモデルが返す複雑な応答を正確に表現するには制約があります。
例えば、モデルの応答が次の順序だったとします。
- 推論内容
- ツール呼び出し
- ツールの結果
- 追加のテキスト
- 画像
これらをテキスト、ツール呼び出し、メディアという別々の属性に分けると、プロバイダーが返した順序を復元できない場合があります。特に、ツール呼び出しを何度も行うエージェントでは、会話履歴の忠実な再現が重要です。
Spring AI 2.1.0-M1では、メッセージの内容を順序付きのMessagePartとして保持します。主なパートは次のとおりです。
TextPartReasoningPartToolCallPartToolResultPartMediaPartUnknownPart
UnknownPartが用意されている点も実務上重要です。Spring AI側でまだモデル化されていないプロバイダー固有のブロックも、未対応だからといって黙って破棄せず、元のJSONを保持できます。
パートを明示的な順序で組み立てる
例えば、2つの画像についてモデルに質問するメッセージは、次のように構築できます。
UserMessage question = UserMessage.builder()
.part(TextPart.of("What changed between these two charts?"))
.part(MediaPart.of(firstChart))
.part(MediaPart.of(secondChart))
.build();
AssistantMessage answer = chatModel
.call(new Prompt(question))
.getResult()
.getOutput();
for (ReasoningPart reasoning : answer.getReasoning()) {
System.out.println("Reasoning: " + reasoning.text());
}
System.out.println(answer.getText());
従来のgetText()やgetMedia()などのアクセサーは、Message Partsをもとにしたビューとして利用できます。そのため、既存コードをすぐに全面書き換えなくても、段階的に新しい構造へ移行できます。
ツールループでは「モデルに返す情報」が重要になる
推論モデルやエージェントでは、モデルが出力した推論、ツール呼び出し、ツール結果を次のターンへ正確に返す必要があります。ReasoningPartやToolCallPartは、プロバイダー固有の署名などを含むOpaquePayloadを保持できます。
この情報は、単なる表示用テキストとは異なります。プロバイダーによっては、推論に関連する署名や暗号化されたデータを次のリクエストでそのまま返す必要があります。Message Partsは、こうした「人間が読むためのテキスト」と「モデル間で再利用する構造化データ」を分離して扱うための基盤になります。
ただし、2.1.0-M1時点でMessage Partsをネイティブに生成・消費するのは、新しいOpenAiResponsesChatModelが中心です。他のChatModel実装にも展開される予定ですが、現段階ではプロバイダーごとの差を意識した設計が必要です。
OpenAI Chat CompletionsとResponses APIの使い分け
Spring AI 2.1.0-M1では、既存のChat CompletionsベースのOpenAiChatModelに加えて、OpenAiResponsesChatModelが追加されました。
切り替えはプロパティで行えます。
spring.ai.openai.chat.api=responses
接続設定など、その他のspring.ai.openai設定は引き続き利用できます。
Responses APIを選ぶ主な理由
今回のリリース情報で示されている大きな理由は、推論とツール呼び出しの組み合わせです。GPT-5.4以降の現行フラッグシップモデルを使ってエージェントを構築する場合、Chat Completionsでは推論の設定によってツール呼び出しに制約があります。一方、Responses APIはその組み合わせを前提に設計されています。
したがって、次のようなアプリケーションではResponses APIを優先的に検討できます。
- 複数のツールを順番に呼び出す業務エージェント
- 推論強度を指定して回答品質と処理コストを調整するアプリケーション
- Web検索、ファイル検索、コード実行などのホスト型ツールを利用する処理
- 画像やPDFを入力に含めるマルチモーダルアプリケーション
- 構造化出力とツール呼び出しを組み合わせる処理
OpenAiResponsesChatOptionsでは、推論の強度や要約、出力の詳細度、構造化出力、画像・PDF入力などを扱えます。また、OpenAI側で実行されるWeb検索、ファイル検索、コードインタープリター、リモートMCP、画像生成などは、HostedToolで利用できます。
Stateless設計をどう理解するか
OpenAiResponsesChatModelはステートレスです。各呼び出しでは、Promptに含まれる情報全体を送信します。このため、Spring AIのChatMemory、アドバイザー、RAGなどの既存の設計を組み合わせられます。
一方で、会話履歴をどこへ保存するかは注意が必要です。2.1.0-M1では、Message Partsを含む推論情報を保持できるのはInMemoryChatMemoryRepositoryが中心で、永続リポジトリはまだMessage Partsを保存しません。永続メモリを使う構成では、会話自体は継続できても、各ターンの推論情報が次回へ引き継がれず、モデルが毎回最初から推論する可能性があります。
本番の会話基盤を設計する場合は、少なくとも次の点を分けて考える必要があります。
- ユーザーに表示するテキスト履歴
- ツール呼び出しとツール結果の履歴
- 推論やプロバイダー固有の署名情報
- 将来再送する必要がある未認識のメッセージブロック
- 個人情報や機密情報を含むメモリの保存期間
外部EmbeddingをVector Storeへ登録するupsert()
これまでのVectorStore.add()は、Vector Storeが保持するEmbeddingモデルを使ってベクトルを計算する設計でした。しかし実際のRAG基盤では、Embeddingをアプリケーションの外部で作るケースがあります。
例えば、次のような構成です。
- 大量データをプロバイダーのバッチAPIで夜間処理する
- データ基盤チームがEmbeddingパイプラインを管理する
- 画像や音声などをマルチモーダルモデルでベクトル化する
- 既存システムから本文とベクトルを移行する
- コストやスループットの都合でオンライン生成を避ける
2.1.0-M1では、計算済みのベクトルをEmbeddedDocumentと組み合わせてVectorStore.upsert()へ渡せます。
float[] embedding = loadEmbeddingFromPipeline();
Document document = new Document(
"8f14e45d-ceea-467a-9a3e-5b1c2d6f7a90",
"some text",
Map.of("source", "manual")
);
vectorStore.upsert(List.of(
new EmbeddedDocument(document, embedding)
));
upsert()は、同じIDが存在する場合に既存行を置き換えます。安定したIDを使えば、途中で失敗した取り込みジョブを再実行しやすくなります。大量データの移行では、次のような処理設計と相性がよい機能です。
- 外部パイプラインで本文とEmbeddingを生成する
- データを安定したドキュメントIDで分割する
- バッチ単位で
upsert()する - 失敗したバッチだけ再実行する
- 件数とベクトル次元数を検証する
ベクトルの次元数はインデックスの幅と一致している必要があります。Spring AI 2.1.0-M1では、バッチ内のデータが書き込まれる前にインデックス幅が検証されます。移行時には、Embeddingモデルを変更した際に次元数が変わっていないか、古いデータと新しいデータが混在していないかを確認することが重要です。
このマイルストーンでupsert()に対応する主なVector Storeは次のとおりです。
- pgvector
- Redis
- Elasticsearch
- Qdrant
その他のVector Storeでは、対応するまで利用できません。導入前に、採用しているストアの対応状況と、既存のインデックス設定を切り分けて評価する必要があります。
本文をVector Storeの外部に置く設計
画像や大容量ファイルなど、本文をVector Storeへ直接保存したくない場合は、DocumentMetadata.CONTENT_REFで外部コンテンツへの参照を持たせる設計も可能です。
例えば、次のように役割を分離できます。
- ベクトル検索用のEmbeddingはVector Storeへ保存する
- 検索結果の識別子や参照先をメタデータへ保存する
- 実体の画像やファイルはオブジェクトストレージへ保存する
- 回答生成時に必要なコンテンツだけ取得する
この構成は、検索インデックスのサイズ抑制や、アクセス制御の分離に有効です。一方で、参照先の削除、権限チェック、ライフサイクル管理はアプリケーション側の責務になります。
実務での移行方針
Spring AI 2.1.0-M1を既存アプリケーションへ導入する場合、すべての機能を一度に切り替える必要はありません。目的別に段階的な検証を行う方が、変更の影響を把握しやすくなります。
1. 既存のチャット処理はアクセサー互換性を活用する
既存コードでgetText()やgetToolCalls()を利用している場合、Message Parts導入後も従来のアクセサーを利用できます。まずは依存関係とSpring Bootの更新を行い、既存テストが通ることを確認します。
その後、ツール呼び出しの順序や推論情報が必要な箇所から、getReasoning()や個別のパート処理へ移行するとよいでしょう。
2. Responses APIは限定的なエージェントから使う
既存の全モデル呼び出しを一括してResponses APIへ変更するのではなく、推論とツール呼び出しが必要なユースケースに限定して評価します。
評価項目には、次のような観点を含めます。
- ツール呼び出しが期待した順序で実行されるか
- ツール結果を次のターンへ正しく返せるか
- ストリーミング時にMessage Partsを正しく集約できるか
- 推論強度によるレイテンシーとコストの変化
- ChatMemoryの保存方式が要件を満たすか
- 失敗時に再実行可能な状態を作れるか
3. 外部Embeddingは再取り込み可能なジョブにする
upsert()を使う取り込み処理では、安定したID、バッチ単位の再実行、次元数の検証を最初から組み込みます。
特に、Embeddingモデルを変更する場合は、既存ベクトルを上書きするだけでよいのか、別インデックスを作って切り替えるのかを決める必要があります。モデルや次元数が異なるベクトルを同じインデックスへ混在させる設計は避けるべきです。
周辺の改善にも注目
今回のリリースでは、主要機能以外にも運用上役立つ修正が含まれています。
- OpenAIのタイムアウト設定が再び適切に反映されるようになった
- OpenAIのstrictモード向けツールスキーマで
additionalProperties: falseが補完される - ストリーミング処理を1分で打ち切る意図しない固定タイムアウトが修正された
- MCPツールなど、Spring AI以外で生成されたスキーマにもstrictモード対応が適用される
- Bedrockでメディアだけを含むユーザーメッセージが不正な空テキストブロックを送らなくなった
TextReaderのリソースストリームが適切にクローズされるようになったInMemoryChatMemoryRepositoryが保存時にメッセージリストをコピーするようになった- MCPステートレスサーバーをカスタマイズするためのCustomizerが追加された
生成AIアプリケーションでは、モデル機能だけでなく、ストリーミング、タイムアウト、スキーマ、リソース管理の不具合が運用障害につながります。新機能を使わない場合でも、これらの修正を目的に検証する価値があります。
まとめ:2.1系は「複雑なモデル応答を正しく扱う」ための基盤
Spring AI 2.1.0-M1の中心テーマは、生成AIモデルとのやり取りを単純な文字列入出力から、順序付きの構造化メッセージへ拡張することです。
実務上の要点は次のとおりです。
- Message Partsにより、推論、ツール呼び出し、メディア、未知のブロックを順序付きで保持できる
- OpenAI Responses APIは、推論とツールを組み合わせるエージェント処理に適している
- Chat Completionsからの切り替えはプロパティで行える
- Responses API利用時は、Message Partsを含む永続チャットメモリの制約に注意する
VectorStore.upsert()により、外部計算済みEmbeddingを再実行可能な形で登録できる- ベクトル次元数、安定したID、ストレージの対応状況を移行設計に組み込む
- M1ではAPIや対応範囲が変わる可能性があるため、限定的な検証から始める
特に、RAGからエージェントへ発展させる計画があるチームにとって、Message PartsとResponses APIは将来の会話履歴・ツール実行基盤に関わる変更です。まずは小さなツールループや既存Embeddingの移行ジョブで試し、モデル応答の保持方法と運用要件を確認することが、2.1系を安全に取り入れる第一歩になります。
出典: Spring公式ブログ
