はじめに
前回は、Jevを使った「DecisionRAG」について学び、文書の階層構造をたどりながら、必要な情報を段階的に取得する仕組みを実装しました。
今回は、複数の文書から情報を取得し、それらを組み合わせて回答する Multi-hop RAG(マルチホップRAG) について学んでいこうと思います。
Multi-hop RAGでは、検索で得られた情報をもとに、次に必要な情報を判断して検索します。今回は、この判断にJevを使い、複数の検索結果を組み合わせて正しく回答できるかをNaive RAGと比較していきます。
今回がRAG学習シリーズの最後となります。
これまでの記事・実装
- Step0:Naive RAG
- Step1:Jevを使ったReranking RAG
- Step2:Jevを使ったFiltering RAG
- Step3:Jevを使ったAdaptive RAG
- Step4:Corrective RAGの実装
- Step5:Jevを使ったSelf-RAG
- Step6:Jevを使ったDecisionRAG
- GitHub:jev-rag-learning
Multi-hop RAGとは?
Multi-hop RAGとは、複数の文書から情報を段階的に取得し、得られた情報を組み合わせて回答するRAGの構成です。
Naive RAGでは、質問をもとに1回検索し、取得した上位文書をLLMに渡して回答を生成します。一方、Multi-hop RAGでは、検索結果を確認し、必要な情報が不足していれば次の検索を行います。
| 比較 | Naive RAG | Multi-hop RAG |
|---|---|---|
| 文書の検索 | 基本1回 | 必要に応じて複数回 |
| 検索文 | 最初の質問 | 取得済みの情報と不足項目を反映 |
| 情報の収集 | 上位文書を取得 | 段階的に必要な情報を収集 |
| Jevの役割 | なし | 次に必要な情報を判断 |
| 特徴 | シンプルで高速 | 複数の情報を組み合わせられる |
Multi-hop RAGの特徴は、単に検索回数を増やすのではなく、前の検索結果を踏まえて次の検索を決めることです。
この考え方に関連する研究として、2023年に発表されたIRCoT(Interleaving Retrieval with Chain-of-Thought Reasoning)があります。検索と段階的な推論を交互に行い、複数の情報を必要とする質問に対応する手法です。
今回はこの考え方を参考に、Jevで回答に不足している情報を判断し、段階的に文書を収集する簡易版Multi-hop RAGを実装します。
※ 図はプラン・請求・データ削除の順に情報を取得するイメージです。実際の検証では、最初に請求の文書が取得され、データ削除、プランの順に検索が進みました。
Adaptive RAGとの違い
Step3のAdaptive RAGでも複数回検索を実装しましたが、着目する部分が異なります。
| 比較 | Adaptive RAG | Multi-hop RAG |
|---|---|---|
| 主な役割 | 検索戦略を選択する | 複数の情報を段階的に取得する |
| Jevの判断 | 検索なし・1回・複数回 | 次に何を調べるべきか |
| 重視すること | 質問に合った処理の選択 | 検索結果を使った次の情報収集 |
Adaptive RAGは検索方法の選択に、Multi-hop RAGは段階的な情報収集に重点を置いています。両者は組み合わせることもできます。
なお、今回の実装は不足している規定を順番に収集する構成です。前の検索で特定した具体的な値を次の検索条件として必須にする、より依存関係の強いMulti-hop検索とは区別します。
処理の流れ
今回は、検索した文書をJevで評価し、不足している情報があれば検索文を作り直します。
処理の内容は以下のとおりです。
| 処理 | 役割 |
|---|---|
| 初回検索 | 質問に関連する文書を取得 |
| Jevによる判断 | 回答に不足している情報を選択 |
| 追加検索 | 不足情報を検索して文書を蓄積 |
| Generation | 収集した文書をもとに回答を生成 |
今回の実装では、Jevは次に必要な情報を判断し、検索文の生成はLLMが担当します。
検索は初回を含め最大3回とし、必要な根拠が揃わなければ、確認できない情報を断定しないようにします。
成果物
今回も、架空のSaaS「TaskFlow」の問い合わせ回答システムを使用します。
Naive RAGとMulti-hop RAGを比較し、段階的な検索によって必要な文書を揃えられるかを検証します。
| 質問 | 検証する内容 |
|---|---|
| SSOが使えるプランを月の途中で退会した場合、当月の請求額とデータ削除までの日数は? | 複数の規定を取得して回答できるか |
| 退会後、データはいつ削除されますか? | 不要な追加検索を行わないか |
① 複数の文書が必要な場合
SSOが使えるプランを月の途中で退会した場合、当月の請求額とデータ削除までの日数は?
正解を得るには、次の情報が必要です。
| 必要な情報 | マニュアルの節 | 内容 |
|---|---|---|
| SSO対応プラン | Proプラン | SSOに対応 |
| 月額料金 | Proプラン | 12,000円(税別) |
| 退会月の請求 | 退会後の請求 | 満額請求・日割りなし |
| データ削除 | 退会後のデータ削除 | 退会完了から30日後 |
正解は、**「SSOが使えるのはProプランで、当月の請求額は12,000円(税別)。月途中の退会でも日割りにはならず、データは退会完了から30日後に削除される」**です。
Naive RAGでも1回の検索で必要な文書が揃えば正しく回答できます。今回は、実際の検索結果を比較して違いを確認します。
② 単純な質問の場合
退会後、データはいつ削除されますか?
正解は「退会完了から30日後」です。
この質問では、1回の検索で必要な文書を取得できるため、Multi-hop RAGが不要な追加検索を行わずに回答できるかを確認します。
検索の評価方法
同じ質問・マニュアル・回答生成モデルを使い、Naive RAGとMulti-hop RAGを比較します。
| 比較項目 | Naive RAG | Multi-hop RAG |
|---|---|---|
| 検索方法 | Embedding | Embedding+段階的な再検索 |
| 検索回数 | 1回 | 最大3回 |
| 1回の検索候補 | 上位3件 | 上位3件 |
| Jevによる判断 | なし | 不足情報を判断 |
| 回答モデル | GPT-4.1-mini | GPT-4.1-mini |
確認するのは、必要な文書の取得状況、Jevの判断、検索文の変化、最終回答、処理時間とトークン数です。
なお、Multi-hop RAGでは検索回数だけでなく、Jevによる判断や検索文生成のコストも発生します。回答生成LLMへの入力トークン数と、システム全体のAPIコストは区別して評価します。
実装内容
今回はJevのchoiceを使い、取得済みの文書から次に不足している情報を1つ選択します。
送り先はPOST https://api.typesafe.ai/v1/systemone、モデルはjev-latestです。stateには元の質問と、それまでに収集した文書を渡します。
選択肢は質問ごとに変更します。1問目では次の4つを使用します。
| 選択肢 | 意味 |
|---|---|
plan |
SSO対応プランや月額料金が未確認 |
billing |
退会月の請求ルールが未確認 |
deletion |
データ削除までの期間が未確認 |
done |
必要な根拠がすべて揃っている |
done以外が選ばれると、LLMが不足している項目について検索文を1つ生成し、再検索します。
各回の上位3件は重複を除いて蓄積します。検索は最大3回で、最後の判定でもdoneにならなければ「一部の情報をマニュアルから確認できません。」と回答します。
① Jevで最初に不足している情報を判断する
1問目の初回検索では、次の3件が取得されました。
- 退会後の請求
- 日割り請求のルール
- プラン変更時の日割り
これらをJevに渡して、次に必要な情報を判断させます。
{
"state": "質問: SSOが使えるプランを月の途中で退会した場合、当月の請求額とデータ削除までの日数は?\n\n取得済み文書:\n# 退会\n## 退会後\n### 退会後の請求\n\n退会手続きが完了した月の料金は満額請求されます。月の途中で解約しても日割りにはなりません。退会の翌月以降、月額料金は請求されません。年払いの未経過分は返金しません。\n\n# 請求\n## 日割り\n### 日割り請求のルール\n\n日割り請求は、月の途中で契約内容が変わった場合に使います。日割り額は、月額料金を30で割った1日あたりの金額に、残日数をかけて計算します。\n\n# 請求\n## 日割り\n### プラン変更時の日割り\n\n月の途中でプランを変更した場合、差額は日割りで請求します。Standardプランから上位プランへ変更したときも、変更日以降の料金は日割りになります。",
"questions": {
"next": {
"type": "choice",
"instructions": "質問への回答に必要な根拠を確認し、次に調べるべき情報を1つ選んでください。推測で情報が揃ったと判断せず、すべての項目を文書で確認できた場合だけdoneを選んでください。",
"criteria": {
"plan": "SSOに対応するプランや月額料金の根拠が不足している",
"billing": "月途中で退会した場合の請求ルールが未確認",
"deletion": "退会完了からデータ削除までの期間が未確認",
"done": "プラン、料金、退会時の請求、データ削除の根拠がすべて揃っている"
}
}
},
"model": "jev-latest"
}
Jevから返ってきたレスポンスです。
{
"model": "jev-1.14.0",
"answers": {
"next": {
"type": "choice",
"choice": "deletion",
"confidence": 0.83,
"probabilities": {
"plan": 0.12,
"billing": 0.01,
"deletion": 0.87,
"done": 0
}
}
},
"usage": {
"input_tokens": 844,
"output_tokens": 47
}
}
choiceはdeletionでした。
「退会後の請求」により満額請求は確認できていますが、SSO対応プランとデータ削除日数はまだ不明です。今回は、そのうちデータ削除が選択されました。
比較実行でもdeletionが選択され、確率は0.87でした。
この判定を受けて、LLMが次の検索文を生成しました。
退会完了からデータ削除までの日数は何日ですか?
② 追加検索後、Jevの判断が変化する
2回目の検索では、「退会後のデータ削除」が取得されました。
これまでに集めた文書を再びJevへ渡すと、今度はplanが選択されました。
比較実行の確率は以下のとおりです。
| 選択肢 | 確率 |
|---|---|
| plan | 0.91 |
| billing | 0.04 |
| deletion | 0.01 |
| done | 0.04 |
請求とデータ削除の情報が揃ったため、残る不足情報はSSO対応プランと料金です。
次の検索文は以下になりました。
SSOが使えるプラン名を教えてください。
この時点ではプラン名がまだ分からないため、検索文にも「Proプラン」は含まれません。
3回目の検索結果は以下のとおりです。
| 文書 | 類似度 |
|---|---|
| ログイン | 0.5630 |
| Proプラン | 0.4536 |
| Freeプラン | 0.4104 |
「Proプラン」を取得したことで、SSO対応プランと月額料金を確認できました。
その後、Jevはdoneを選択しました。比較実行におけるdoneの確率は0.99でした。
検索結果が増えることでJevの判断が変わり、次に探す情報も切り替わることを確認できました。
③ 単純な質問では追加検索しない
2問目では、次の質問を使用します。
退会後、データはいつ削除されますか?
この質問では、確認すべき情報がデータ削除日数だけなので、選択肢はdeletionとdoneの2つです。
初回検索では「退会後のデータ削除」「退会後の請求」「支払い失敗」が取得されました。
{
"state": "質問: 退会後、データはいつ削除されますか?\n\n取得済み文書:\n# 退会\n## 退会後\n### 退会後のデータ削除\n\n退会が完了してから 30 日後に、ワークスペースのタスク、添付ファイル、メンバー一覧は完全に削除されます。30 日以内であれば、同じ管理者アカウントからワークスペースを再開できます。30 日を過ぎると再開はできません。\n\n# 退会\n## 退会後\n### 退会後の請求\n\n退会手続きが完了した月の料金は満額請求されます。月の途中で解約しても日割りにはなりません。退会の翌月以降、月額料金は請求されません。年払いの未経過分は返金しません。\n\n# 請求\n## 支払い\n### 支払い失敗\n\n支払いが失敗すると、7 日後と 14 日後に再試行します。14 日を過ぎても支払われない場合、ワークスペースは読み取り専用になります。データはすぐには削除されません。",
"questions": {
"next": {
"type": "choice",
"instructions": "質問への回答に必要な根拠を確認し、次に調べるべき情報を1つ選んでください。推測で情報が揃ったと判断せず、すべての項目を文書で確認できた場合だけdoneを選んでください。",
"criteria": {
"deletion": "退会完了からデータ削除までの期間が未確認",
"done": "データ削除までの期間の根拠が文書で確認できている"
}
}
},
"model": "jev-latest"
}
返ってきたレスポンスです。
{
"model": "jev-1.14.0",
"answers": {
"next": {
"type": "choice",
"choice": "done",
"confidence": 0.98,
"probabilities": {
"deletion": 0.01,
"done": 0.99
}
}
},
"usage": {
"input_tokens": 797,
"output_tokens": 32
}
}
choiceはdoneでした。
必要な「退会後のデータ削除」が最初の検索結果に含まれていたため、追加検索せずに回答生成へ進みます。
比較実行でもdoneが選択され、確率は0.99でした。
実行結果
比較はnpm run multihopで実行します。
Naive RAGでは、類似度上位3件を1回検索して回答します。Multi-hop RAGでは、各検索の上位3件を重複なく蓄積し、Jevが必要な情報が揃ったと判断するまで検索します。
① SSOが使えるプランを月の途中で退会した場合、当月の請求額とデータ削除までの日数は?
Naive RAGの結果
検索上位3件は以下のとおりです。
| 文書 | 類似度 |
|---|---|
| 退会後の請求 | 0.5867 |
| 日割り請求のルール | 0.5391 |
| プラン変更時の日割り | 0.5348 |
検索は1338 ms、回答生成LLMへの入力トークンは340、回答生成は1209 msでした。
回答は「退会した月は満額で、日割りにならない」という内容でした。
ただし、SSOが使えるプランとデータ削除までの日数は、確認できないという回答でした。
Multi-hop RAGの結果
1回目の検索結果はNaive RAGと同じ3件です。
Jevの判断と追加検索の結果は、次のようになりました。
| 段階 | Jevの判断 | 次の検索 |
|---|---|---|
| 1回目 |
deletion(0.87) |
データ削除までの日数 |
| 2回目 |
plan(0.91) |
SSOが使えるプラン |
| 3回目 |
done(0.99) |
検索終了 |
これにより、「退会後のデータ削除」と「Proプラン」を追加で取得できました。
最終的に蓄積した文書は8件です。
- 退会後の請求
- 日割り請求のルール
- プラン変更時の日割り
- 退会後のデータ削除
- 支払い失敗
- ログイン
- Proプラン
- Freeプラン
回答生成LLMへの入力トークンは708、回答生成は1189 msでした。
各処理の時間は以下のとおりです。
| 処理 | 実行時間 |
|---|---|
| 検索3回 | 2370 ms |
| Jevによる判断3回 | 795 ms |
| 検索文の生成2回 | 2531 ms |
| 最終回答の生成 | 1189 ms |
最終回答は、**「SSOが使えるのはProプランで、退会した月は満額、データは退会完了から30日後に削除される」**でした。
ただし、Proプランのマニュアルには月額12,000円(税別)と記載されているにもかかわらず、回答文にはこの金額が含まれていませんでした。
比較と考察
| 評価項目 | Naive RAG | Multi-hop RAG |
|---|---|---|
| 検索回数 | 1回 | 3回 |
| 取得した主要な根拠 | 退会後の請求 | 退会後の請求、データ削除、Proプラン |
| Jevの判断 | なし | deletion → plan → done |
| 最終回答 | 満額請求。プランと削除日数は不明 | Proプラン、満額請求、30日後。金額は未回答 |
| 回答生成LLM入力トークン | 340 | 708 |
| 検索時間 | 1338 ms | 2370 ms |
| Jev+検索文生成 | なし | 3326 ms |
Multi-hop RAGでは、Naive RAGが取得できなかった文書を段階的に収集できました。
ただし、回答生成LLMへの入力トークンは340から708に増加しています。さらに、Jevによる判断や検索文生成のトークンも別途発生します。
また、最終回答には請求額の12,000円が含まれていませんでした。
これは検索不足ではなく、必要な文書が揃っていても、回答生成時に一部の情報が抜け落ちることがあるという問題です。
Jevのdoneは検索に必要な根拠が揃ったという判断であり、生成された回答の完全性まで保証するものではありません。
② 退会後、データはいつ削除されますか?
Naive RAGの結果
検索上位3件は以下のとおりです。
| 文書 | 類似度 |
|---|---|
| 退会後のデータ削除 | 0.6478 |
| 退会後の請求 | 0.5101 |
| 支払い失敗 | 0.4841 |
検索は799 ms、回答生成LLMへの入力トークンは343、回答生成は1106 msでした。
回答は「退会完了から30日後に削除される」でした。
Multi-hop RAGの結果
最初の検索で、Naive RAGと同じ3件を取得しました。
Jevはdone(確率0.99)を選択し、追加検索は行われませんでした。
検索は679 ms、Jevの判断は210 ms、回答生成LLMへの入力トークンは343、回答生成は915 msでした。
回答はNaive RAGと同じく「退会完了から30日後に削除される」でした。
比較と考察
| 評価項目 | Naive RAG | Multi-hop RAG |
|---|---|---|
| 検索回数 | 1回 | 1回 |
| Jevの判断 | なし | done(0.99) |
| 最終回答 | 30日後に削除 | 30日後に削除 |
| 回答生成LLM入力トークン | 343 | 343 |
| 検索時間 | 799 ms | 679 ms |
| Jevの判断 | なし | 210 ms |
必要な文書が最初の検索に含まれている場合は、Jevがdoneを選択して追加検索を終了できました。
一方で、追加検索をしなくてもJevの判断処理は発生します。この1回の計測では、検索は679 ms、Jevは210 ms、回答生成は915 msでした。1回だけでは、どちらが速いかは判断できません。
単純な質問では、追加検索を避けることに加えて、そもそもJevによる判断を実行する必要があるかも検討する余地があります。
まとめ
今回は、Jevのchoiceを使って次に必要な情報を判断し、検索結果を蓄積しながら回答するMulti-hop RAGを実装しました。
Naive RAGとの比較結果は以下のとおりです。
| 質問 | Naive RAG | Multi-hop RAG |
|---|---|---|
| 複数の規定が必要 | 請求ルールのみ回答 | プラン・請求・データ削除まで回答。ただし金額は欠落 |
| 単純な質問 | 正しく回答 | 追加検索せず正しく回答 |
1問目では、Jevがdeletion → plan → doneと判断し、検索文を変えることで「退会後のデータ削除」と「Proプラン」を取得できました。
1回の検索では見つからなかった情報を、段階的に収集できたことが今回の成果です。
ただし、Jevがdoneと判断したあとでも、最終回答から月額12,000円が抜け落ちていました。
ここから、必要な文書を取得できたことと、質問のすべての項目に回答できたことは別の問題だと分かります。
改善策としては、取得した文書から回答に必要な項目を構造化して保持する方法や、Step5で実装したSelf-RAGのような回答検証を組み合わせる方法が考えられます。
また、複数回検索すると関係の薄い文書も蓄積され、今回のように入力トークンが増加します。Step2のFiltering RAGなどを組み合わせ、必要な根拠だけを回答生成に渡す工夫も有効そうです。
2問目では、Jevが最初の検索でdoneを選択し、不要な追加検索を避けられました。一方、Jevへの問い合わせ分の処理時間は発生しています。
今回の学習を通じて、Multi-hop RAGでは**「次に何を探すか」だけでなく、「いつ検索を終えるか」「集めた情報をどう回答に反映するか」も重要**だと感じました。
これで、Naive RAGから始めたRAG学習シリーズは一区切りです。これまで学んだ各手法は独立して使うだけでなく、組み合わせることで、より複雑な質問にも対応できると考えています。


