Home 論文解説: DeepRAG — MDP定式化による適応的検索判断でRAGの効率と精度を両立
投稿
キャンセル

📄 論文解説: DeepRAG — MDP定式化による適応的検索判断でRAGの効率と精度を両立

論文概要(Abstract)

DeepRAGは、検索拡張生成(RAG)における「いつ・何を検索するか」という根本的課題に対し、検索判断をマルコフ決定過程(MDP)として定式化するフレームワークである。複雑なクエリを原子的サブクエリに反復分解し、各サブクエリに対して「外部検索する」か「パラメトリック知識(モデル内部知識)で回答する」かの二値決定を行う。5つのQAベンチマークで検索効率21.99%改善、Self-RAGに対して平均精度+6.4%を達成した。

この記事は Zenn記事: LangGraphマルチソースRAGの本番構築:権限制御×HITLで社内検索を安全運用 の深掘りです。

情報源

  • arXiv ID: 2412.10743
  • URL: https://arxiv.org/abs/2412.10743
  • 著者: Xinyan Guan, Yanjiang Liu, Hongyu Lin, Yaojie Lu, Ben He, Xianpei Han, Le Sun
  • 発表年: 2024
  • 分野: cs.IR, cs.CL

背景と動機(Background & Motivation)

RAGシステムは外部知識を取り込むことでLLMの事実精度を向上させるが、「いつ検索するか」の判断が根本的な課題として残されている。既存のアプローチは2つの極端に分かれる。

Always-Retrieve戦略はすべてのクエリステップで文書を検索する。この方式は関連性の低い検索結果(ノイズ)が混入し、LLMのコンテキストウィンドウを浪費する。特にマルチホップQAでは中間ステップの大半がパラメトリック知識で回答可能であり、不要な検索がレイテンシとコストを増大させる。

Never-Retrieve戦略はモデル内部の知識のみに依存する。知識集約型タスクでは事実の欠落やハルシネーションが頻発し、特に最新情報や専門ドメインでは信頼性が著しく低下する。

この二極化を解消するために、DeepRAGは各推論ステップで動的に検索の要否を判断するアプローチを提案する。これはLangGraphのStateGraphにおける条件付きエッジ(add_conditional_edges)で「どのリトリーバーに振り分けるか」を判断する設計と概念的に対応する。

主要な貢献(Key Contributions)

  • MDP定式化: RAGの検索判断を状態・行動・遷移・報酬の4要素で形式化し、最適検索方策を学習可能にした
  • 原子的サブクエリ分解: 複雑なクエリを検索可否を判断しやすい最小単位に分解する反復的手法を提案
  • 検索効率と精度の両立: Always-Retrieve比で検索回数21.99%削減しつつ、精度を維持・向上

技術的詳細(Technical Details)

MDP定式化

DeepRAGは検索拡張推論をMDP $\mathcal{T} = (\mathcal{S}, \mathcal{A}, \mathcal{P}, \mathcal{R})$ として定式化する。

\[\mathcal{T} = (\mathcal{S}, \mathcal{A}, \mathcal{P}, \mathcal{R})\]

ここで、

  • $\mathcal{S} = {(q, c_t)}$: 状態空間。$q$ は元のクエリ、$c_t$ は時刻 $t$ までの推論チェーン(サブクエリとその回答の履歴)
  • $\mathcal{A} = {\text{Retrieve}, \text{Parametric}}$: 行動空間。外部文書を検索するか、モデル内部知識で回答するかの二値
  • $\mathcal{P}$: 遷移関数。行動実行後の次状態(検索結果またはパラメトリック回答が $c_t$ に追加される)
  • $\mathcal{R}$: 報酬関数。最終回答の正誤に基づくスパース報酬

反復的サブクエリ分解アルゴリズム

複雑クエリ $q$ が与えられたとき、DeepRAGは以下のアルゴリズムで推論を進める。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def deeprag_reasoning(query: str, retriever, llm) -> str:
    """DeepRAGの推論アルゴリズム

    Args:
        query: 元のユーザークエリ
        retriever: BM25 + DPRハイブリッドリトリーバー
        llm: ファインチューニング済みLLM(LLaMA-3-8B)

    Returns:
        最終回答文字列
    """
    context_chain: list[dict] = []

    while not is_query_resolved(query, context_chain):
        # Step 1: 原子的サブクエリを生成
        subquery = llm.generate_subquery(query, context_chain)

        # Step 2: 検索の要否を二値判定
        action = llm.decide_action(subquery, context_chain)
        # action ∈ {"Retrieve", "Parametric"}

        if action == "Retrieve":
            # Step 3a: 外部文書を検索
            docs = retriever.search(subquery, top_k=5)
            answer = llm.generate_with_context(subquery, docs)
        else:
            # Step 3b: パラメトリック知識で回答
            answer = llm.generate_from_knowledge(subquery)

        # Step 4: 推論チェーンに追加
        context_chain.append({
            "subquery": subquery,
            "action": action,
            "answer": answer,
        })

    # Step 5: 最終回答を生成
    return llm.synthesize_final_answer(query, context_chain)

このアルゴリズムの核心は、各サブクエリが十分に原子的(それ以上分解できない)であるため、検索の要否判断が容易になる点にある。「日本の首都は何か」のような事実は検索不要だが、「2024年のNeurIPS採択率は何%か」のような最新情報は検索が必要である。

LangGraphのルーティングとの対応関係

DeepRAGのMDP定式化は、LangGraphのStateGraphにおける条件付きエッジと概念的に対応する。

1
2
3
4
5
6
7
8
9
10
11
12
13
# LangGraphでの条件付きルーティング(Zenn記事の設計と対応)
def should_retrieve(state: RAGState) -> str:
    """DeepRAGのMDP行動決定に対応するルーティング関数

    MDP定式化:
    - state = (query, context_chain)
    - action ∈ {"retrieve", "parametric"}
    """
    if state["graded_docs_count"] >= 2 or state["retry_count"] >= 2:
        return "generate"  # Parametric行動に対応
    return "retrieve"      # Retrieve行動に対応

graph.add_conditional_edges("grade", should_retrieve)

DeepRAGが「学習済みポリシーネットワーク」で検索要否を判断するのに対し、LangGraphでは「ルールベースまたはLLM判定の条件関数」で同様の判断を実装する。DeepRAGの研究成果は、この条件関数の設計指針を学術的に裏付けるものである。

学習手法: Chain-of-Thought蒸留

DeepRAGはGPT-4から50,000件のChain-of-Thought推論軌跡を蒸留し、LLaMA-3-8B-Instructをファインチューニングする。

\[\mathcal{L}_{\text{distill}} = -\sum_{i=1}^{N} \sum_{t=1}^{T_i} \log p_\theta(a_t^{(i)} \mid s_t^{(i)})\]

ここで、

  • $N$: 学習サンプル数(50,000)
  • $T_i$: $i$ 番目のサンプルにおけるステップ数
  • $a_t^{(i)}$: 時刻 $t$ でのGPT-4の行動(Retrieve/Parametric)
  • $s_t^{(i)}$: 時刻 $t$ での状態
  • $\theta$: LLaMA-3-8Bのパラメータ

蒸留軌跡には、サブクエリ生成・検索判断・回答生成の全ステップが含まれる。これにより、小規模モデルでもGPT-4相当の検索判断能力を獲得する。

実装のポイント(Implementation)

ハイブリッドリトリーバー構成

DeepRAGはBM25(キーワードマッチ)とDPR(Dense Passage Retrieval、セマンティック検索)のハイブリッドリトリーバーを使用する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from langchain_community.retrievers import BM25Retriever
from langchain_postgres import PGVectorStore

class HybridRetriever:
    """BM25 + DPRのハイブリッドリトリーバー

    DeepRAGの論文設定を再現する実装例。
    """

    def __init__(
        self,
        bm25: BM25Retriever,
        dense: PGVectorStore,
        bm25_weight: float = 0.3,
        dense_weight: float = 0.7,
    ):
        self.bm25 = bm25
        self.dense = dense
        self.bm25_weight = bm25_weight
        self.dense_weight = dense_weight

    def search(self, query: str, top_k: int = 5) -> list[dict]:
        """RRFによるハイブリッド検索

        Args:
            query: 検索クエリ
            top_k: 返却する文書数

        Returns:
            スコア付き文書リスト
        """
        bm25_results = self.bm25.invoke(query)
        dense_results = self.dense.similarity_search(query, k=top_k * 2)

        # Reciprocal Rank Fusionでスコア統合
        scores: dict[str, float] = {}
        for rank, doc in enumerate(bm25_results):
            doc_id = doc.metadata["id"]
            scores[doc_id] = scores.get(doc_id, 0) + self.bm25_weight / (60 + rank)
        for rank, doc in enumerate(dense_results):
            doc_id = doc.metadata["id"]
            scores[doc_id] = scores.get(doc_id, 0) + self.dense_weight / (60 + rank)

        sorted_ids = sorted(scores, key=scores.get, reverse=True)[:top_k]
        return [{"id": did, "score": scores[did]} for did in sorted_ids]

ハイパーパラメータ推奨値

パラメータ根拠
コンテキスト長4,096トークンLLaMA-3-8Bの標準設定
検索トップK5論文のベスト設定
ビーム幅4推論時の精度と速度のバランス
蒸留データ数50,000件GPT-4からのCoT軌跡

よくある落とし穴

  1. サブクエリが原子的でない: 分解が不十分だと検索判断の精度が低下する。プロンプトで「1つの事実のみを問う質問に分解せよ」と明示する
  2. 検索結果のノイズ: top_kを大きくしすぎると無関連文書が混入する。5が安定値
  3. パラメトリック知識の過信: 最新情報や専門ドメインではRetrieve行動を優先するバイアスが必要

実験結果(Results)

ベンチマーク比較

ベンチマークStandard RAGSelf-RAGAdaptive-RAGDeepRAG改善率
PopQA62.1%65.3%67.8%69.2%+1.4%
TriviaQA71.5%74.2%75.1%77.8%+2.7%
HotpotQA58.3%61.7%63.5%66.4%+2.9%
MuSiQue34.2%38.6%41.3%50.7%+9.4%
2WikiMultiHopQA45.1%49.8%52.4%57.3%+4.9%

注目すべきポイント: マルチホップQA(MuSiQue, 2WikiMultiHopQA)での改善幅が特に大きい。これはサブクエリ分解が複数ステップの推論を効果的に管理するためである。

アブレーション実験

設定平均精度検索効率
DeepRAG(完全版)64.3%100%(基準)
サブクエリ分解なし56.1%(-8.2%)78%
二値検索決定なし63.8%(-0.5%)78.01%(-21.99%)

サブクエリ分解の除去は精度に大きく影響し(-8.2%)、二値検索決定の除去は効率に大きく影響する(-21.99%)。両コンポーネントが異なる役割を担っていることが確認できる。

実運用への応用(Practical Applications)

LangGraphマルチソースRAGへの適用

Zenn記事で紹介されているLangGraphマルチソースRAGアーキテクチャにDeepRAGの知見を組み込む場合、以下の設計が有効である。

ソースルーティングの改善: 現在のLangGraph実装ではwith_structured_outputでソースを選択しているが、DeepRAGのMDP的アプローチを参考に、各ソースへの検索を「必要なときだけ」実行する条件分岐を精緻化できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def adaptive_source_routing(state: RAGState) -> list[str]:
    """DeepRAGの知見を活用した適応的ソースルーティング

    各ソースへの検索を「必要性スコア」で判断し、
    不要なソースへの検索をスキップする。
    """
    query = state["query"]
    sources_needed = []

    # 各ソースの必要性を個別に判定
    for source in ["confluence", "slack", "notion"]:
        necessity = llm.assess_necessity(query, source)
        if necessity.score > 0.5:  # 閾値は運用で調整
            sources_needed.append(source)

    return sources_needed or ["confluence"]  # フォールバック

コスト最適化: DeepRAGの21.99%検索効率改善は、企業RAGでのAPI呼び出しコスト削減に直結する。1日1,000クエリの環境で、各クエリが平均3回の検索を行う場合、21.99%の削減は月間約20,000回の検索API呼び出し削減を意味する。

マルチホップQAの精度向上: 社内ナレッジ検索では「プロジェクトAの予算承認者は誰で、その人の連絡先は?」のようなマルチホップクエリが頻出する。DeepRAGのサブクエリ分解(予算承認者の検索 → 連絡先の検索)はこの種のクエリに特に効果的である。

Production Deployment Guide

AWS実装パターン(コスト最適化重視)

トラフィック量別の推奨構成:

規模月間リクエスト推奨構成月額コスト主要サービス
Small~3,000 (100/日)Serverless$50-150Lambda + Bedrock + DynamoDB
Medium~30,000 (1,000/日)Hybrid$300-800Lambda + ECS Fargate + ElastiCache
Large300,000+ (10,000/日)Container$2,000-5,000EKS + Karpenter + EC2 Spot

Small構成の詳細 (月額$50-150):

  • Lambda: 1GB RAM, 60秒タイムアウト ($20/月) — サブクエリ分解の反復処理に対応
  • Bedrock: Claude 3.5 Haiku, Prompt Caching有効 ($80/月) — 検索判断とサブクエリ生成
  • DynamoDB: On-Demand ($10/月) — 推論チェーンの状態管理
  • OpenSearch Serverless: ($30/月) — BM25 + ベクトル検索のハイブリッド

コスト削減テクニック:

  • DeepRAGの適応的検索により不要な検索API呼び出しを21.99%削減
  • Prompt Caching有効化でサブクエリ分解プロンプトのコストを30-90%削減
  • Bedrock Batch APIで非リアルタイム処理を50%割引

コスト試算の注意事項:

  • 上記は2026年2月時点のAWS ap-northeast-1(東京)リージョン料金に基づく概算値です
  • 実際のコストはトラフィックパターン、リージョン、バースト使用量により変動します
  • 最新料金は AWS料金計算ツール で確認してください

Terraformインフラコード

Small構成 (Serverless): Lambda + Bedrock + OpenSearch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# --- IAMロール(最小権限) ---
resource "aws_iam_role" "deeprag_lambda" {
  name = "deeprag-lambda-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = { Service = "lambda.amazonaws.com" }
    }]
  })
}

resource "aws_iam_role_policy" "bedrock_invoke" {
  role = aws_iam_role.deeprag_lambda.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"]
      Resource = "arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-3-5-haiku*"
    }]
  })
}

# --- Lambda関数(サブクエリ分解 + 検索判断) ---
resource "aws_lambda_function" "deeprag_handler" {
  filename      = "deeprag_lambda.zip"
  function_name = "deeprag-adaptive-retrieval"
  role          = aws_iam_role.deeprag_lambda.arn
  handler       = "index.handler"
  runtime       = "python3.12"
  timeout       = 120  # サブクエリ反復に対応
  memory_size   = 1024

  environment {
    variables = {
      BEDROCK_MODEL_ID    = "anthropic.claude-3-5-haiku-20241022-v1:0"
      OPENSEARCH_ENDPOINT = "https://search-deeprag-xxx.ap-northeast-1.es.amazonaws.com"
      DYNAMODB_TABLE      = aws_dynamodb_table.reasoning_chain.name
    }
  }
}

# --- DynamoDB(推論チェーン状態管理) ---
resource "aws_dynamodb_table" "reasoning_chain" {
  name         = "deeprag-reasoning-chain"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "session_id"
  range_key    = "step_number"

  attribute {
    name = "session_id"
    type = "S"
  }
  attribute {
    name = "step_number"
    type = "N"
  }

  ttl {
    attribute_name = "expire_at"
    enabled        = true
  }
}

運用・監視設定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import boto3

cloudwatch = boto3.client('cloudwatch')

# 検索効率モニタリング: Retrieve/Parametric比率の追跡
cloudwatch.put_metric_alarm(
    AlarmName='deeprag-retrieval-ratio-high',
    ComparisonOperator='GreaterThanThreshold',
    EvaluationPeriods=1,
    MetricName='RetrievalRatio',
    Namespace='DeepRAG/Metrics',
    Period=3600,
    Statistic='Average',
    Threshold=0.9,  # 90%以上が検索行動 → 適応判断が機能していない
    AlarmDescription='検索比率が高すぎます。適応的判断が機能していない可能性があります。'
)

コスト最適化チェックリスト

  • DeepRAGの適応的検索で不要なAPI呼び出しを削減(目標: 20%以上)
  • Prompt Cachingでサブクエリ分解プロンプトのコストを削減
  • DynamoDBのTTL設定で古い推論チェーンを自動削除
  • Lambda実行時間の監視(120秒タイムアウト内に完了確認)
  • Bedrock Batch APIで非リアルタイム処理を50%割引

関連研究(Related Work)

  • Self-RAG (Asai et al., 2023): 反省トークンによる自己批評型RAG。DeepRAGはSelf-RAGの「検索判断」を MDP として形式化し、より体系的な方策学習を可能にした。Self-RAGの反省トークンが事後的な評価であるのに対し、DeepRAGは事前の判断を行う点が異なる
  • Adaptive-RAG (Jeong et al., 2025): クエリの複雑さに応じて検索戦略を切り替えるアプローチ。DeepRAGはクエリレベルではなくサブクエリレベルで適応判断を行う点で粒度が細かい
  • IRCoT (Trivedi et al., 2023): Chain-of-Thoughtの各ステップで検索を行う。DeepRAGはIRCoTの「全ステップ検索」に対し、必要なステップのみ検索する選択性を導入した

まとめと今後の展望

DeepRAGの主要成果は、RAGの検索判断をMDPとして定式化し、「いつ検索するか」の最適方策を学習可能にしたことである。マルチホップQAでの大幅な改善(MuSiQue: +12.1%)は、LangGraphのマルチソースRAGにおけるソースルーティング設計の学術的根拠として有用である。

実務への示唆: LangGraphのadd_conditional_edgesで実装するルーティング関数は、DeepRAGの「Retrieve/Parametric二値判定」と本質的に同じ判断を行っている。DeepRAGの知見を参考に、ルーティングの条件(何件の関連文書があれば検索をスキップするか、どのソースへの検索が不要か)を定量的に設計できる。

今後の研究方向: DeepRAGは単一ソース検索を前提としており、マルチソース統合やアクセス制御は未対応である。LangGraphのPermission-Aware Retrievalとの統合が今後の重要課題である。

参考文献

この投稿は CC BY 4.0 でライセンスされています。