Skip to content
Published on

GraphRAG完全ガイド:知識グラフがRAGの限界をいかに超えるか

Authors

標準RAGが失敗する場面

ベクター検索ベースのRAGを運用していると、特定の質問パターンで一貫して失敗することに気づきます。

標準RAGが壊れる質問:

  • 「この四半期のレポート全体から、主なリスク要因を要約して」
  • 「製品レビューで繰り返し現れる不満のパターンは?」
  • 「CEO交代後の会社戦略の変化を説明して」
  • 「製品Aと製品Bの共通する欠陥の特徴は?」

なぜ失敗するのでしょうか?これらの質問に共通するのは、単一のチャンクでは答えられず、ナレッジベース全体を横断する理解が必要という点です。

標準RAGの仕組みを考えると:

  1. 質問をエンベッド
  2. コサイン類似度で最も関連性の高いチャンクを3〜5件取得
  3. そのチャンクから回答を生成

「これらのレポート全体の主要なリスク要因は?」と聞くと、RAGは1つのレポートの1セクションを取得するだけで、全文書にまたがるパターンを見ることができません。これが根本的な限界です。


GraphRAGとは?(Microsoft Research、2024)

Microsoft Researchが2024年に発表した論文「From Local to Global: A GraphRAG Approach to Query-Focused Summarization」(Edge et al., 2024)で紹介された手法です。

コアアイデアはシンプルです:単にテキストをチャンクに分割するのではなく、文書からエンティティと関係を抽出して知識グラフを構築し、その上で検索する。

標準RAG文書 → チャンク分割 → エンベッド → ベクターDB → 類似度検索 → 関連チャンク

GraphRAG:
文書 → エンティティ抽出 → 知識グラフ構築 → コミュニティ検出 →
     → 階層的サマリー生成 → 多段階検索(ローカル + グローバル)

コア構造の理解

1. エンティティと関係の抽出

GraphRAGはLLMを使って文書からエンティティ(人物、組織、場所、概念)とその関係を抽出します。

入力テキスト:
「サムスンは2024年にGalaxy S25を発売し、AppleのiPhone 16と直接競合しています。
Galaxy S25はQualcomm Snapdragon 8 Eliteチップを搭載しています。」

抽出されるエンティティ:
- サムスン(組織)
- Galaxy S25(製品)
- Apple(組織)
- iPhone 16(製品)
- Qualcomm(組織)
- Snapdragon 8 Elite(製品/技術)

抽出される関係:
- サムスン --[発売]--> Galaxy S25
- Galaxy S25 --[競合]--> iPhone 16
- Galaxy S25 --[搭載]--> Snapdragon 8 Elite
- Qualcomm --[製造]--> Snapdragon 8 Elite

2. コミュニティ検出

エンティティグラフにLeidenアルゴリズム等を適用し、密接に連結されたエンティティのグループ(コミュニティ)を見つけます。

例:スマートフォン市場コミュニティ、半導体コミュニティ、ソフトウェアエコシステムコミュニティ

3. 階層的サマリー

各コミュニティに対して複数の粒度レベルでサマリーを生成します:

Level 0(最詳細):個別エンティティ/関係のサマリー
Level 1:小規模コミュニティのサマリー
Level 2:中規模コミュニティのサマリー(最もよく使われる)
Level 3(最包括的):グローバルトピックのサマリー

コードで見るGraphRAG

Microsoftの公式graphragライブラリを使います。

# インストール
pip install graphrag

# プロジェクト初期化
mkdir my-graphrag-project
graphrag init --root ./my-graphrag-project

生成されるsettings.yamlの主要設定:

# settings.yamlの主要設定
llm:
  api_key: ${GRAPHRAG_API_KEY}
  type: openai_chat
  model: gpt-4o-mini  # インデックス作成用(コスト削減のためmini使用)
  model_supports_json: true

embeddings:
  llm:
    model: text-embedding-3-small

input:
  type: file
  file_type: text
  base_dir: "input"  # ここにドキュメントを配置

chunks:
  size: 1200
  overlap: 100
# インデックス作成(知識グラフの構築)
# graphrag index --root ./my-graphrag-project
# 内部的に行われること:
# 1. ドキュメントのチャンク分割
# 2. エンティティと関係の抽出(LLMの呼び出し)
# 3. グラフの構築
# 4. コミュニティ検出の実行
# 5. 各コミュニティのサマリー生成(LLMの呼び出し)

# Python APIでのクエリ
import asyncio
from graphrag.query.api import local_search, global_search

# ローカル検索:特定エンティティ/関係中心の質問
async def search_local(query: str):
    result = await local_search(
        config_dir="./my-graphrag-project",
        data_dir="./my-graphrag-project/output",
        root_dir="./my-graphrag-project",
        community_level=2,
        response_type="multiple paragraphs",
        query=query,
    )
    return result.response

# グローバル検索:ナレッジベース全体の合成が必要な質問
async def search_global(query: str):
    result = await global_search(
        config_dir="./my-graphrag-project",
        data_dir="./my-graphrag-project/output",
        root_dir="./my-graphrag-project",
        community_level=2,
        response_type="multiple paragraphs",
        query=query,
    )
    return result.response

# 使用例
local_result = asyncio.run(search_local(
    "サムスンとTSMCの関係はどうなっていますか?"
))

global_result = asyncio.run(search_global(
    "これらの文書における半導体産業の主なトレンドは何ですか?"
))

ローカル vs グローバル検索:使い分け

2つのクエリモードを理解することがGraphRAG活用の鍵です。

クエリタイプ最適モード
特定エンティティへの質問ローカル検索「田中さんの役割は?」
2エンティティの関係ローカル検索「サムスンとTSMCの関係は?」
全体的なトレンド把握グローバル検索「この文書群の主なテーマは?」
文書横断のパターン分析グローバル検索「業界全体のリスク要因は?」
時系列の変化両方を活用「5年間の戦略変化は?」

ローカル検索の内部処理:

  1. クエリから関連エンティティを特定
  2. そのエンティティに連結したテキストチャンク、関係、コミュニティサマリーを収集
  3. LLMで最終回答を生成

グローバル検索の内部処理:

  1. 全コミュニティサマリーを複数の「バッチ」に分割
  2. 各バッチに対して部分回答を生成(Mapフェーズ)
  3. 部分回答を統合して最終回答を生成(Reduceフェーズ)

知識グラフを直接見る

GraphRAGが生成したグラフを可視化することで洞察が得られます。

import pandas as pd
import networkx as nx

# GraphRAG出力ファイルからエンティティと関係をロード
entities_df = pd.read_parquet("./output/entities.parquet")
relationships_df = pd.read_parquet("./output/relationships.parquet")

print(f"総エンティティ数:{len(entities_df)}")
print(f"総関係数:{len(relationships_df)}")

# NetworkXグラフの構築
G = nx.DiGraph()

for _, entity in entities_df.iterrows():
    G.add_node(entity["title"], type=entity["type"])

for _, rel in relationships_df.iterrows():
    G.add_edge(
        rel["source"],
        rel["target"],
        weight=rel["weight"],
        description=rel["description"]
    )

# 最も連結度の高いエンティティ(ハブ)を探す
top_hubs = sorted(G.degree(), key=lambda x: x[1], reverse=True)[:10]
print("最も重要なエンティティ Top 10:")
for entity, degree in top_hubs:
    print(f"  {entity}{degree}個の接続")

GraphRAG vs 標準RAG:性能比較

Microsoftの原論文より(HotPotQA、MuSiQueデータセット):

グローバルクエリ(全体要約が必要):
- 標準RAG:  網羅性40%、多様性57%
- GraphRAG:  網羅性72%、多様性62%
  → 網羅性が80%向上!

ローカルクエリ(特定事実の検索):
- 標準RAG:  精度 約65%
- GraphRAG:  精度 約70%
  → わずかな改善

速度:
- 標準RAG0.52- GraphRAG:  310秒(コミュニティサマリーの集約のため)

結論:グローバルクエリではGraphRAGが圧倒的に優れますが、特定の事実検索では標準RAGと同程度か遅くなります。


コストの現実:率直な計算

GraphRAGの最大の欠点はインデックス作成コストです。

# GraphRAGインデックスコストの見積もり
# 前提:1,000件のドキュメント、各1,000トークン

num_documents = 1000
tokens_per_doc = 1000
total_tokens = num_documents * tokens_per_doc  # 100万トークン

# インデックス作成でのLLM呼び出し回数の見積もり
entity_extraction_calls = total_tokens / 1200  # チャンクごとに1回
# 各呼び出し:約800入力トークン + 約400出力トークン

community_summary_calls = 200  # コミュニティ数 × レベル
# 各呼び出し:約2,000入力トークン + 約500出力トークン

# GPT-4o-mini基準($0.15/1M入力、$0.60/1M出力)
entity_input_cost = (entity_extraction_calls * 800 / 1_000_000) * 0.15
entity_output_cost = (entity_extraction_calls * 400 / 1_000_000) * 0.60
community_cost = (community_summary_calls * 2500 / 1_000_000) * 0.40

total_indexing_cost = entity_input_cost + entity_output_cost + community_cost
print(f"推定インデックスコスト:${total_indexing_cost:.2f}")
# 1,000件のドキュメント:約$1〜5(GPT-4o-mini使用時)
# 10,000件のドキュメント:約$10〜50
# 100,000件のドキュメント:約$100〜500

# クエリコスト(グローバル検索)
# グローバル検索は各クエリで全コミュニティサマリーを処理
# 200コミュニティ × 500トークン = クエリあたり約10万トークン処理
global_query_cost = (100_000 / 1_000_000) * 2.50  # GPT-4o価格
print(f"グローバル検索のクエリあたりコスト:${global_query_cost:.3f}")  # $0.25/クエリ

この費用が許容できるかの判断基準:

  • 初期インデックス作成:文書が頻繁に変更されない場合は一回限りのコスト
  • クエリコスト:グローバル検索は標準RAGの10〜100倍高くなることがある

GraphRAGが適切な場合 vs 標準RAGで十分な場合

GraphRAGが価値を発揮する場合:

  • 数百〜数千件の文書にまたがるパターン/トレンド分析
  • 財務報告書、特許、法律文書の分析
  • ナレッジベースがほぼ変更されない場合(インデックスコストが一回限り)
  • 「この会社について何を知っているか?」のような広範な質問が多い場合

標準RAGで十分な場合:

  • 特定の事実検索(「この契約書の有効期限は?」)
  • 高速レスポンスが必要なリアルタイムサービス
  • 文書が頻繁に更新される場合(再インデックスのコスト)
  • 小規模ナレッジベース(100件未満)
  • コスト予算が厳しい場合

LightRAG:より実用的な代替案

MicrosoftのGraphRAGが複雑すぎるか費用が高いと感じるなら、LightRAGを検討してください。

# pip install lightrag-hku
from lightrag import LightRAG, QueryParam
from lightrag.llm import gpt_4o_mini_complete

rag = LightRAG(
    working_dir="./lightrag-storage",
    llm_model_func=gpt_4o_mini_complete,
)

# ドキュメントの追加
with open("document.txt", "r") as f:
    rag.insert(f.read())

# クエリ(naive、local、global、hybridの4モード)
result = rag.query(
    "主なトレンドは何ですか?",
    param=QueryParam(mode="global")  # hybridモードも効果的
)

LightRAGは実装がシンプルで費用も安く、小〜中規模のナレッジベースでは十分な性能を発揮します。


まとめ:GraphRAGはツールであり、全ての答えではない

GraphRAGは強力ですが、全ての状況に適しているわけではありません。

重要なポイント:

  • グローバルクエリでは標準RAGを大きく上回る
  • インデックスコストが高いため、ナレッジベースが安定している場合に適している
  • クエリコストも標準RAGより高い
  • 手軽に始めるにはMicrosoftのgraphragライブラリまたはLightRAGを活用

投資対効果を考えましょう。「このコーパス全体のパターンを理解する必要がある」という明確な要件がある時にGraphRAGを導入し、特定の事実検索が主な用途なら、よく調整された標準RAGの方がコスト効率が良いです。