跳到主要内容

Model Routing(モデルルーティング)

备注

モデルルーティングは実験的な機能であり、今後のリリースで変更される可能性があります。

リクエストによって必要なモデルは異なります。挨拶や短い事実確認の質問であれば、小さく高速で 安価なモデルで回答できますが、コードの作成や詳細な分析には、より大きなモデルが必要です。モデルルーティングを使うと、各リクエストが アプリケーションの他の部分を変更することなく、それに適したモデルに送信されます。

RoutingChatModel は ChatModel の一種で、ChatModelRouter の判断に従って、各リクエストを複数のチャットモデル(ルート)のいずれかに送信します。 ChatModel であるため、チャットモデルが使われるあらゆる場所(AI Service、エージェント、 RAG など)で使用できます。

ChatModel chatModel = RoutingChatModel.builder()
.route("simple", "Greetings, small talk and short factual questions", smallModel)
.route("complex", "Writing or debugging code, multi-step reasoning, detailed analysis", largeModel)
.router(new DecisionModelChatModelRouter(decisionModel))
.defaultRoute("complex")
.build();

Assistant assistant = AiServices.create(Assistant.class, chatModel);

smallModel と largeModel は任意の ChatModel、decisionModel は任意の DecisionModel(たとえば TypeSafeDecisionModel。意思決定モデルを参照)です。モデルを必要としないルーターについては、 後述します。

ストリーミングの場合は、StreamingChatModel と一緒に RoutingStreamingChatModel を使用します。動作は同じです。

ルーター​

ChatModelRouter は ChatModelRoutingResult を返します。いずれかのルートを選ぶ場合は ChatModelRoutingResult.route(name)、 デフォルトのルートを選ぶ場合は ChatModelRoutingResult.defaultRoute() です。 デフォルトのルートは必須です。これにより、たとえばモデルを呼び出すルーターが判断に迷ったり失敗したりしたときでも、 リクエストの送信先となるモデルが常に存在します。

意思決定モデルルーター​

DecisionModelChatModelRouter は、意思決定モデルを使って、説明がリクエストに最もよく合うルートを選択します。 システムメッセージと、会話の最後の 10 件のメッセージ( ツール呼び出しとツール結果を含む。maxMessages)、さらに最後のユーザーメッセージがそれより古い場合はそれも送信します。これにより、「yes, go ahead」のような短い フォローアップも理解されます。2,000 文字を超えるメッセージは、途中が短縮されます (maxMessageLength)。システムメッセージは常に全文が送信され、messageFilter(...) で、たとえばサイズの大きいツール結果などのメッセージを除外できます。画像など、テキスト以外の コンテンツはマーカー(たとえば [attached image])で表現されるため、説明に画像が含まれているルートを、 そのリクエストに対して選択できます。 意思決定モデルは通常、チャットモデルよりもはるかに高速で安価であるため、ルーティングによる レイテンシとコストの増加は、チャットモデルの呼び出しに比べてわずかです。各ルートの説明をしっかり書いてください。説明のないルートは、その名前が説明として使われます。

リクエストにユーザーメッセージがない場合や、選ばれたルートの確率が minProbability を下回る場合は、デフォルトのルートが選択されます。 これは、意思決定モデルが判断に自信を持てないリクエストを、 より大きなモデルに送るのに便利です。minProbability を使うには、確率を報告する意思決定モデルが必要です。そうでない場合、呼び出しは 失敗します。意思決定モデルが失敗した場合は、デフォルトのルートが使われ、警告がログに記録されます。代わりにリクエストを失敗させるには、 fallbackStrategy(FAIL) を設定します。

ChatModelRouter router = DecisionModelChatModelRouter.builder()
.decisionModel(decisionModel)
.minProbability(0.7)
.build();

互いに無関係な複数のトピックに関するリクエストを扱うルートには、複数の説明を指定できます。

ChatModel chatModel = RoutingChatModel.builder()
.route("simple", "Greetings, small talk and short factual questions", smallModel)
.route("complex", List.of("Writing or debugging code", "Legal contract analysis", "Tax planning"), largeModel)
.router(new DecisionModelChatModelRouter(decisionModel))
.defaultRoute("simple")
.build();

この場合、意思決定モデルは各説明を別々の選択肢として扱い、そのルートの確率は 各説明の確率の合計になります。すべてのトピックを列挙した 1 つの説明と比べると、 特に小さな意思決定モデルでは、判断の確信度が高まることがあります。意思決定モデルが判断に迷う場合は、 確率をすべての選択肢に分散させるため、説明が多いルートほど多くの確率を得ます。そのルートに不明確なケースを任せたい場合を除き、 各ルートの説明の数は均等に保ってください。

カスタムルーター​

ルーターは、単純なルールにすることもできます。

ChatModel chatModel = RoutingChatModel.builder()
.route("simple", smallModel)
.route("complex", largeModel)
.router(request -> ChatModelRoutingResult.route(
request.chatRequest().messages().size() > 20 ? "complex" : "simple"))
.defaultRoute("simple")
.build();

ルーターは、ChatRequest、呼び出しのオプション、およびそれを処理できるルート(ChatModelRoute: 名前と説明)を受け取ります。 ルーターは、validate(...) を実装することで、ルーティングチャットモデルの作成時にルートを検証することもできます。

非同期呼び出し​

chat(...) と StreamingChatResponseHandler を使ったストリーミングは、呼び出しスレッド上で route(...) を呼び出し、 ルートが選択されるまでの間、そのスレッドをブロックします。DecisionModelChatModelRouter の場合は、意思決定モデルが回答するまでです。これらは イベントループのスレッド(たとえば Vert.x や Quarkus のリアクティブなエンドポイント)から呼び出さないでください。 ノンブロッキングのメソッド、つまり chatAsync(...) と Publisher へのストリーミング(StreamingChatModel.chat(ChatRequest))は、 代わりに routeAsync(...) を呼び出すため、ルーティングがブロックすることはありません。routeAsync(...) はデフォルトでは実装されていないため、 ラムダとして書かれたルーターでは、これらの呼び出しが AsyncNotSupportedException で失敗します。 そのようなルーターをノンブロッキングの呼び出しで使うには、routeAsync(...) を実装してください。

ChatModelRouter router = new ChatModelRouter() {

@Override
public ChatModelRoutingResult route(ChatModelRoutingRequest request) {
return ChatModelRoutingResult.route(
request.chatRequest().messages().size() > 20 ? "complex" : "simple");
}

@Override
public CompletableFuture<ChatModelRoutingResult> routeAsync(ChatModelRoutingRequest request) {
return CompletableFuture.completedFuture(route(request)); // route(...) does not block
}
};

ブロックするルーター(たとえば、データベースでユーザーを検索するもの)は、代わりに、任意の executor 上で CompletableFuture.supplyAsync(() -> route(request), executor) を使って route(...) を実行するか、より望ましくは、 ノンブロッキングのクライアントを使用できます。DecisionModelChatModelRouter は、DecisionModel.decideAsync(...) を使って routeAsync(...) を実装しています。

リクエストのルーティング方法​

  • 選択されたモデルは、直接呼び出された場合と同じようにリクエストを処理します。そのモデルのデフォルトパラメータとリスナーが適用されます。
  • リクエストは異なるプロバイダーのモデルに送られる可能性があるため、共通のリクエストパラメータ (ChatRequestParameters)のみを設定し、プロバイダー固有のものは設定しないでください。
  • 選択されたルートの名前は、呼び出しのリスナー属性に追加されます (RoutingChatModel.ROUTE_ATTRIBUTE の下)。これにより、選択されたモデルのリスナーがそれを報告でき、また、返される AiMessage の 属性にも保存されます。
  • ツール呼び出しループの各ラウンドは同じモデルにとどまります。ツールの結果で終わるリクエストは、 ルーターに問い合わせることなく、そのツールを要求した AiMessage に保存されたルートに送られます。これは、メッセージの属性が保持される限り (デフォルトのシリアライズでは保持されます)、永続的なチャットメモリでも機能します。
  • supportedCapabilities() は、少なくとも 1 つのルートがサポートする機能を返します。ある 機能(たとえば JSON スキーマのレスポンス形式)を必要とするリクエストは、それを宣言しているルートにのみルーティングされます。 ルーターにはそれらのルートだけが見え、デフォルトのルートがそれを宣言していない場合は、宣言している最初のルートが使われます。 どのルートもその機能を宣言していない場合は、すべてのルートが候補のままとなり、選択されたモデルが、 直接呼び出された場合と同様に、リクエストを自分で受け入れるか拒否します。
  • ルーターが未知のルートを返した場合、リクエストは IllegalStateException で失敗します。
  • 選択されたモデルは、ルーティングを完了したスレッド上で呼び出されます。呼び出しスレッド、またはノンブロッキングの 呼び出しでは routeAsync(...) を完了したスレッド(たとえば、意思決定モデルのレスポンスのスレッド)です。
  • ルーティングチャットモデルがネストされている場合、AiMessage に保存される名前は、最も外側のルーティングチャットモデルのものです。 ツール呼び出しループでは、内側のルーティングチャットモデルは自身のルートを見つけられないため、再びルーターに問い合わせ、 別のモデルが選択される可能性があります。内側のルートが外側のルートと同じ名前を持つ場合、内側のルーティングチャットモデルは その名前の自身のルートを使用します。ネストされたルーティングチャットモデルのルートには、それぞれ異なる名前を付けてください。

留意事項​

  • すべてのルートが会話を共有します。新しいユーザーメッセージの後で、別のルートが、 他のルートが生成したメッセージを受け取ることがあります。異なるプロバイダーのルートは、互いのメッセージを読み取れなければなりません。プロバイダー固有のコンテンツでは、これが失敗することがあります。 たとえば、署名付きで返された思考内容や、別のプロバイダーが受け付けない形式のツール呼び出し ID などです。 同じプロバイダーのモデル間でルーティングすれば、これを避けられます。
  • DecisionModelChatModelRouter が見るのはメッセージのテキストであり、画像やその他の添付ファイルの内容ではありません。 添付されているという事実だけを知っています。
  • RAG を使う AI Service では、DecisionModelChatModelRouter は、チャットモデルと同じように、取得したコンテンツが追加された ユーザーメッセージを見ます。取得したドキュメントも判断に影響し、長いコンテンツは 意思決定モデルの入力上限を超えることがあります。その場合は、デフォルトのルートが使われ、警告がログに記録されます。
  • プロンプトキャッシュはモデルごとです。ルートを切り替える会話では、 前のモデルのキャッシュの恩恵を受けられず、長い会話ではルーティングによる節約を上回ってしまうことがあります。
  • ルーティングチャットモデルには独自のデフォルトのリクエストパラメータがなく、provider() は OTHER を返します。チャットモデルからデフォルトのリクエストパラメータを読み取るコード (たとえば toolChoice を調整するため)には、各ルートのパラメータではなく、空のパラメータが見えます。選択されたモデルのデフォルトパラメータは、各リクエストに引き続き適用されます。
  • ルーティングチャットモデルとそのルートがすべて同じ型の Bean である場合は、AI Service がルーティングチャットモデルを使用するようにしてください。 たとえば Spring Boot では、 @AiService(wiringMode = EXPLICIT, chatModel = "routingChatModel") を使います。
  • リスナーは、ルーティングチャットモデルではなく、各ルートのモデルに設定します。選択されたモデルのリスナーが 各呼び出しを観測し、ルート名はその属性で参照できます(RoutingChatModel.ROUTE_ATTRIBUTE)。 ただし、オプションを受け取らない StreamingChatModel.chat(ChatRequest) が返す Publisher は例外で、この場合は ルート名はレスポンスの属性にのみ含まれます。
  • ルーティングは、リクエストが送信される前にモデルを選択します。失敗したリクエストを別のモデルで再試行することはありません。