はじめに
前回は、Jevを使った「Filtering RAG」について学び、不要な文書を除外することで、LLMに渡す情報を減らせることを確認しました。今回は、質問に応じて検索方法を切り替える Adaptive RAG(アダプティブRAG) について学んでいこうと思います。
Adaptive RAGでは、質問の内容や複雑さに応じて、検索するかどうかや検索方法を選択します。今回は、この判断に Jev を使い、Naive RAGと比較して、回答品質や処理時間がどのように変化するのかを実験していきます。
これまでの記事・実装
Adaptive RAGとは?
Adaptive RAGとは、質問の内容や複雑さに応じて、検索の有無や検索戦略を動的に選択するRAGの構成です。
Naive RAGでは、質問の内容にかかわらず同じ検索処理を実行します。一方、Adaptive RAGでは、質問に適した処理を選択します。
| 比較 | Naive RAG | Adaptive RAG |
|---|---|---|
| 検索の実行 | 毎回実行 | 必要に応じて実行 |
| 検索方法 | 固定 | 質問に応じて選択 |
| 複雑な質問 | 同じ検索処理 | 複数回の検索も可能 |
| 特徴 | シンプル | 処理効率と回答品質の両立を目指す |
Adaptive RAGでは、主に以下の3つの戦略を使い分けます。
| 戦略 | 処理 | 用途 |
|---|---|---|
| No Retrieval | 検索せずに回答する | 一般的な質問 |
| Single-step | 1回検索して回答する | 単純な情報検索 |
| Multi-step | 複数回検索して回答する | 複数の情報を段階的に調べる質問 |
2024年に発表されたAdaptive-RAGの論文でも、質問の複雑さに応じて、この3つの戦略を選択する方法が提案されています。
処理の流れ
Naive RAGとの大きな違いは、Retrievalの前に、検索戦略を選択する処理が追加されることです。
処理の内容は以下のとおりです。
| 処理 | 役割 |
|---|---|
| Adaptive | Jevで質問に適した検索戦略を選択 |
| Retrieval | 選択した戦略に従って検索 |
| Generation | 取得した情報をもとにLLMが回答を生成 |
今回は、Jevのchoiceを使用して、「検索なし」「単発検索」「複数回検索」の3つから適切な戦略を選択します。
なお、元論文では学習済みの分類器を使って戦略を選択しています。今回は、その判断部分をJevで実装する構成です。
成果物
今回も、架空のSaaS「TaskFlow」の問い合わせ回答システムを使用します。Naive RAGとAdaptive RAGを比較し、質問に応じて適切な検索戦略を選択できるかを検証します。
例えば、次の3つの質問を使用します。
| 質問 | 期待する戦略 |
|---|---|
| 「日割り」とはどういう意味ですか? | No Retrieval |
| TaskFlowでは退会後、データはいつ削除されますか? | Single-step |
| プラン変更後に退会した場合、請求とデータ削除はどうなりますか? | Multi-stepの候補 |
1つ目は一般的な用語の説明なので、検索せずに回答できます。2つ目はTaskFlow固有の情報なので、マニュアルの検索が必要です。3つ目はプラン変更・請求・退会に関する情報を確認する必要があり、複数回検索する候補になります。
ただし、複数の文書が必要だからといって、必ず複数回検索が必要とは限りません。 1回の検索で回答できる場合もあるため、実際のNaive RAGの結果を確認して検証します。
検索の評価方法
今回は、同じ質問とマニュアルを使用し、Naive RAGとAdaptive RAGを比較します。違いは、検索戦略を選択する処理の有無です。
| 比較項目 | Naive RAG | Adaptive RAG |
|---|---|---|
| 検索戦略 | 単発検索で固定 | Jevが選択 |
| 検索回数 | 原則1回 | 0回・1回・複数回 |
| Embeddingモデル | 同じ | 同じ |
| 回答生成モデル | GPT-4.1-mini | GPT-4.1-mini |
| Jevによる判断 | なし | あり |
主に以下の項目を確認します。
- Jevが質問に適した検索戦略を選択できたか
- Naive RAGと比較して回答品質が変わったか
- 検索回数・処理時間・APIコストがどう変化したか
Jevの判断にも処理時間やAPIコストが発生するため、検索を省略できても必ずしも全体の処理が速くなるとは限りません。
実装内容
今回は、Jevのchoiceを使って2つの判断を実装します。
| 判断 | タイミング | Jevが選択する内容 |
|---|---|---|
| 検索戦略の選択 | 検索前 | 検索なし・単発検索・複数回検索 |
| 追加検索の判定 | 複数回検索の途中 | 今の文書で十分か・追加検索が必要か |
どちらもPOST https://api.typesafe.ai/v1/systemoneを使用し、リクエストのモデルにはjev-latestを指定します。
① Jevで検索戦略を選択する
まず、ユーザーの質問をJevに渡し、3つの戦略から適切なものを選択させます。
stateには質問を、questions.strategyには判断内容と3つの選択肢を指定します。
以下は「日割りとはどういう意味ですか?」という質問を送ったリクエストです。
{
"state": "質問: 「日割り」とはどういう意味ですか?",
"questions": {
"strategy": {
"type": "choice",
"instructions": "TaskFlowの問い合わせに答えるため、マニュアル検索をどう使うか。一般的な言葉の意味だけを聞いていて、料金・退会・期限などの製品の規定を聞いていないときは no_retrieval。製品の規定を1つ調べれば足りるときは single_step。別々の規定を突き合わせるときは multi_step。",
"criteria": {
"no_retrieval": "一般的な言葉の意味や常識で答えられ、TaskFlowの料金・退会・期限などの規定は不要",
"single_step": "TaskFlowの規定を1つ調べれば足りる",
"multi_step": "請求と削除のように、別々の規定を突き合わせないと答えられない"
}
}
},
"model": "jev-latest"
}
Jevには検索処理を実行させるのではなく、どの検索戦略を実行すべきかを判断させるのがポイントです。
② Jevの検索戦略の判定結果
返ってきたレスポンスです。
{
"model": "jev-1.14.0",
"answers": {
"strategy": {
"type": "choice",
"choice": "no_retrieval",
"confidence": 0.91,
"probabilities": {
"no_retrieval": 0.94,
"single_step": 0.06,
"multi_step": 0
}
}
},
"usage": {
"input_tokens": 537,
"output_tokens": 48
}
}
answers.strategy.choiceがno_retrievalなので、アプリは検索を省略して回答生成へ進みます。
同じ判断基準で3つの質問を評価した結果は、以下のとおりです。
| 質問 | Jevの選択 | 選択肢の確率 |
|---|---|---|
| 「日割り」とはどういう意味ですか? | no_retrieval |
0.94 |
| TaskFlowでは退会後、データはいつ削除されますか? | single_step |
1.00 |
| プラン変更後に退会した場合、請求とデータ削除はどうなりますか? | multi_step |
0.94 |
※ 上記は個別のAPI呼び出し結果です。後述の比較実行では、それぞれ0.96、1.00、0.93でした。
このように、Jevのchoiceを使うことで、質問に応じて検索方法を切り替えるための判断結果を取得できます。
③ Jevで追加検索の必要性を判断する
multi_stepが選択された場合は、最初の検索結果を取得した後、追加検索が必要かをJevに判断させます。
今度はstateに質問と取得済みの文書を渡し、questions.coverageで以下の2択を判断します。
| 選択肢 | 意味 |
|---|---|
enough |
今の文書だけで回答できる |
another |
別の情報を追加検索する必要がある |
3問目の「プラン変更後に退会した場合、請求とデータ削除はどうなりますか?」では、最初に「退会後の請求」「退会後のデータ削除」「退会できる人」の3件が取得されました。
この3件をJevに渡し、追加検索が必要かを判断させます。
{
"state": "質問: プラン変更後に退会した場合、請求とデータ削除はどうなりますか?\n\n文書:\n# 退会\n## 退会後の請求\n\n退会手続きが完了した月の料金は満額請求されます。月の途中で解約しても日割りにはなりません。退会の翌月以降、月額料金は請求されません。年払いの未経過分は返金しません。\n\n# 退会\n## 退会後のデータ削除\n\n退会が完了してから 30 日後に、ワークスペースのタスク、添付ファイル、メンバー一覧は完全に削除されます。30 日以内であれば、同じ管理者アカウントからワークスペースを再開できます。30 日を過ぎると再開はできません。\n\n# 退会\n## 退会できる人\n\n退会手続きはワークスペースの管理者だけが実行できます。画面は「設定」「請求」「退会」です。メンバー権限のユーザーには退会ボタンは表示されません。",
"questions": {
"coverage": {
"type": "choice",
"instructions": "この文書だけで質問に答えられるか。",
"criteria": {
"enough": "質問が求めている規定が、渡された文書に含まれている",
"another": "別の規定を追加で検索しないと答えられない"
}
}
},
"model": "jev-latest"
}
④ Jevの追加検索の判定結果
レスポンスではanotherが選択されました。
{
"model": "jev-1.14.0",
"answers": {
"coverage": {
"type": "choice",
"choice": "another",
"confidence": 0.2,
"probabilities": {
"enough": 0.4,
"another": 0.6
}
}
},
"usage": {
"input_tokens": 708,
"output_tokens": 32
}
}
anotherの確率が0.60、enoughが0.40で、追加検索が必要と判断されました。ただし、2つの確率は近く、検索戦略の選択と比べると判断は明確ではありません。
なお、Jevは追加検索の必要性だけを判断し、検索文そのものは生成しません。 今回の実装では、anotherが返った場合に、取得済みの見出しをLLMに渡して追加検索用の質問を1つ作成します。
実行結果
比較はnpm run adaptで実行します。Naive RAGは毎回、類似度の上位3件を1回検索して回答します。
① 「日割り」とはどういう意味ですか?
Naive RAGの上位3件は、類似度0.6385の「日割り請求のルール」、0.5291の「プラン変更時の日割り」、0.3776の「請求日」です。検索は1378 ms、入力トークンは271、回答生成は1093 msです。回答は「月の途中で契約内容が変わった場合に、月額を1日あたりの金額にして残日数をかける」という、TaskFlowの規定でした。
Adaptive RAGはno_retrieval(検索なし)を選びました。信頼度は0.94、確率はno_retrieval 0.96、single_step 0.04、multi_step 0.00です。検索回数は0、Jevは297 ms、入力トークンは92、回答生成は1469 msです。回答は「期間の料金を日数に応じて按分すること」という一般的な説明でした。
| 検索回数 | 入力トークン | 回答 | |
|---|---|---|---|
| Naive RAG | 1 | 271 | 月額を1日あたりにして残日数をかける |
| Adaptive RAG | 0 | 92 | 日数に応じて按分する |
② TaskFlowでは退会後、データはいつ削除されますか?
Naive RAGの上位3件は、類似度0.5848の「退会後のデータ削除」、0.4615の「支払い失敗」、0.4485の「通信と保管」です。検索は762 ms、入力トークンは307、回答生成は1166 msです。回答は「退会完了から30日後に完全に削除される」でした。
Adaptive RAGはsingle_step(1回検索)を選びました。信頼度は1.00、確率はsingle_stepが1.00です。Jevは186 msです。検索した文書も回答もNaive RAGと同じで、入力トークンは307、検索は826 ms、回答生成は1004 msです。
| 戦略 | 検索回数 | 入力トークン | 回答 | |
|---|---|---|---|---|
| Naive RAG | 1回検索で固定 | 1 | 307 | 30日後に削除される |
| Adaptive RAG | single_step(信頼度1.00) | 1 | 307 | 30日後に削除される |
③ プラン変更後に退会した場合、請求とデータ削除はどうなりますか?
Naive RAGの上位3件は、類似度0.5803の「退会後の請求」、0.5757の「退会後のデータ削除」、0.4961の「退会できる人」です。検索は650 ms、入力トークンは333、回答生成は1740 msです。回答は「退会した月は満額で、日割りにはならない。データは30日後に削除される」でした。
Adaptive RAGはmulti_step(複数回検索)を選びました。信頼度は0.89、確率はmulti_step 0.93、single_step 0.07です。Jevは518 msです。1回目の検索はNaive RAGと同じ3件です。追加が必要と判断し、2回目は「プラン変更後の退会請求とデータ削除規定」で検索しています。2回目の3件目が類似度0.5004の「プラン変更時の日割り」で、文書数は4件になりました。追加検索文の作成は901 ms、検索は合計1573 ms、入力トークンは397、回答生成は1886 msです。回答は、退会した月は満額で、データは30日後に削除される、という内容のままです。
| 検索回数 | 文書数 | 入力トークン | 回答 | |
|---|---|---|---|---|
| Naive RAG | 1 | 3 | 333 | 退会月は満額。データは30日後に削除 |
| Adaptive RAG | 2 | 4 | 397 | 退会月は満額。データは30日後に削除 |
1回目の検索で「退会後の請求」と「退会後のデータ削除」は取れています。2回目で増えたのは「プラン変更時の日割り」です。
まとめ
今回は、Jevのchoiceで検索戦略を選び、Naive RAGと比較しました。
1問目は検索なしでした。回答はTaskFlowの計算式から、日数で按分するという一般的な説明に変わり、入力トークンは271から92に減っています。検索の1378 msはなくなり、Jevは297 msです。回答生成は1093 msから1469 msになりました。
2問目は1回検索でした。文書も回答もNaive RAGと同じで、退会完了から30日後に削除されます。増えたのはJevの186 msです。
3問目は複数回検索でした。2回目の検索で「プラン変更時の日割り」が加わり、文書数は3件から4件、入力トークンは333から397に増えています。回答は、退会した月は満額で、データは30日後に削除される、という内容のままです。



