🧠

RAGなしで始めるナレッジグラフQA——コンテナで再現する比較検証

に公開

RAG なしで始めるナレッジグラフ QA——コンテナで再現する比較検証

ねらい
「RAG なし」= RAG に含まれるベクトル検索部分を実装せず、ナレッジグラフ単独で論理的に答える最小構成を Docker だけで動かして確認する。差分・否定・経路・カウントなど、ベクトル検索が失敗しやすい質問で、KG(Cypher/SPARQL)が正確に答えられることを手元で再現する。


重要な注釈:RAG と KG は対立ではなく補完関係

本記事のタイトルは「RAG なし」ですが、これは「RAG を否定する」という意味ではありません。むしろ、KG と RAG の役割分担を理解するための比較実験 です。

実務では、KG と RAG は併用されます:

  • RAG の役割:大量の非構造化テキスト(ドキュメント、記事、ログ)から関連情報を素早く検索し、文脈を提供する
  • KG の役割:構造化された知識(エンティティ・関係・ルール)から論理的に推論し、正確な回答を導く

本記事では、KG が得意とする領域を明確にするために、敢えて KG 単独での動作を検証 しています。実装ではベクトル DB(Qdrant)も起動していますが、意図的に LLM を使わずに、データストアの本質的な特性の違い を浮き彫りにしています。

設計的な重要なポイント:なぜ LLM を使わないのか

この実験では、意図的に LLM を省いています。パイプラインは以下の通り:

  • KG: 質問 → Cypher(手書き) → グラフ実行 → 回答
  • ベクトル検索: 質問 → ベクトル埋め込み → テキスト検索 → キーワード抽出 → 回答

現実のシステムでは、ベクトル検索の後ろに LLM(ChatGPT など)がついて自然言語で回答を生成します(これが RAG)。しかし、その LLM でも データストアの根本的な特性(テキスト検索の曖昧性)は超えられません。詳しくは「RAG を超える知識統合」の5 つの意味質問型を参照してください。

エンジニアリング・リソースの長期的視点

RAG は「プロンプト調整」「リランキング」などで精度を上げられますが、継続的な調整が必要です。一方、KG は構造確定後は安定します:

観点 RAG KG
embedding モデル進化時 全データ再処理・再チューニング 影響なし
LLM 変更時 プロンプト再調整必須 影響なし
スケーリング(規模増大時) ベクトル検索の計算コスト爆増 Cypher クエリで線形時間実行

大規模運用での実例:Wikidata(8,800 万エンティティ)のような規模では、RAG のベクトル検索は実用的でなく、KG は高速に応答可能です。

実務的な結論:初期段階では RAG が低コストで有効。しかし、長期的・大規模運用を視野に入れると、KG への投資が ROI が高い。理想は「論理的クエリが必要なら KG、テキスト探索が必要なら RAG を補助」という役割分担です。


1. 何を用意するか(ローカルのみ)

  • macOS(または Linux/WSL2)
  • Docker / Docker Compose v2
  • curl(動作確認)
  • 使うもの:
    • Neo4j 5(Property Graph, Cypher)
    • Qdrant(ベクトル検索用のベクトル DB:比較対象。ベクトル検索は"失敗例"として参照)

この記事は ベクトル検索を"使わない"実装が主役ですが、比較のため最小のベクトル検索ベースラインも同時起動します。


2. 実験環境の構成

本実験は Docker コンテナで動作し、以下が起動します:

  • Neo4j 5 — ナレッジグラフ(Property Graph)
  • Qdrant — ベクトル DB(ベクトル検索のベースライン)
  • FastAPI — 比較用 API

詳細なセットアップ手順、試験データセット(5 項目版・50 項目版)、試験質問については、GitHub の実装ガイドを参照してください。

実装ガイド: experiments/kg-no-rag/README.md

このガイドには以下が含まれます:

  • グラフ構造と試験データの詳細説明
  • 5 つの試験質問(集合・差分・経路・否定・交差)
  • クイックスタートコマンド
  • API エンドポイントの使い方
  • 50 項目データセットによるスケール依存性の実証

3. 5 つの意味質問型——KG が得意、ベクトル検索が失敗しやすい質問パターン

本実験で検証する 5 つの質問タイプを具体例で示します。

Q1-集合(Union) — 「A または B に該当するものは?」

// A または B に該当する人物を検索
Q: "ソフトウェアエンジニアまたはデータサイエンティストは誰ですか?"

KG (Cypher):
  MATCH (p:Person)-[:hasRole]->(r:Role)
  WHERE r.name IN ['SoftwareEngineer', 'DataScientist']
  RETURN p.name

ベクトル検索:
  → ベクトル検索で "エンジニア" "データサイエンティスト" を検索
  → ノイズが多い場合、関連ドキュメントが多すぎて曖昧

Q2-差分(Set Difference) — 「A に属するが B には属さない者は?」

// A に属するが B には属さない人物を検索
Q: "プロジェクトXに参加しているがプロジェクトYには参加していない人は?"

KG (Cypher):
  MATCH (p:Person)-[:participates]->(px:Project {name: 'X'})
  WHERE NOT EXISTS { (p)-[:participates]->(:Project {name: 'Y'}) }
  RETURN p.name

ベクトル検索:
  → 「プロジェクトX」と「プロジェクトY」の差を推論できない
  → テキスト検索では否定条件を正確に表現不可

Q3-経路(Path Traversal) — 「A から B へのつながりは?」

// A から B への経路を辿る
Q: "太郎がプロジェクトXを通じて、どの組織につながっているか?"

KG (Cypher):
  MATCH path = (p:Person {name: '太郎'})-[:participates]->(:Project {name: 'X'})-[:belongsTo]->(org:Organization)
  RETURN org.name, path

ベクトル検索:
  → 多段階のグラフ構造を追跡できない
  → テキスト検索は "点" 単位であり、"経路" を辿れない

Q4-否定(Negation) — 「A ではない条件を満たすのは?」

// A ではない条件を満たす人物を検索
Q: "東京オフィスに配属されていない従業員は?"

KG (Cypher):
  MATCH (emp:Employee)
  WHERE NOT EXISTS { (emp)-[:assignedTo]->(:Office {location: 'Tokyo'}) }
  RETURN emp.name

ベクトル検索:
  → 「否定」という論理演算子をテキストから推論困難
  → ベクトル検索は類似度で判定するため、"ない" という条件が曖昧

Q5-交差(Intersection) — 「A かつ B の両方に属する者は?」

// A かつ B の両方の条件を満たす人物を検索
Q: "プロジェクトXにも参加し、かつ管理職でもある人は?"

KG (Cypher):
  MATCH (p:Person)-[:participates]->(:Project {name: 'X'})
  MATCH (p)-[:hasRole]->(r:Role {type: 'Manager'})
  RETURN p.name

ベクトル検索:
  → 複数条件の交差をベクトル検索では不正確
  → スケール増加時にノイズが増えると精度が急落

結果の見え方

質問型 KG ベクトル検索(小規模) ベクトル検索(大規模) 理由
Q1-集合 ✅ 正確 ⚠️ 変動 ❌ ノイズで失敗 OR は明確だが、ノイズに弱い
Q2-差分 ✅ 正確 ⚠️ 変動 ⚠️ 変動 論理的な差分(A かつ NOT B)はベクトル検索に不向き
Q3-経路 ✅ 正確 ❌ 失敗 ❌ 失敗 多段階経路をベクトル検索で追跡不可
Q4-否定 ✅ 正確 ❌ 失敗 ❌ 失敗 論理的否定が曖昧
Q5-交差 ✅ 正確 ⚠️ 変動 ❌ 失敗 複数条件の厳密な AND が難しい

4. 実行結果の概要

ローカルで実行すると、以下の結果が得られます。

5 項目版

{
  "summary": {
    "kg_correct": 5,
    "kg_total": 5,
    "rag_correct": 2,
    "rag_total": 5
  }
}

解釈: KG は全問正解。RAG は集合と差分のみ正解(2/5)。

⚠️ 注釈:以下は手元で複数回実行した際の参考値です。RAG(ベクトル検索)の結果は実行ごとに変動します。この実例では Q1-集合と Q2-差分のみ成功しましたが、実行環境や embedding の初期値によって異なる結果が得られる可能性があります。

小規模データ(5 項目版)の結果だけでなく、規模を増やした 50 項目版でも傾向は変わりませんでした。

50 項目版

{
  "summary": {
    "kg_correct": 5,
    "kg_total": 5,
    "rag_correct": 1,
    "rag_total": 5
  }
}

解釈: KG は相変わらず全問正解。RAG は大幅に精度低下(ノイズが増えると精度が急落)。

⚠️ 注釈:以下は手元で複数回実行した際の参考値です。RAG(ベクトル検索)の結果は実行ごとに変動します。この実例では 1/5 の成功率でしたが、一般的には 0 ~ 1/5 の範囲で変動します。ベクトル空間における類似度計算の不確定性により、実行環境や初期条件によって結果が大きく異なります。


5. 結果の解釈

実験結果はすべての試験で一貫して同様の傾向を示しました。特に Q3(経路)と Q4(否定)は全試行でベクトル検索が失敗し、KG は常に正確な結果を返しました。
以下の表はその傾向を簡潔にまとめたものです。

質問型 傾向
Q1-集合・Q2-差分・Q5-交差 ベクトル検索は部分的に成功するが精度が不安定
Q3-経路・Q4-否定 ベクトル検索は全試行で失敗、KG は常に正確

KG の強み

  1. スケール不変性 — データが 5 件でも 50 件でも、Cypher クエリの結果は同じ
  2. 論理厳密性 — 集合、差分、否定、経路追跡を正確に実行
  3. 構造理解 — ドメイン知識をグラフ構造として記述すれば、複雑な関係も明確に表現可能

RAG の限界

  1. スケール依存性 — データが増えるとベクトル検索のノイズが増加
  2. 操作的な問い — 差分、否定、カウント、経路追跡は得意でない
  3. 曖昧性 — テキストが増えるほど意図を正確に拾いづらくなる

6. さらに学ぶために

理論的背景

本記事で実装した内容の理論的背景やアーキテクチャ的な位置づけについては、以下の記事をご覧ください:

技術資料

実装ガイドを見る(GitHub)


参考文献


更新履歴

  • 2025-10-24 — 初版公開
  • 2025-10-30 — 参考文献を追記

※本記事は AI を活用して執筆しています。

GitHubで編集を提案

Discussion