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

再帰アドバイザー

再帰アドバイザーとは何ですか ?

Advisors Recursive 再帰アドバイザは、下流のアドバイザチェーンを複数回ループできる特殊な型のアドバイザです。このパターンは、たとえば次のような特定の条件が満たされるまで LLM を繰り返し呼び出す必要がある場合に便利です。

  • 呼び出す必要がなくなるまでツール呼び出しをループで実行する

  • 構造化された出力を検証し、検証が失敗した場合は再試行する

  • リクエストの変更による評価ロジックの実装

  • リクエストを変更して再試行ロジックを実装する

CallAdvisorChain.copy(CallAdvisor after) 法は、再帰アドバイザパターンを可能にする重要なユーティリティです。この手法は、元のチェーンで指定されたアドバイザの後に続くアドバイザのみを含む新しいアドバイザチェーンを作成し、再帰アドバイザが必要に応じてこのサブチェーンを呼び出すことを可能にします。このアプローチにより、以下のことが保証されます。

  • 再帰アドバイザーはチェーン内の残りのアドバイザーをループすることができる

  • チェーンの他のアドバイザーは各反復を観測しインターセプトすることができる

  • アドバイザーチェーンは適切な順序と観測性を維持する

  • 再帰アドバイザーはそれ以前に実行されたアドバイザーを再実行しない

組み込みの再帰アドバイザー

Spring AI には、このパターンを示す 2 つの組み込み型再帰アドバイザーが搭載されています。

ToolCallingAdvisor

ToolCallingAdvisor は、ツール呼び出しループをアドバイザーチェーンの一部として実装し、ChatModel の内部実行に依存しません。ツールが存在する場合は DefaultChatClient によって自動的に登録され、ChatClient がツール拡張型対話を行うためのデフォルトのメカニズムとなります。

強調表示:

  • ToolExecutionEligibilityChecker がツール呼び出しがなくなったと報告するまで、アドバイザーチェーンをループ処理します。

  • callAdvisorChain.copy(this) を使用して再帰呼び出し用のサブチェーンを作成します。他のアドバイザーは、各反復処理を監視したり、インターセプトしたりできます。

  • return-direct をサポート: ツールの結果が returnDirect = true の場合、アドバイザーはループを中断し、ツールの結果を LLM に送り返さずに呼び出し元に返します。

  • DefaultChatClient が単一ツールアドバイザーの不変条件を強制するために使用する ToolAdvisor マーカーインターフェースを実装します。カスタムサブクラスは、代替として透過的に登録されます。

  • ループの各反復にわたってトークンの使用量を累積するため、最終的な ChatResponse は最後のモデル呼び出しだけでなく、すべてのモデル呼び出しの累積使用量を報告します ( 複数ステップのフローにおける累積使用量を参照)。

簡単な例:

var toolCallingAdvisor = ToolCallingAdvisor.builder()
    .toolCallingManager(toolCallingManager)
    .advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
    .build();

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(toolCallingAdvisor)
    .build();

ビルダー API 全体、フックメソッド、構成オプション、メモリアドバイザの順序付けの相互作用、ユーザー制御の実行パターン、カスタムサブクラス拡張パターンについては、ToolCallingAdvisor を参照してください。

ツールループがより広範なツール呼び出しアーキテクチャにどのように適合するかについての概念的な概要については、ツール呼び出し: The Tool Calling Loop を参照してください。

` ToolCallingAdvisor の拡張フックを使用して段階的なツール開示を実装する具象サブクラスについては、ツール検索ツールを参照してください。

StructuredOutputValidationAdvisor

StructuredOutputValidationAdvisor は、構造化された JSON 出力を JSON スキーマに対して検証し、検証に失敗した場合は、設定可能な回数まで呼び出しを再試行します。

主な機能:

  • 想定される出力型から JSON スキーマを生成するか、事前に指定されたスキーマ文字列を受け入れます。

  • LLM レスポンスをスキーマに対して検証します。

  • 検証が失敗した場合に呼び出しを再試行します(デフォルト: 最大 3 回)。

  • 再試行時に検証エラーメッセージを追加してプロンプトを表示することで、モデルの自己修正を支援します。

  • 再帰呼び出しのためのサブチェーンを作成するために callAdvisorChain.copy(this) を使用します。

  • トークンの使用量は、検証試行ごとに累積されるため、返される ChatResponse は、最終試行だけでなく、すべての再試行の累積使用量を報告します ( 複数ステップのフローにおける累積使用量を参照)。

  • オプションでカスタム JsonMapper をサポートします。

アドバイザーは、outputType (スキーマを自動的に生成)または outputJsonSchema (事前に指定されたスキーマ文字列)のいずれかで構成できます。この 2 つのオプションは相互に排他的です。

outputType の例:

var validationAdvisor = StructuredOutputValidationAdvisor.builder()
    .outputType(MyResponseType.class)
    .maxRepeatAttempts(3)
    .build();

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(validationAdvisor)
    .build();

あらかじめ用意された JSON スキーマを使用した例:

var validationAdvisor = StructuredOutputValidationAdvisor.builder()
    .outputJsonSchema(myConverter.getJsonSchema())
    .build();

あるいは、EntityParamSpec コンシューマーを介して、アドバイザーを手動で構成することなく、entity() 呼び出しでスキーマ検証を直接有効にすることもできます。

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

より詳細な使用方法については、スキーマ検証と自己修正を参照してください。

Accumulating Token Usage in Custom Recursive Advisors

A recursive advisor performs more than one model call per invocation, so it must accumulate the token usage of every call to report a correct cumulative total; otherwise only the last call’s usage is visible to the caller. Spring AI provides org.springframework.ai.chat.client.advisor.UsageAccumulator to make this straightforward. Create one instance per adviseCall invocation (or per stream subscription, inside a Flux.defer, to keep it subscription-local), fold each round’s response in with addRoundResponse(…​), and stamp the cumulative total onto the final response with applyAccumulatedUsage(…​):

UsageAccumulator usage = new UsageAccumulator();
ChatClientResponse response;
do {
    response = callAdvisorChain.copy(this).nextCall(request);
    usage.addRoundResponse(response.chatResponse());
    // ... decide whether to loop again ...
}
while (loopAgain);
return usage.applyAccumulatedUsage(response);

The underlying token math lives in org.springframework.ai.support.UsageCalculator (accumulateResponseUsage and withUsage), which UsageAccumulator wraps.