はじめに
RAG(Retrieval-Augmented Generation)は、LLMに外部知識を与えて回答精度を向上させる技術として注目されています。しかし、従来のRAGは「1回だけ検索して回答」という単純なアプローチでした。
本記事では、**答えに近づくまで何度も検索を繰り返す「反復検索型RAG」**を実装し、その動作原理と通常のRAGとの違いを詳しく解説します。
目次
反復検索型RAGとは
従来のRAG(1回検索)
反復検索型RAG(複数回検索)
主な特徴
- 自己評価機能: LLMが検索結果を分析し、信頼度を評価
- クエリ最適化: 不足情報を特定し、検索クエリを自動改善
- 反復プロセス: 十分な情報が集まるまで検索を繰り返す
- 早期終了: 改善が見込めない場合は適切に終了
実装の詳細
システムアーキテクチャ
class IterativeRAG:
def __init__(
self,
api_key: str,
provider: str = "anthropic",
max_iterations: int = 5,
confidence_threshold: float = 0.85
):
# 初期化処理
重要なパラメータ
| パラメータ | 説明 | デフォルト値 |
|---|---|---|
max_iterations |
最大検索回数 | 5回 |
confidence_threshold |
信頼度閾値 | 0.85 |
検索ループの実装
for iteration in range(1, self.max_iterations + 1):
# 1. ドキュメント検索
search_results = self._search_documents(current_query, top_k=5)
# 2. 結果を分析(LLMが評価)
analysis = self._analyze_results(question, search_results, iteration_history)
# 3. 終了条件チェック
if analysis['confidence'] >= self.confidence_threshold:
break # 十分な情報が集まった
# 4. 次のクエリを生成
if analysis['next_query'] and analysis['next_query'].lower() != 'なし':
current_query = analysis['next_query']
else:
break # これ以上の検索は不要
信頼度評価の仕組み
重要: 信頼度は検索スコアの平均ではありません。
検索スコア vs 信頼度
| 項目 | 検索スコア | 信頼度 |
|---|---|---|
| 算出方法 | ベクトル類似度(自動計算) | LLMの意味理解(AI判断) |
| 評価対象 | 単語の一致度 | 質問への回答可能性 |
| 例 | 0.68(「IBM Bob」が含まれる) | 0.10(「誕生日」は書かれていない) |
LLMによる分析プロンプト
analysis_prompt = f"""
あなたは情報検索の専門家です。以下の情報を分析してください。
【元の質問】
{original_query}
【現在の検索結果】
{results_text}
以下の形式で分析結果を返してください:
CONFIDENCE: [0.0-1.0の数値]
REASONING: [この結果で質問に答えられるか、何が不足しているかの説明]
MISSING_INFO: [不足している情報があれば具体的に記述、なければ「なし」]
NEXT_QUERY: [追加検索が必要な場合の改善されたクエリ、不要なら「なし」]
"""
実際の動作例と分析
ケース1: 情報が存在しない場合
質問: 「IBM Bobの誕生日を教えてください。」
検索プロセス
検索 1回目
├─ クエリ: IBM Bobの誕生日を教えてください。
├─ 信頼度: ❌ 0.10
└─ 検索結果スコア: 0.680, 0.641, 0.595(平均: 0.639)
検索 2回目
├─ クエリ: IBM Bob 発表日 リリース日 2024 2025
├─ 信頼度: ❌ 0.15
└─ 検索結果スコア: 0.618, 0.614, 0.546
検索 3回目
├─ クエリ: IBM Bob 発表日 リリース日 2024 2025
├─ 信頼度: ❌ 0.20
└─ LLMが「これ以上検索不要」と判断 → 終了
最終結果
├─ 検索回数: 3回(最大5回の制限内)
└─ 最終信頼度: 0.20
なぜ3回で終了したのか?
終了条件は2つ:
- 信頼度が閾値(0.85)以上 ← 今回は未達成
- LLMが「これ以上検索不要」と判断 ← 今回はこちら
# コードの該当部分
if analysis['next_query'] and analysis['next_query'].lower() != 'なし':
current_query = analysis['next_query']
else:
break # これ以上の検索は不要と判断
LLMが「検索を続けても同じ結果しか得られない」「質問に答える情報がそもそも存在しない」と判断し、早期終了しました。
スコアが高いのに信頼度が低い理由
検索結果スコア平均: 0.639(64%)
実際の信頼度: 0.10(10%)
理由:
- 「IBM Bob」という単語は文書に含まれている → スコア高い
- しかし「誕生日」「発表日」の情報は書かれていない → 信頼度低い
これは意図的な設計で、より正確な回答を生成するための仕組みです。
ケース2: 情報が存在する場合
質問: 「IBM Bobの強みを100文字程度で教えてください。」
検索プロセス
検索 1回目
├─ クエリ: IBM Bobの強みを100文字程度で教えてください。
├─ 信頼度: ⚠️ 0.75
├─ 検索結果スコア: 0.747, 0.712, 0.650
└─ 判定: 0.75 < 0.85 → 検索継続
検索 2回目
├─ クエリ: IBM Bob エンタープライズ機能 セキュリティ 統合 差別化
├─ 信頼度: ✅ 0.85
├─ 検索結果スコア: 0.809, 0.804, 0.687
└─ 判定: 0.85 >= 0.85 → 検索終了
最終結果
├─ 検索回数: 2回
└─ 最終信頼度: 0.85
理想的な動作例
- 1回目(0.75): 基本情報は取得できたが、詳細が不足
- LLMが分析: 「エンタープライズ機能の詳細が不足」
- クエリ改善: より具体的なキーワードで再検索
- 2回目(0.85): 閾値到達で効率的に終了
# 終了判定のコード
if analysis['confidence'] >= self.confidence_threshold:
print(f"[OK] 十分な情報が集まりました(信頼度: {analysis['confidence']:.2f})")
break
通常のRAGとの比較
メリット(優位性)
1. 複雑な質問への対応力
通常のRAG:
質問: 「PythonとJavaの機械学習ライブラリを比較して」
→ 1回検索 → 部分的な情報のみ → 不完全な回答
反復検索RAG:
1回目: 「Python 機械学習」→ TensorFlow, PyTorchの情報
2回目: 「Java 機械学習」→ Weka, DeepLearning4jの情報
3回目: 「比較 性能 使いやすさ」→ 比較情報
→ 包括的な回答
2. 情報の不足を自己認識
- 通常のRAG: 不足していても気づかず回答
- 反復検索RAG: 「何が足りないか」を分析して追加検索
3. クエリの自動最適化
元の質問: 「IBM Bobの強みを教えて」
↓
改善クエリ: 「IBM Bob エンタープライズ機能 セキュリティ 統合 差別化」
4. 信頼度の可視化
- ユーザーは「どれだけ確実な情報か」を数値で確認可能
- 低信頼度(0.20)なら「情報源に答えがない」と判断できる
デメリット(課題)
1. コストと時間
| 項目 | 通常のRAG | 反復検索RAG |
|---|---|---|
| LLM呼び出し | 1回 | 3-5回以上 |
| API料金 | 低 | 2-5倍 |
| 応答時間 | 速い(数秒) | 遅い(10-30秒) |
実例:
- 通常: 1回の検索 + 1回の回答生成 = 2回のLLM呼び出し
- 反復: 3回の分析 + 1回の回答生成 = 4回のLLM呼び出し
2. 必ずしも改善しない場合
誕生日の質問:
1回目: 0.10 → 2回目: 0.15 → 3回目: 0.20
→ 情報がないのに検索を繰り返す(無駄なコスト)
3. シンプルな質問には過剰
質問: 「Pythonとは何ですか?」
→ 1回の検索で十分なのに、複数回検索してしまう可能性
使い分けの推奨
反復検索RAGが有効なケース ✅
- 複雑な質問: 比較、分析、多面的な情報が必要
- 専門的な質問: 詳細な技術情報、複数の観点
- 探索的な質問: 「〜について詳しく」「包括的に」
- 高精度が必要: 重要な意思決定、正確性重視
例:
- 「製品AとBの技術的な違いと導入コストを比較」
- 「この技術の歴史、現状、将来展望を教えて」
通常のRAGで十分なケース ⭕
- シンプルな質問: 定義、基本情報
- 速度重視: リアルタイム応答が必要
- コスト制約: 大量のクエリ処理
- 情報が豊富: 1回で十分な情報が得られる
例:
- 「Pythonとは?」
- 「この製品の価格は?」
総合評価
| 評価項目 | 通常のRAG | 反復検索RAG |
|---|---|---|
| 複雑な質問への対応 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| シンプルな質問 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| コスト効率 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| 応答速度 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| 精度・信頼性 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
改善提案
1. ハイブリッドアプローチ
# 初回検索の信頼度で判断
if first_confidence >= 0.8:
# 通常のRAGモード(1回で終了)
return generate_answer(first_results)
else:
# 反復検索モード
continue_iterative_search()
2. 早期終了の改善
# 信頼度が改善しない場合は早期終了
if len(iteration_history) >= 2:
confidence_improvement = (
iteration_history[-1].confidence -
iteration_history[-2].confidence
)
if confidence_improvement < 0.05:
break # 改善が見込めない
3. コスト最適化
# 軽量モデルで分析、重量モデルで最終回答のみ
self.model_analyze = "gpt-4o-mini" # 分析用
self.model_answer = "gpt-4o" # 回答生成用
まとめ
反復検索型RAGの特徴
✅ 優位性:
- 複雑な質問に対する高い対応力
- 情報不足の自己認識と自動補完
- クエリの自動最適化
- 信頼度の可視化
❌ 課題:
- コストが2-5倍
- 応答時間が遅い
- シンプルな質問には過剰
推奨される使い方
重要な質問・複雑な質問 → 反復検索RAG
日常的な質問・シンプルな質問 → 通常のRAG
または、初回の信頼度で自動切り替えするハイブリッドアプローチが理想的です。
実装のポイント
- 信頼度は検索スコアの平均ではない: LLMが内容を読んで判断
- 2つの終了条件: 信頼度閾値到達 or LLMの判断
- 早期終了の重要性: 無駄なコストを避ける
今後の展望
- より効率的な終了判定アルゴリズム
- コスト最適化(軽量モデルの活用)
- ハイブリッドアプローチの実装
- ストリーミング対応による体感速度の改善
おわりに
反復検索型RAGは、通常のRAGと比べてコストと時間がかかりますが、複雑な質問に対する回答精度は大幅に向上します。用途に応じて適切に使い分けることで、より実用的なRAGシステムを構築できます。
この記事が、RAGシステムの設計・実装の参考になれば幸いです。