DualRAGは、天津大学などの研究チームがACL 2025で発表した反復型RAGの手法です。推論を進めながら「次に何を調べるか」を決めるRaQ(Reasoning-augmented Querying)と、集めた文書をエンティティごとに要約して積み上げるpKA(progressive Knowledge Aggregation)の2つの処理を交互に回し、複数の文書をまたぐ質問(マルチホップ質問)に答えます。RAGそのものの基本はRAGとは?仕組みとLLM・ファインチューニングとの違い・企業での導入例を解説で扱っているので、この記事では論文と公式実装から、DualRAGの処理の中身と実験結果の読み方、採用を判断する条件を整理します。
まとめ:DualRAGの要点と採用判断
- DualRAGは「推論しながら足りない知識を見つけて検索する」RaQと、「検索結果をエンティティ単位の知識アウトラインに要約して蓄積する」pKAの2系統で動く反復型RAGです。
- Qwen2.5-72B-Instructでの実験では、2WikiMultihopQAのAccが85.0、MuSiQueが66.3で、比較した反復型RAG(IRCoT・MetaRAG・GenGround)を上回りました。一方、HotpotQAのEMとF1ではIRCoTやMetaRAGに届いていません。
- 実験での平均検索反復回数は1問あたり1.64〜2.20回で、IRCoTの3.76〜4.26回を下回りました。1回の反復の中では複数の検索クエリを実行します。なお、論文が報告するのは出力トークンだけで、知識アウトラインを毎回プロンプトに入れる入力側のコストは示されていません。
- 公式実装(GitHub: cbxgss/rag)は公開されていますが、ライセンス表記がありません(2026-10-06時点)。業務コードへの流用には著者の許諾が要ります。
- 向いているのは、人物・組織・製品といったエンティティの関係をたどる2〜4段の質問です。単発質問では指標によって優劣が分かれます。長文生成では単純なRAGを上回りましたが、ELI5のRouge-LではIRCoTを下回りました。
DualRAGの論文情報と公開物
DualRAGの正式名は「DualRAG: A Dual-Process Approach to Integrate Reasoning and Retrieval for Multi-Hop Question Answering」です。arXivには3つの版があり、主要な実験表(Table 2)の数値はv1からv3、ACL Anthology掲載版まで変わっていません。v2以降には、検索回数やトークン量の分析、FLAREとの比較などの付録が加わっています。
| 項目 | 内容 |
|---|---|
| 掲載 | ACL 2025 Long Papers(pp.31877-31899) |
| arXiv | 2504.18243(v1 2025-04-25/v3 2025-10-03) |
| 著者 | Rong Cheng、Jianye Hao ほか計9名 |
| 主な所属 | 天津大学 College of Intelligence and Computing |
| 公式実装 | GitHub cbxgss/rag(ライセンス表記なし) |
| 学習済み重み | READMEに配布先の記載なし |
原典はACL Anthology掲載版とarXiv版、実装はGitHubの公式リポジトリ(cbxgss/rag)で確認できます。
単発RAGと従来の反復型RAGで解けない3つの課題
論文は、マルチホップ質問で知識の需要が推論の途中で変わる点を出発点にしています。たとえば「監督の生年が遅いのはどちらの映画か」という質問では、まず各映画の監督名を調べ、その名前が分かってから初めて生年を検索できます。最初の質問文だけで検索する単発のRAG(retrieve-then-read)には、取得した監督名を使って追加検索する段階がありません。
論文が挙げる課題は「いつ検索するか」「何を検索するか」「集めた情報をどう整理するか」の3つです。IRCoTのように一定間隔で検索する方式は、知識が足りているときにも推論を中断します。検索結果を並べてプロンプトに入れるだけでは、反復のたびにノイズが積み上がり、文書の並び順にも回答が左右されます。並び順の影響はLost in the Middle現象とは|RAG精度が落ちる仕組みと設計での対策で扱っている現象で、DualRAGの論文もこの研究を引用しています。
検索件数を増やすだけでは、取得結果を使った追加検索と同じ改善が得られるとは限りません。付録H.1では、GPT-4o-miniを使ったHotpotQAで、1回に10件を取る方式と200件を取る方式のF1が0.523と0.487で、件数を20倍にしても上がりませんでした。1回目の結果を手がかりに2回目を検索する方式は0.575です。
RaQとpKAの仕組み:3つのLLM処理役と知識アウトライン
DualRAGの主要部品は、3つのLLM処理役と知識を保持するKnowledge Outlineです。RaQ側にReasonerとEntity Identifier、pKA側にKnowledge SummarizerとKnowledge Outlineがあります。LLMを使う3役は同じベースLLMにプロンプトを変えて担当させ、Knowledge Outlineはコード側で保持・更新します。
| 系統 | 部品 | 入力 | 出力 |
|---|---|---|---|
| RaQ | Reasoner | 質問・推論履歴・知識アウトライン | 次の推論1歩と検索要否 |
| RaQ | Entity Identifier | 推論履歴・既知のエンティティ | エンティティごとの検索クエリ群 |
| pKA | Knowledge Summarizer | エンティティ・クエリ・取得文書 | エンティティ単位の要約 |
| pKA | Knowledge Outline | 過去の要約と新しい要約 | エンティティ別の知識一覧 |
RaQの検索要否判定とエンティティ別クエリ生成
Reasonerは、それまでの推論と知識アウトラインを読んで推論を1歩進め、知識が足りなければ検索フラグを立てます。公式実装ではJSONで {"thought": ..., "need_retrieve": true} の形を返させています。フラグが立たなければ反復を終え、最後に回答役のLLMが答えを書きます。
フラグが立つと、Entity Identifierが「足りない知識はどのエンティティに属するか」を判断し、エンティティごとに複数のクエリを作ります。論文のケーススタディでは、1回目に「El Extraño Viaje director」「El Extraño Viaje director birth year」のように2作品分のクエリを出し、監督名が分かった2回目には「Charles Saunders birth year」のように監督個人のクエリへ切り替えています。前の反復で出てきた別表記のエンティティを同じものとして結び付けるのもこの部品の役目です。
pKAのエンティティ別要約と知識アウトラインへの蓄積
エンティティごとに集めた文書は、リランカーで上位10件に絞ってからKnowledge Summarizerに渡します。Summarizerは質問に関係する部分だけを要約し、関係する記述が無ければ「None」を返します。要約はエンティティ名を見出しにしたKnowledge Outlineへ追記され、次の反復ではReasonerがこのアウトラインを読みます。
アウトラインを使わず要約だけを並べた変種(付録H.3のw/o KO*)では、2WikiMultihopQAのAccが85.0から74.3に下がりました。要約するだけでは足りず、エンティティ単位にまとめ直すところに効果があるという結果です。
公式実装から読むDualRAGのループ
公式リポジトリの src/rag/duralrag/rag.py(ディレクトリ名は原文どおり「duralrag」)を読むと、処理は1つの for ループにまとまっています。設定ファイル config/task/method/dualrag.yaml の値は、最大反復5回、クエリごとの取得50件、リランク後10件です。下のコードは、そのループをLLMと検索をスタブに差し替えて再現したもので、論文のケーススタディ(Table 9)に登場する文書を抜粋・短縮したデータで処理順を確認できる例です。検索先と回答はスタブに固定しており、実際のLLMの推論精度を検証するものではありません。
import json
MAX_ITER, TOPK, RERANK_TOPK = 5, 50, 10 # 公式設定 dualrag.yaml の値
def dualrag(question, llm, retrieve, rerank):
kb, thoughts = {}, [] # kb: エンティティ -> 要約のリスト(Knowledge Outline)
for step in range(MAX_ITER):
# RaQ-1 Reasoner: 推論を1歩進め、知識が足りるかを判定
r = json.loads(llm("infer", question=question, kb=kb, thoughts=thoughts))
thoughts.append(r["thought"])
if not r["need_retrieve"]:
break
# RaQ-2 Entity Identifier: 足りない知識をエンティティ単位のクエリ群にする
ents = json.loads(llm("EI", question=question, kb=kb, thoughts=thoughts))["entities"]
for e in ents:
docs = []
for q in e["keywords"]:
docs += [d for d in retrieve(q, TOPK) if d not in docs]
docs = rerank(e["entity"], docs, RERANK_TOPK)
# pKA: エンティティごとに要約し、アウトラインへ追記
s = llm("KS", question=question, entity=e["entity"], docs=docs, thoughts=thoughts)
if s.strip().casefold() != "none": # 大文字・小文字をそろえて完全一致で判定
kb.setdefault(e["entity"], []).append(s)
return llm("answer", question=question, kb=kb, thoughts=thoughts), kb, len(thoughts)
# ---- 動作確認用のスタブ(LLMと検索の代わり)----
# 文書は論文 Table 9 のケーススタディから引用
CORPUS = {"Love In Pawn": "Love in Pawn is a 1953 British comedy film directed by Charles Saunders.",
"Charles Saunders": "Charles Joel Saunders (8 April 1904 - April 1997) was an English film director.",
"El Extraño Viaje": "El extraño viaje is a 1964 Spanish black drama film directed by Fernando Fernán Gómez.",
"Fernando Fernán Gómez": "Fernando Fernández Gómez (28 August 1921 - 21 November 2007)."}
def stub_llm(role, **kw):
kb = kw.get("kb", {})
if role == "infer":
need = len(kb) < 4
return json.dumps({"thought": f"known={sorted(kb)}", "need_retrieve": need})
if role == "EI":
names = ["Love In Pawn", "El Extraño Viaje"] if not kb else ["Charles Saunders", "Fernando Fernán Gómez"]
return json.dumps({"entities": [{"entity": n, "keywords": [n, n + " director"]} for n in names]})
if role == "KS":
return " ".join(kw["docs"]) or "None"
return "El Extraño Viaje" if "Fernando Fernán Gómez" in kb else "unknown"
retrieve = lambda q, k: [v for n, v in CORPUS.items() if q.startswith(n)][:k]
rerank = lambda e, docs, k: docs[:k]
answer, kb, n = dualrag("Which film has the director who was born later, El Extraño Viaje or Love In Pawn?",
stub_llm, retrieve, rerank)
print(answer, n, list(kb))
実行すると El Extraño Viaje 3 ['Love In Pawn', 'El Extraño Viaje', 'Charles Saunders', 'Fernando Fernán Gómez'] と出力され、3回目の推論で検索不要と判定して終わります。1問あたりのLLM呼び出しは「Reasonerの実行回数+検索が必要と判定した回数+各反復でのSummarizer実行回数の合計+回答1回」で、このケースでは10回です。検索反復回数が少なくてもLLM呼び出しは直列に並ぶため、応答時間が増える要因になります。単発RAGとの時間差は、モデルや入出力長をそろえて測定する必要があります。
公式実装のライセンス・実行環境・要約破棄条件
- ライセンス:リポジトリにLICENSEファイルが無く、GitHub APIの
licenseもnullです(2026-10-06確認)。READMEは許諾と協業の問い合わせ先として著者のメールアドレスを載せています。手法を自前で実装するのは自由ですが、コードを流用する場合は許諾を取ってください。 - 実行環境:
pyproject.tomlはPython 3.10.16に固定され、検索はfaiss-gpu-cu12、LLMはvLLMで立てたQwen2.5をlitellm経由で呼ぶ構成です。CUDA 12のGPUが無い環境では、そのままでは動きません。 - 要約の破棄条件:公式実装は、Summarizerの出力に「None」か「none」が部分文字列として含まれると要約を捨てます。「nonetheless」や作品名に「None」を含む要約も捨てられるので、移植するときは上のコードのように完全一致で判定するほうが安全です。
IRCoT・FLARE・Self-RAG・GenGroundとの違い
反復型RAGは「検索のきっかけ」と「取得文書の扱い」で分けると違いが見えます。DualRAGの特徴は、検索のきっかけをLLM自身の知識不足の判定に置き、取得文書をエンティティ単位の要約に変換してから推論へ戻す点です。
| 手法 | 検索のきっかけ | クエリの作り方 | 取得文書の扱い |
|---|---|---|---|
| IRCoT | 推論の区切りごと(一定間隔) | 直前の推論文 | 文書をそのまま追加 |
| FLARE | 出力トークンの確率が低いとき | 生成途中の文 | 文書をそのまま追加 |
| Self-RAG | 学習で埋め込んだ特殊トークン | モデルが判断 | 文書ごとに関連性を自己評価 |
| GenGround | サブ質問ごと(上限まで反復) | 質問を分解したサブ質問 | サブ質問ごとに回答を接地 |
| MetaRAG | 仮回答の点検結果 | 仮回答の不足箇所 | 仮回答を修正 |
| DualRAG | Reasonerの知識不足判定 | エンティティ別の複数クエリ | エンティティ単位に要約して蓄積 |
検索の要否を判断する手法としては、DeepRAGとは?検索の要否を1問ずつ判断する仕組みと論文の実験結果【ICLR 2026】も同じ系統に入ります。DeepRAGは検索するかどうかの判断を学習で身に付けさせる点に重心があり、DualRAGは取得した知識の整理(pKA)まで含めて設計している点が異なります。
実験結果:精度・検索回数・Oracleとの比較
実験はHotpotQA・2WikiMultihopQA・MuSiQueから各1,000問を抽出して行われました。検索はbge-small-en-v1.5による埋め込みとfaissのベクトル索引、bge-reranker-v2-m3による再順位付けで、上位50件から10件に絞る設定を全手法に共通で使っています。反復型の手法は最大5回までです。
評価指標のうちAccは「正解文字列が出力に含まれていれば正解」とする緩い判定で、EMは完全一致、F1はトークン単位の一致度です。これとは別に、Qwen2.5-72B-Instructを判定役にしたLLM判定の正解率も載っています。LLMを評価者に使う方式の注意点はRAG評価とは?検索と生成を分けて測る指標・Ragasでの実装・運用への組み込み【2026年版】で整理しています。
Qwen2.5-72B-InstructでのAccとF1
| 手法 | HotpotQA Acc/F1 | 2Wiki Acc/F1 | MuSiQue Acc/F1 |
|---|---|---|---|
| NativeRAG | 54.3/60.3 | 46.5/48.1 | 30.2/33.6 |
| IRCoT | 69.7/67.4 | 83.6/67.5 | 51.0/48.1 |
| MetaRAG | 58.8/66.9 | 59.7/61.2 | 47.5/51.7 |
| GenGround | 64.8/61.8 | 78.1/70.3 | 53.0/43.0 |
| DualRAG | 70.0/65.7 | 85.0/77.3 | 66.3/56.3 |
| Oracle(参考) | 72.4/79.6 | 85.6/83.5 | 68.0/69.1 |
差が大きいのはMuSiQueで、AccはGenGroundの53.0に対して66.3です。HotpotQAではOracleを除く比較手法の中でAccが最高ですが、IRCoTとの差は0.3ポイントで、F1は67.4(IRCoT)と66.9(MetaRAG)を下回り、EMも49.7でMetaRAGの53.4に届いていません。「全データセット・全指標で最高」ではないので、自社の評価で比べるときは指標をそろえて見てください。
2WikiMultihopQAとMuSiQueには公式の検索コーパスが無く、論文は各データセットの根拠文と非根拠文を合わせて検索対象を作っています。評価用データセットから構成した文書集合であり、自社の文書量・ノイズ・根拠文書の有無が異なる環境に同じ精度を当てはめることはできません。
検索回数と出力トークン量
付録Table 11の平均検索反復回数は、1問あたりHotpotQA 1.64回、2WikiMultihopQA 1.99回、MuSiQue 2.20回でした。これは個々のクエリの実行回数ではありません。IRCoTは3.76〜4.26回、MetaRAGは2.93〜3.14回です。2WikiMultihopQAで反復の上限を2回に絞った条件でも、DualRAGのAccは83.5で、上限5回の85.0とほとんど変わりません。同じ条件でIRCoTは66.9まで落ちます。
出力トークンは1問平均598.1で、MetaRAGの597.9とほぼ同じ、GenGroundの862.2より少ない量です。内訳はReasoner 208.7、Entity Identifier 206.4、Summarizer 183.0でした。ただし表は出力トークンだけを数えています。DualRAGは知識アウトラインと推論履歴を毎回プロンプトに入れるので、API料金を見積もるなら入力トークンを自分で計測する必要があります。
7BモデルでのOracle超えと72Bモデルとの比較
要旨の「一部ではOracleを上回る」は、正解の根拠文書を直接渡す上限設定(Oracle)よりDualRAGの数値が高い場合を指します。72Bモデルでは、2WikiMultihopQAのAccが85.0でOracleの85.6に迫ったものの、上回った指標はありません。上回ったのはQwen2.5-7B-Instructのほうで、2WikiMultihopQAのAccはOracle 60.1に対してDualRAG 75.8、ファインチューニング版(DualRAG-FT)は82.0でした。
根拠文書を一度に渡されても、7Bモデルは複数の文書をつないだ推論を1回でやり切れないと読めます。DualRAGは1歩ずつ推論して要約を積むので、小さいモデルの推論の弱さを補う方向に効きます。また、Accは出力に正解が含まれれば正解とみなすため、長めに答える手法が有利になります。F1で比べると、7BのHotpotQAではOracle 67.6に対してDualRAG 58.6で、上回っていません。
DualRAGの構成部品を除いた場合のAcc変化
72Bモデルで部品を1つずつ外すと、MuSiQueのAccは66.3から、Reasonerの知識不足判定を外すと56.1、Entity Identifierを外すと54.2、Knowledge Outlineを外すと58.1に下がりました。HotpotQAでのAcc低下は0.5〜1.3ポイントで、MuSiQueより小さい結果でした。ただし、この比較だけでは推論の段数と効果の関係は断定できません。一方、リランカーを外した場合は2WikiMultihopQAのAccが86.3へ上がっており、リランカーは常に精度を上げるわけではありません。
小型モデル向けファインチューニング(DualRAG-FT)
DualRAG-FTは、72Bモデルで動かしたDualRAGの軌跡を教師データにして、7Bモデルの3つの役(Reasoner・Entity Identifier・Knowledge Summarizer)を学習させたものです。HotpotQA・2WikiMultihopQA・MuSiQueの訓練セットから5,000問分の軌跡を生成し、正解に至ったものだけを残して、計87,068件の学習データを作っています。
| 役割 | そのまま | 加工分 | 合計 |
|---|---|---|---|
| Reasoner | 33,342 | 0 | 33,342 |
| Entity Identifier | 4,951 | 17,158 | 22,109 |
| Knowledge Summarizer | 22,058 | 9,559 | 31,617 |
加工では、Entity Identifierの重複エンティティをクロスエンコーダーで統合し、Summarizerの要約から抜けた情報を正解文書と照合して補っています。学習はLLaMA-Factoryで、DeepSpeed ZeRO Stage 3による全パラメータ学習、学習率3e-6、NVIDIA A100 80GB×16台で約10時間です。結果、7Bモデルの2WikiMultihopQAはF1が64.4から74.6に上がりました。どの役を学習から外しても精度が落ちるわけではなく、Entity Identifierだけ未学習にした条件ではHotpotQAのAccが64.8から65.7へわずかに上がっています。同じ方式を自社データで再現するなら、72Bクラスの教師モデルを動かす計算資源と、正解付きの訓練問題が前提になります。
DualRAGを採用すべき場面・向かない場面
DualRAGが効くのは、答えにたどり着くまでに2〜4回の「名前を知ってから次を調べる」段がある質問です。社内文書なら「この障害の原因モジュールの担当チームが去年出した設計書はどれか」のように、製品・部署・文書番号をたどる問い合わせが当てはまります。エンティティ単位で知識を整理するので、固有名詞がはっきりしている文書群ほど相性が良くなります。
逆に、次の条件ではDualRAGを選ぶ理由が弱くなります。
- 1回の検索で答えが出る質問:単発質問のPopQAでは、F1がDualRAG 52.8に対し単純なRAG(NativeRAG)57.9で逆転しています。ただしPopQAのAccではNativeRAGを上回っており、FAQ用途では自社の正解率と呼び出しコストを比較して採否を決める必要があります。
- ELI5型の説明生成でRouge-Lを優先する用途:DualRAGは18.1でIRCoTの21.6を下回りました。ただしASQAではDualRAGが上回っており、長文生成全般に不向きという結果ではありません。
- 応答時間に厳しい対話:掲載スタブ例ではLLMを10回直列に呼びます。実際の呼び出し回数は反復数と処理するエンティティ数で変わるため、導入環境で応答時間を測定してください。
- 日本語の文書:実験は英語データセットと英語用の埋め込みモデル(bge-small-en-v1.5)だけで行われています。日本語で使うなら埋め込みモデルを差し替え、精度を自分で測り直す必要があります。選び方は埋め込みモデルとは?仕組みと日本語モデルの選び方・RAG実装での判断基準【2026年版】を参照してください。
失敗の内訳も把握しておくべきです。論文が誤答100件を手作業で分類したところ、コーパスに知識が無いケースを除いた誤りの75%は、取得文書から書かれていない結論を導く推論の誤り、25%は質問の意図の取り違えでした。DualRAGは検索の精度を上げますが、取得した文書を読み違える誤りは残ります。
よくある質問
DualRAGのRaQとは何ですか?
RaQ(Reasoning-augmented Querying)は、推論を1歩ずつ進めながら知識が足りない箇所を見つけ、エンティティごとの検索クエリを作る処理です。ReasonerとEntity Identifierの2つの役で構成されます。
DualRAGのpKAとは何ですか?
pKA(progressive Knowledge Aggregation)は、検索で集めた文書をエンティティごとに要約し、知識アウトラインに追記していく処理です。次の反復ではRaQがこのアウトラインを読んで推論を続けます。
DualRAGの公式コードは商用で使えますか?
公式リポジトリ(cbxgss/rag)にはライセンス表記がありません(2026-10-06時点)。ライセンスが無いコードは利用許諾が与えられていない扱いになるので、流用する場合は著者に許諾を取る必要があります。論文に書かれた手法を自前で実装することは可能です。
DualRAGとDeepRAGの違いは何ですか?
どちらも検索の要否を判断する反復型RAGです。DeepRAGは検索するかどうかの判断を学習させる点に重心があり、DualRAGは検索の判断に加えて、取得した知識をエンティティ単位に整理する仕組み(pKA)を持つ点が違います。
DualRAGは日本語の文書でも使えますか?
手法は言語に依存しませんが、論文の実験は英語データだけで、埋め込みモデルも英語用のbge-small-en-v1.5です。日本語で使う場合は埋め込みモデルとプロンプトを日本語向けに差し替え、自社の質問で精度を測り直してください。