アドバイザー API
Spring AI アドバイザー API は、Spring アプリケーションで AI 主導のインタラクションをインターセプト、変更、強化するための柔軟で強力な方法を提供します。アドバイザー API を活用することで、開発者はより洗練され、再利用可能で、保守しやすい AI コンポーネントを作成できます。
主な利点としては、繰り返し発生する生成 AI パターンのカプセル化、大規模言語モデル (LLM) との間で送受信されるデータの変換、さまざまなモデルやユースケース間での移植性の提供などが挙げられます。
次の例に示すように、ChatClient API を使用して既存のアドバイザーを構成できます。
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory), // chat-memory advisor
new QuestionAnswerAdvisor(vectorStore) // RAG advisor
)
.build();
String response = this.chatClient.prompt()
// Set advisor parameters at runtime
.advisors(advisor -> advisor.param("chat_memory_conversation_id", "678")
.param("chat_memory_response_size", 100))
.user(userText)
.call()
.content();
ビルダーの defaultAdvisors()
メソッドを使用して、ビルド時にアドバイザーを登録することをお勧めします。
アドバイザーは Observability スタックにも参加するため、その実行に関連するメトリクスとトレースを表示できます。
コアコンポーネント
API は、非ストリーミングシナリオ用の CallAroundAdvisor
と CallAroundAdvisorChain
、およびストリーミングシナリオ用の StreamAroundAdvisor
と StreamAroundAdvisorChain
で構成されています。また、封印されていないプロンプトリクエストを表す AdvisedRequest
、チャット補完レスポンスを表す AdvisedResponse
も含まれます。どちらもアドバイザーチェーン全体で状態を共有するために advise-context
を保持しています。
nextAroundCall()
と nextAroundStream()
は主要なアドバイザーメソッドであり、通常は、封印されていないプロンプトデータの調査、プロンプトデータのカスタマイズと拡張、アドバイザーチェーン内の次のエンティティの呼び出し、オプションでのリクエストのブロック、チャット補完レスポンスの調査、処理エラーを示す例外のスローなどのアクションを実行します。
さらに、getOrder()
メソッドは チェーン内のアドバイザーの順序を決定し、getName()
は一意のアドバイザー名を提供します。
Spring AI フレームワークによって作成されたアドバイザーチェーンを使用すると、getOrder()
値順に複数のアドバイザーを順番に呼び出すことができます。値の低いアドバイザーが最初に実行されます。自動的に追加された最後のアドバイザーは、リクエストを LLM に送信します。
次のフロー図は、アドバイザーチェーンとチャットモデル間のやり取りを示しています。
Spring AI フレームワークは、ユーザーの
Prompt
と空のAdvisorContext
オブジェクトからAdvisedRequest
を作成します。チェーンの各アドバイザーはリクエストを処理し、場合によってはリクエストを変更します。または、次のエンティティを呼び出す呼び出しを行わないことでリクエストをブロックすることもできます。後者の場合、アドバイザーはレスポンスを入力する責任があります。
フレームワークによって提供される最終アドバイザーは、リクエストを
Chat Model
に送信します。チャットモデルのレスポンスはアドバイザーチェーンを介して返され、
AdvisedResponse
に変換されます。その後、共有AdvisorContext
インスタンスが含まれます。各アドバイザーはレスポンスを処理または変更できます。
最終的な
AdvisedResponse
は、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();
}
入力側と出力側の両方で チェーンの先頭にする必要のあるユースケースの場合:
|
API の概要
主要なアドバイザーインターフェースはパッケージ org.springframework.ai.chat.client.advisor.api
にあります。独自のアドバイザーを作成するときに使用する主要なインターフェースは次のとおりです。
public interface Advisor extends Ordered {
String getName();
}
同期アドバイザーとリアクティブアドバイザーの 2 つのサブインターフェースは
public interface CallAroundAdvisor extends Advisor {
/**
* Around advice that wraps the ChatModel#call(Prompt) method.
* @param advisedRequest the advised request
* @param chain the advisor chain
* @return the response
*/
AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain);
}
および
public interface StreamAroundAdvisor extends Advisor {
/**
* Around advice that wraps the invocation of the advised request.
* @param advisedRequest the advised request
* @param chain the chain of advisors to execute
* @return the result of the advised request
*/
Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain);
}
アドバイスの チェーンを継続するには、アドバイスの実装で CallAroundAdvisorChain
と StreamAroundAdvisorChain
を使用します。
インターフェースは
public interface CallAroundAdvisorChain {
AdvisedResponse nextAroundCall(AdvisedRequest advisedRequest);
}
および
public interface StreamAroundAdvisorChain {
Flux<AdvisedResponse> nextAroundStream(AdvisedRequest advisedRequest);
}
アドバイザーの導入
アドバイザーを作成するには、CallAroundAdvisor
または StreamAroundAdvisor
(あるいはその両方) を実装します。実装する主要な方法は、非ストリーミングアドバイザーの場合は nextAroundCall()
、ストリーミングアドバイザーの場合は nextAroundStream()
です。
サンプル
ユースケースを観察および拡張するためのアドバイザーを実装する方法を説明するために、いくつかの実践的な例を紹介します。
ログ記録アドバイザー
チェーンの次のアドバイザーへの呼び出し前の AdvisedRequest
と呼び出し後の AdvisedResponse
をログに記録する、シンプルなログアドバイザーを実装できます。アドバイザーはリクエストとレスポンスを監視するだけで、変更は行わないことに注意してください。この実装は、非ストリーミングシナリオとストリーミングシナリオの両方をサポートします。
public class SimpleLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
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 AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
logger.debug("BEFORE: {}", advisedRequest);
AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);
logger.debug("AFTER: {}", advisedResponse);
return advisedResponse;
}
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
logger.debug("BEFORE: {}", advisedRequest);
Flux<AdvisedResponse> advisedResponses = chain.nextAroundStream(advisedRequest);
return new MessageAggregator().aggregateAdvisedResponse(advisedResponses,
advisedResponse -> logger.debug("AFTER: {}", advisedResponse)); (3)
}
}
1 | アドバイザーに一意の名前を提供します。 |
2 | 順序値を設定することで実行順序を制御できます。値が小さいほど先に実行されます。 |
3 | MessageAggregator は、Flux レスポンスを 1 つの AdvisedResponse に集約するユーティリティクラスです。これは、ストリーム内の個々の項目ではなくレスポンス全体を監視するログ記録やその他の処理に役立ちます。MessageAggregator は読み取り専用操作であるため、レスポンスを変更できないことに注意してください。 |
再読(Re2)アドバイザー
"再読により大規模言語モデルの推論機能が向上する (英語) " の記事では、大規模言語モデルの推論機能を向上させる Re-Reading (Re2) と呼ばれる手法を紹介しています。Re2 手法では、次のように入力プロンプトを拡張する必要があります。
{Input_Query} Read the question again: {Input_Query}
Re2 テクニックをユーザーの入力クエリに適用するアドバイザーを実装するには、次のようにします。
public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
private AdvisedRequest before(AdvisedRequest advisedRequest) { (1)
Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams());
advisedUserParams.put("re2_input_query", advisedRequest.userText());
return AdvisedRequest.from(advisedRequest)
.userText("""
{re2_input_query}
Read the question again: {re2_input_query}
""")
.userParams(advisedUserParams)
.build();
}
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { (2)
return chain.nextAroundCall(this.before(advisedRequest));
}
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { (3)
return chain.nextAroundStream(this.before(advisedRequest));
}
@Override
public int getOrder() { (4)
return 0;
}
@Override
public String getName() { (5)
return this.getClass().getSimpleName();
}
}
1 | before メソッドは、再読み取りテクニックを適用してユーザーの入力クエリを拡張します。 |
2 | aroundCall メソッドは、非ストリーミングリクエストをインターセプトし、再読み取りテクニックを適用します。 |
3 | aroundStream メソッドはストリーミングリクエストをインターセプトし、再読み取りテクニックを適用します。 |
4 | 順序値を設定することで実行順序を制御できます。値が小さいほど先に実行されます。 |
5 | アドバイザーに一意の名前を提供します。 |
Spring AI 組み込みアドバイザー
Spring AI フレームワークには、AI のインタラクションを強化するための組み込みアドバイザーがいくつか用意されています。利用可能なアドバイザーの概要は次のとおりです。
チャットメモリアドバイザー
これらのアドバイザーは、チャットメモリストア内の会話履歴を管理します。
MessageChatMemoryAdvisor
メモリを取得し、それをメッセージのコレクションとしてプロンプトに追加します。このアプローチでは、会話履歴の構造が維持されます。すべての AI モデルがこのアプローチをサポートしているわけではないことに注意してください。
PromptChatMemoryAdvisor
メモリを取得し、プロンプトのシステムテキストに組み込みます。
VectorStoreChatMemoryAdvisor
VectorStore からメモリを取得し、プロンプトのシステムテキストに追加します。このアドバイザーは、大規模なデータセットから関連情報を効率的に検索および取得できます。
ストリーミング vs 非ストリーミング
非ストリーミングアドバイザーは、完全なリクエストとレスポンスを扱います。
ストリーミングアドバイザーは、リアクティブプログラミングの概念 (レスポンスの場合は Flux など) を使用して、リクエストとレスポンスを連続ストリームとして処理します。
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
return Mono.just(advisedRequest)
.publishOn(Schedulers.boundedElastic())
.map(request -> {
// This can be executed by blocking and non-blocking Threads.
// Advisor before next section
})
.flatMapMany(request -> chain.nextAroundStream(request))
.map(response -> {
// Advisor after next section
});
}
破壊的な API 変更
Spring AI アドバイザーチェーンは、バージョン 1.0 M2 から 1.0 M3 にかけて大幅な変更が行われました。主な変更点は次のとおりです。
アドバイザーインターフェース
1.0 M2 には、
RequestAdvisor
とResponseAdvisor
のインターフェースが別々にありました。RequestAdvisor
は、ChatModel.call
メソッドとChatModel.stream
メソッドの前に呼び出されました。これらのメソッドの後に
ResponseAdvisor
が呼び出されました。
1.0 M3 では、これらのインターフェースは次のように置き換えられました。
CallAroundAdvisor
StreamAroundAdvisor
以前は
ResponseAdvisor
の一部であったStreamResponseMode
は削除されました。
コンテキストマップの処理
1.0 M2 の場合:
コンテキストマップは別のメソッド引数でした。
マップは変更可能であり、チェーンに沿って渡されました。
1.0 M3 の場合:
コンテキストマップは、
AdvisedRequest
およびAdvisedResponse
レコードの一部になりました。マップは不変です。
コンテキストを更新するには、更新されたコンテンツを含む新しい変更不可能なマップを作成する
updateContext
メソッドを使用します。
1.0 M3 のコンテキストを更新する例:
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
this.advisedRequest = advisedRequest.updateContext(context -> {
context.put("aroundCallBefore" + getName(), "AROUND_CALL_BEFORE " + getName()); // Add multiple key-value pairs
context.put("lastBefore", getName()); // Add a single key-value pair
return context;
});
// Method implementation continues...
}