このバージョンはまだ開発中であり、まだ安定しているとは考えられていません。最新のスナップショットバージョンについては、Spring AI 1.0.3 を使用してください。

アドバイザー API

Spring AI アドバイザー API は、Spring アプリケーションで AI 主導のインタラクションをインターセプト、変更、強化するための柔軟で強力な方法を提供します。アドバイザー API を活用することで、開発者はより洗練され、再利用可能で、保守しやすい AI コンポーネントを作成できます。

主な利点としては、繰り返し発生する生成 AI パターンのカプセル化、大規模言語モデル (LLM) との間で送受信されるデータの変換、さまざまなモデルやユースケース間での移植性の提供などが挙げられます。

次の例に示すように、ChatClient API を使用して既存のアドバイザーを構成できます。

ChatMemory chatMemory = ... // Initialize your chat memory store
VectorStore vectorStore = ... // Initialize your vector store

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build(), // chat-memory advisor
        QuestionAnswerAdvisor.builder(vectorStore).build()    // RAG advisor
    )
    .build();

var conversationId = "678";

String response = this.chatClient.prompt()
    // Set advisor parameters at runtime
    .advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, conversationId))
    .user(userText)
    .call()
	.content();

ビルダーの defaultAdvisors() メソッドを使用して、ビルド時にアドバイザーを登録することをお勧めします。

アドバイザーは Observability スタックにも参加するため、その実行に関連するメトリクスとトレースを表示できます。

コアコンポーネント

API は、非ストリーミングシナリオ用の CallAdvisor と CallAdvisorChain、およびストリーミングシナリオ用の StreamAdvisor と StreamAdvisorChain で構成されています。また、封印されていないプロンプトリクエストを表す ChatClientRequest、チャット補完レスポンスを表す ChatClientResponse も含まれます。どちらもアドバイザーチェーン全体で状態を共有するために advise-context を保持しています。

Advisors API Classes

adviseCall() と adviseStream() は主要なアドバイザーメソッドであり、通常は、封印されていないプロンプトデータの調査、プロンプトデータのカスタマイズと拡張、アドバイザーチェーン内の次のエンティティの呼び出し、オプションでのリクエストのブロック、チャット補完レスポンスの調査、処理エラーを示す例外のスローなどのアクションを実行します。

さらに、getOrder() メソッドは チェーン内のアドバイザーの順序を決定し、getName() は一意のアドバイザー名を提供します。

Spring AI フレームワークによって作成されたアドバイザーチェーンを使用すると、getOrder() 値順に複数のアドバイザーを順番に呼び出すことができます。値の低いアドバイザーが最初に実行されます。自動的に追加された最後のアドバイザーは、リクエストを LLM に送信します。

次のフロー図は、アドバイザーチェーンとチャットモデル間のやり取りを示しています。

Advisors API Flow
  1. Spring AI フレームワークは、ユーザーの Prompt と空のアドバイザー context オブジェクトから ChatClientRequest を作成します。

  2. チェーンの各アドバイザーはリクエストを処理し、場合によってはリクエストを変更します。または、次のエンティティを呼び出す呼び出しを行わないことでリクエストをブロックすることもできます。後者の場合、アドバイザーはレスポンスを入力する責任があります。

  3. フレームワークによって提供される最終アドバイザーは、リクエストを Chat Model に送信します。

  4. チャットモデルのレスポンスはアドバイザーチェーンを介して返され、ChatClientResponse に変換されます。その後、共有アドバイザー context インスタンスが追加されます。

  5. 各アドバイザーはレスポンスを処理または変更できます。

  6. 最終的な ChatClientResponse は、ChatCompletion を抽出することによってクライアントに返されます。

アドバイザーのオーダー

チェーンのアドバイザーの実行順序は、getOrder() メソッドによって決定されます。理解すべき重要なポイント:

  • 順序値の低いアドバイザーが最初に実行されます。

  • アドバイザーチェーンはスタックとして動作します。

    • チェーンの最初のアドバイザーが最初にリクエストを処理します。

    • また、レスポンスを処理するのも最後です。

  • 実行順序を制御するには:

    • アドバイザーが チェーンで最初に実行されるようにするには (リクエスト処理の場合は最初に、レスポンス処理の場合は最後に)、順序を Ordered.HIGHEST_PRECEDENCE に近い値に設定します。

    • アドバイザーが チェーンで最後に実行されるようにするには (リクエスト処理の場合は最後に、レスポンス処理の場合は最初に)、順序を Ordered.LOWEST_PRECEDENCE に近い値に設定します。

  • 値が大きいほど優先度は低くなると解釈されます。

  • 複数のアドバイザーが同じ順序値を持つ場合、実行順序は保証されません。

順序と実行シーケンスの間に矛盾があるように見えるのは、アドバイザーチェーンのスタックのような性質によるものです。

  • 最も高い優先順位 (最も低い順序値) を持つアドバイザーがスタックの一番上に追加されます。

  • スタックが解放されると、最初にリクエストが処理されます。

  • スタックが巻き戻されるときに、レスポンスを最後に処理します。

念のため、Spring Ordered インターフェースのセマンティクスを以下に示します。

public interface Ordered {

    /**
     * Constant for the highest precedence value.
     * @see java.lang.Integer#MIN_VALUE
     */
    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

    /**
     * Constant for the lowest precedence value.
     * @see java.lang.Integer#MAX_VALUE
     */
    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

    /**
     * Get the order value of this object.
     * <p>Higher values are interpreted as lower priority. As a consequence,
     * the object with the lowest value has the highest priority (somewhat
     * analogous to Servlet {@code load-on-startup} values).
     * <p>Same order values will result in arbitrary sort positions for the
     * affected objects.
     * @return the order value
     * @see #HIGHEST_PRECEDENCE
     * @see #LOWEST_PRECEDENCE
     */
    int getOrder();
}

入力側と出力側の両方で チェーンの先頭にする必要のあるユースケースの場合:

  1. それぞれの側に別々のアドバイザーを使用します。

  2. 異なる順序値で設定します。

  3. アドバイザーコンテキストを使用して、それらの間で状態を共有します。

API の概要

主要なアドバイザーインターフェースはパッケージ org.springframework.ai.chat.client.advisor.api にあります。独自のアドバイザーを作成するときに使用する主要なインターフェースは次のとおりです。

public interface Advisor extends Ordered {

	String getName();

}

同期アドバイザーとリアクティブアドバイザーの 2 つのサブインターフェースは

public interface CallAdvisor extends Advisor {

	ChatClientResponse adviseCall(
		ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain);

}

および

public interface StreamAdvisor extends Advisor {

	Flux<ChatClientResponse> adviseStream(
		ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain);

}

アドバイスの チェーンを継続するには、アドバイスの実装で CallAdvisorChain と StreamAdvisorChain を使用します。

インターフェースは

public interface CallAdvisorChain extends AdvisorChain {

	/**
	 * Invokes the next {@link CallAdvisor} in the {@link CallAdvisorChain} with the given
	 * request.
	 */
	ChatClientResponse nextCall(ChatClientRequest chatClientRequest);

	/**
	 * Returns the list of all the {@link CallAdvisor} instances included in this chain at
	 * the time of its creation.
	 */
	List<CallAdvisor> getCallAdvisors();

}

および

public interface StreamAdvisorChain extends AdvisorChain {

	/**
	 * Invokes the next {@link StreamAdvisor} in the {@link StreamAdvisorChain} with the
	 * given request.
	 */
	Flux<ChatClientResponse> nextStream(ChatClientRequest chatClientRequest);

	/**
	 * Returns the list of all the {@link StreamAdvisor} instances included in this chain
	 * at the time of its creation.
	 */
	List<StreamAdvisor> getStreamAdvisors();

}

アドバイザーの導入

アドバイザーを作成するには、CallAdvisor または StreamAdvisor (あるいはその両方) を実装します。実装する主要な方法は、非ストリーミングアドバイザーの場合は nextCall()、ストリーミングアドバイザーの場合は nextStream() です。

サンプル

ユースケースを観測および拡張するためのアドバイザーを実装する方法を説明するために、いくつかの実践的な例を紹介します。

ログ記録アドバイザー

チェーンの次のアドバイザーへの呼び出し前の ChatClientRequest と呼び出し後の ChatClientResponse をログに記録する、シンプルなログアドバイザーを実装できます。アドバイザーはリクエストとレスポンスを監視するだけで、変更は行わないことに注意してください。この実装は、非ストリーミングシナリオとストリーミングシナリオの両方をサポートします。

public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {

	private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);

	@Override
	public String getName() { (1)
		return this.getClass().getSimpleName();
	}

	@Override
	public int getOrder() { (2)
		return 0;
	}


	@Override
	public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
		logRequest(chatClientRequest);

		ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);

		logResponse(chatClientResponse);

		return chatClientResponse;
	}

	@Override
	public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest,
			StreamAdvisorChain streamAdvisorChain) {
		logRequest(chatClientRequest);

		Flux<ChatClientResponse> chatClientResponses = streamAdvisorChain.nextStream(chatClientRequest);

		return new ChatClientMessageAggregator().aggregateChatClientResponse(chatClientResponses, this::logResponse); (3)
	}

	private void logRequest(ChatClientRequest request) {
		logger.debug("request: {}", request);
	}

	private void logResponse(ChatClientResponse chatClientResponse) {
		logger.debug("response: {}", chatClientResponse);
	}

}
1 アドバイザーに一意の名前を提供します。
2 順序値を設定することで実行順序を制御できます。値が小さいほど先に実行されます。
3MessageAggregator は、Flux レスポンスを 1 つの ChatClientResponse に集約するユーティリティクラスです。これは、ストリーム内の個々の項目ではなくレスポンス全体を監視するログ記録やその他の処理に役立ちます。MessageAggregator は読み取り専用操作であるため、レスポンスを変更できないことに注意してください。

再読(Re2)アドバイザー

"再読により大規模言語モデルの推論機能が向上する (英語) " の記事では、大規模言語モデルの推論機能を向上させる Re-Reading (Re2) と呼ばれる手法を紹介しています。Re2 手法では、次のように入力プロンプトを拡張する必要があります。

{Input_Query}
Read the question again: {Input_Query}

Re2 テクニックをユーザーの入力クエリに適用するアドバイザーを実装するには、次のようにします。

public class ReReadingAdvisor implements BaseAdvisor {

	private static final String DEFAULT_RE2_ADVISE_TEMPLATE = """
			{re2_input_query}
			Read the question again: {re2_input_query}
			""";

	private final String re2AdviseTemplate;

	private int order = 0;

	public ReReadingAdvisor() {
		this(DEFAULT_RE2_ADVISE_TEMPLATE);
	}

	public ReReadingAdvisor(String re2AdviseTemplate) {
		this.re2AdviseTemplate = re2AdviseTemplate;
	}

	@Override
	public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) { (1)
		String augmentedUserText = PromptTemplate.builder()
			.template(this.re2AdviseTemplate)
			.variables(Map.of("re2_input_query", chatClientRequest.prompt().getUserMessage().getText()))
			.build()
			.render();

		return chatClientRequest.mutate()
			.prompt(chatClientRequest.prompt().augmentUserMessage(augmentedUserText))
			.build();
	}

	@Override
	public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
		return chatClientResponse;
	}

	@Override
	public int getOrder() { (2)
		return this.order;
	}

	public ReReadingAdvisor withOrder(int order) {
		this.order = order;
		return this;
	}

}
1before メソッドは、再読み取りテクニックを適用してユーザーの入力クエリを拡張します。
2 順序値を設定することで実行順序を制御できます。値が小さいほど先に実行されます。

Spring AI 組み込みアドバイザー

Spring AI フレームワークには、AI のインタラクションを強化するための組み込みアドバイザーがいくつか用意されています。利用可能なアドバイザーの概要は次のとおりです。

チャットメモリアドバイザー

これらのアドバイザーは、チャットメモリストア内の会話履歴を管理します。

  • MessageChatMemoryAdvisor

    メモリを取得し、それをメッセージのコレクションとしてプロンプトに追加します。このアプローチでは、会話履歴の構造が維持されます。すべての AI モデルがこのアプローチをサポートしているわけではないことに注意してください。

  • PromptChatMemoryAdvisor

    メモリを取得し、プロンプトのシステムテキストに組み込みます。

  • VectorStoreChatMemoryAdvisor

    VectorStore からメモリを取得し、プロンプトのシステムテキストに追加します。このアドバイザーは、大規模なデータセットから関連情報を効率的に検索および取得できます。

質問回答アドバイザー
  • QuestionAnswerAdvisor

    このアドバイザーは、ベクトルストアを使用して質問応答機能を提供し、Naive RAG (Retrieval-Augmented Generation) パターンを実装します。

  • RetrievalAugmentationAdvisor

    Advisor that implements common Retrieval Augmented Generation (RAG) flows using the building blocks defined in the `org.springframework.ai.rag` package and following the Modular RAG Architecture.
推論アドバイザー
  • ReReadingAdvisor

    LLM 推論のための再読戦略(RE2)を実装し、入力フェーズでの理解を強化します。論文「再読は LLM における推論力を向上させる」(arxiv.org/pdf/2309.06275 (英語) )に基づいています。

コンテンツセーフティアドバイザー
  • SafeGuardAdvisor

    モデルが有害または不適切なコンテンツを生成しないように設計されたシンプルなアドバイザー。

ストリーミング vs 非ストリーミング

Advisors Streaming vs Non-Streaming Flow
  • 非ストリーミングアドバイザーは、完全なリクエストとレスポンスを扱います。

  • ストリーミングアドバイザーは、リアクティブプログラミングの概念 (レスポンスの場合は Flux など) を使用して、リクエストとレスポンスを連続ストリームとして処理します。

@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain chain) {

    return  Mono.just(chatClientRequest)
            .publishOn(Schedulers.boundedElastic())
            .map(request -> {
                // This can be executed by blocking and non-blocking Threads.
                // Advisor before next section
            })
            .flatMapMany(request -> chain.nextStream(request))
            .map(response -> {
                // Advisor after next section
            });
}

ベストプラクティス

  1. モジュール性を高めるために、アドバイザーが特定のタスクに集中できるようにします。

  2. 必要に応じて、adviseContext を使用してアドバイザー間で状態を共有します。

  3. 柔軟性を最大限に高めるには、アドバイザーのストリーミングバージョンと非ストリーミングバージョンの両方を実装します。

  4. 適切なデータフローを確保するために、チェーン内のアドバイザーの順序を慎重に検討してください。

破壊的な API 変更

アドバイザーインターフェース

  • 1.0 M2 には、RequestAdvisor と ResponseAdvisor のインターフェースが別々にありました。

    • RequestAdvisor は、ChatModel.call メソッドと ChatModel.stream メソッドの前に呼び出されました。

    • これらのメソッドの後に ResponseAdvisor が呼び出されました。

  • 1.0 M3 では、これらのインターフェースは次のように置き換えられました。

    • CallAroundAdvisor

    • StreamAroundAdvisor

  • 以前は ResponseAdvisor の一部であった StreamResponseMode は削除されました。

  • 1.0.0 では、次のインターフェースが置き換えられました。

    • CallAroundAdvisor → CallAdvisorStreamAroundAdvisor → StreamAdvisorCallAroundAdvisorChain → CallAdvisorChainStreamAroundAdvisorChain → StreamAdvisorChain

    • AdvisedRequest → ChatClientRequest および AdivsedResponse → ChatClientResponse

コンテキストマップの処理

  • 1.0 M2 の場合:

    • コンテキストマップは別のメソッド引数でした。

    • マップは変更可能であり、チェーンに沿って渡されました。

  • 1.0 M3 の場合:

    • コンテキストマップは、AdvisedRequest および AdvisedResponse レコードの一部になりました。

    • マップは不変です。

    • コンテキストを更新するには、更新されたコンテンツを含む新しい変更不可能なマップを作成する updateContext メソッドを使用します。