6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI for Science の基盤技術 #1:GraphRAG:Microsoft GraphRAG ソースコード完全ガイド

6
Last updated at Posted at 2025-12-18

はじめに

本記事の目的

本記事では、Microsoft が公開している GraphRAGのソースコード を詳細に解説します。GraphRAG は、従来の RAG(Retrieval-Augmented Generation)の限界を克服するために設計された、知識グラフベース の新しい情報検索・生成手法です。

特に AI for Science(科学研究のための AI 活用)の文脈において、なぜ単純なベクトル検索ベースの RAG では不十分なのか、そして GraphRAG がどのようにその課題を解決するのかを、実装レベルで理解することを目指します。

本記事を通じて、読者は以下を習得できます。

  • GraphRAG の設計思想とアーキテクチャ
  • 知識グラフの構築プロセス(エンティティ抽出、関係性抽出、コミュニティ検出)
  • Local Search / Global Search / DRIFT Search の仕組み
  • 自身の研究データへの応用方法

対象読者

  • 大学・研究機関の研究者:論文・実験データの知識統合に興味がある方
  • AI/ML エンジニア:RAG システムの改善を検討している方
  • データサイエンティスト:大規模テキストデータからの知識抽出に取り組む方
  • LLM アプリケーション開発者:より高度な文脈理解を実現したい方

前提知識:Python 基礎、RAG の基本概念、LLM の基礎知識


第1章 なぜ AI for Science に GraphRAG が必要なのか

「科学とは、事実の集積ではない。事実間の関係性の発見である。」
— アンリ・ポアンカレ

AI for Science(科学研究への AI 活用)が急速に進展する中、研究者は膨大な論文、実験データ、データベースから知識を抽出し、統合する必要に迫られています。しかし、現在主流の RAG アプローチには根本的な限界があります。

本章では、なぜ従来の RAG では科学研究の知識発見に不十分なのか、そして GraphRAG がどのようにその課題を解決するのかを明らかにします。

1.1 科学研究における知識の本質

1.1.1 科学知識は「つながり」でできている

科学における発見の多くは、一見無関係に見える事実の間に新しい「つながり」を見出すことから生まれます。

例えば、

  • 薬物相互作用の発見:タンパク質 A と化合物 B の関係、化合物 B と疾患 C の関係を統合して、新たな治療標的を発見
  • 気候変動研究:海水温、大気成分、生態系の変化を関連付けて、複合的な影響を予測
  • 材料科学:結晶構造、製造プロセス、物性の関係から新材料を設計

これらはすべて、エンティティ(実体)リレーション(関係) のネットワークとして表現できます。

1.1.2 論文・データベース・実験結果の相互関係

現代の科学研究では、以下のような多様なデータソースを横断的に活用する必要があります。

データソース 含まれる知識
学術論文 PubMed、arXiv 研究成果、手法、考察
データベース UniProt、ChEMBL 構造化された事実データ
実験データ ラボノート、計測結果 生の観測値
特許文献 USPTO、EPO 応用・実用化情報

これらのデータは相互に参照し合い、複雑な知識ネットワークを形成しています。

1.1.3 知識発見は関係性の発見である

科学における重要な問いの多くは、関係性 を問うものです:

  • 「この遺伝子変異は、どの疾患と関連しているか?」
  • 「この触媒は、どのような反応条件で最も効率的か?」
  • 「このデータセット全体から見えてくる主要なトレンドは何か?」

これらの問いに答えるには、個々の事実を知るだけでなく、事実間のつながりを理解する 必要があります。

1.2 従来の RAG では何が足りないのか

1.2.1 ベクトル検索の限界:類似性 ≠ 関連性

従来の RAG(Baseline RAG)は、以下のプロセスで動作します。

[クエリ] → [ベクトル化] → [類似度検索] → [Top-K 文書取得] → [LLM に渡して回答生成]

この方式の根本的な問題は、「ベクトル空間での近さ」と「意味的な関連性」は必ずしも一致しない ことです。

例:薬物相互作用の調査

クエリ:「アスピリンと抗凝固薬の相互作用について教えて」

検索結果 ベクトル類似度 実際の関連性
アスピリンの一般的な効能説明 高い 低い(相互作用情報なし)
ワルファリンの投与量ガイドライン 中程度 高い(併用禁忌情報あり)
抗凝固療法の歴史 高い 低い(学術史のみ)

ベクトル検索は「アスピリン」「抗凝固」という単語に反応しますが、両者の関係性を明示的に持つ文書 を優先的に取得できません。

1.2.2 「木を見て森を見ず」問題

Baseline RAG は、クエリに類似した 局所的な テキストチャンクを取得します。しかし、以下のような 全体像を問う質問 には対応困難です。

  • 「このデータセットの主要なテーマは何か?」
  • 「この分野の研究トレンドはどう変化しているか?」
  • 「異なるアプローチ間の共通点と相違点は?」

Microsoft Research の検証結果 によると、Baseline RAG は「データセット全体のテーマを理解する」「複数の情報源を横断して推論する」タスクで、著しくパフォーマンスが低下します。

1.2.3 異分野間の知識統合ができない

科学研究のブレークスルーは、しばしば 異分野の知識の組み合わせ から生まれます。

しかし Baseline RAG では、

  • 異なる分野の文書が 異なるベクトル空間領域 に配置される
  • クエリは通常 一つの分野の言葉 で表現される
  • 結果として 異分野の関連情報が検索されにくい

例えば、「機械学習を用いた創薬」について調べたい場合、機械学習論文と創薬論文は別々のクラスタに存在し、両者を橋渡しする情報が取得されにくくなります。

1.2.4 仮説生成・推論に弱い

科学研究では、既知の事実から 新しい仮説を生成する ことが重要です。

Baseline RAG は、

  • 既存文書の 引用・要約 は得意
  • 文書に 明示的に書かれていない推論 は苦手
  • 「A→B、B→C ならば A→C かもしれない」という 推移的推論 ができない

1.3 GraphRAG が解決すること

1.3.1 知識グラフによる関係性の明示的表現

GraphRAG は、テキストから 知識グラフ を構築します。

[生テキスト] → [LLM によるエンティティ・関係抽出] → [知識グラフ]

この構造により、

  • 関係性が明示的 に保存される
  • 推移的な関係 を辿れる(A→B→C)
  • 異なるエンティティ間のパス を発見できる

1.3.2 コミュニティ検出で「森」を俯瞰する

GraphRAG は Leiden アルゴリズム を用いて、グラフをコミュニティ(クラスタ)に分割します。

[知識グラフ] → [コミュニティ検出] → [階層的コミュニティ構造] → [各コミュニティの要約生成]

これにより、

  • データセット全体のテーマ を把握できる(Global Search)
  • 異なる粒度 での理解が可能(コミュニティ階層)
  • 「木を見て森を見ず」問題 を解決

1.3.3 階層的要約による多視点分析

各コミュニティに対して、LLM が 要約レポート を生成します:

レベル 粒度 内容例
Level 0 最小 個別エンティティの詳細
Level 1 サブトピックの要約
Level 2 主要テーマの概要

クエリの性質に応じて、適切なレベルの要約を活用できます。

1.3.4 AI for Science への応用シナリオ

GraphRAG は、以下のような科学研究シナリオで威力を発揮します。

シナリオ 従来 RAG GraphRAG
文献レビュー 関連論文の羅列 テーマ別に構造化された概要
仮説生成 既知情報の再提示 エンティティ間の未知の関係発見
異分野統合 単一分野に偏る 分野横断的なつながりを可視化
データ全体の理解 断片的 階層的・体系的

1.4 RAG vs GraphRAG 比較

1.4.1 アーキテクチャの違い

項目 Baseline RAG GraphRAG
インデックス ベクトル埋め込み 知識グラフ + コミュニティ + ベクトル
検索方式 Top-K 類似度検索 グラフ探索 + コミュニティ活用
コンテキスト 独立したチャンク 関係性を含む構造化情報
前処理コスト 低い 高い(LLM による抽出)

1.4.2 得意なクエリタイプの違い

クエリタイプ Baseline RAG GraphRAG
事実確認「X とは何か」
関係性「X と Y の関係は」
全体像「主要テーマは」
比較「A と B の違いは」
推論「A なら B では?」

1.4.3 科学研究での使い分け

Baseline RAG が適するケース:

  • 特定の事実を素早く確認したい
  • 文書量が少ない
  • コスト制約が厳しい

GraphRAG が適するケース:

  • 大規模文書コーパスを扱う
  • 全体像の把握が必要
  • 異分野・複数ソースの統合が必要
  • 新しい仮説・洞察を得たい

次章では、GraphRAG の基本概念(知識グラフ、コミュニティ検出)をより詳しく解説します。

第2章 GraphRAG の基本概念

GraphRAG を理解するには、まず「知識グラフ」と「コミュニティ検出」という2つの重要な概念を押さえる必要があります。本章では、これらの基礎概念と、GraphRAG がどのようにそれらを活用しているかを解説します。

2.1 知識グラフとは

2.1.1 エンティティとリレーション

知識グラフは、エンティティ(Entity)リレーション(Relationship) から構成されるグラフ構造のデータモデルです。

エンティティ は、現実世界における識別可能な対象を表します:

  • 人物:研究者、著者、発明家
  • 場所:都市、研究機関、国
  • 概念:理論、手法、疾患
  • 物体:化合物、タンパク質、機器

GraphRAG におけるエンティティは、以下の属性を持ちます。

@dataclass
class Entity(Named):
    type: str | None = None           # エンティティの種類(PERSON, PLACE等)
    description: str | None = None     # エンティティの説明文
    description_embedding: list[float] | None = None  # 説明文のベクトル埋め込み
    community_ids: list[str] | None = None  # 所属するコミュニティ
    text_unit_ids: list[str] | None = None  # 出現するテキストチャンク
    rank: int | None = 1              # 重要度ランク(次数中心性等に基づく)

リレーション は、エンティティ間の関係を表します。

@dataclass
class Relationship(Identified):
    source: str          # 関係の起点となるエンティティ
    target: str          # 関係の終点となるエンティティ
    weight: float | None = 1.0        # 関係の強さ(エッジの重み)
    description: str | None = None     # 関係の説明文
    text_unit_ids: list[str] | None = None  # 出現するテキストチャンク

2.1.2 グラフ構造の表現力

知識グラフがベクトル検索と比較して優れている点は、関係性を明示的に保持できる ことです。

ベクトル空間での表現:

Einstein → [0.23, -0.45, 0.67, ...]  # 高次元空間の1点
Relativity → [0.21, -0.42, 0.71, ...]  # 近い点だが関係性は不明確

グラフでの表現:

Einstein --[developed]--> Relativity  # 関係が明示的
         --[worked_at]--> Princeton
         --[influenced]--> Bohr

この明示的な関係性により、以下のことが可能になります。

機能 説明
パス探索 エンティティ間の経路を発見 A→B→C の関係チェーン
推移的推論 間接的な関係を推論 A が B に影響、B が C に影響 → A は C に影響?
サブグラフ抽出 関連する情報のまとまりを取得 ある人物に関連するすべての概念
中心性分析 重要なエンティティを特定 多くの関係を持つハブとなるエンティティ

2.2 GraphRAG のアプローチ

2.2.1 知識グラフ × LLM の融合

GraphRAG の革新的な点は、LLM を使って非構造化テキストから知識グラフを自動構築する ことです。

従来の知識グラフ構築には、専門家による手動のアノテーションや、ルールベースの情報抽出が必要でした。GraphRAG では、LLM の言語理解能力を活用して、このプロセスを自動化します。

GraphRAG Knowledge Model の構成要素:

モデル 説明 用途
Document 入力文書(CSV の行、.txt ファイル) 元データへの参照
TextUnit 分析単位のテキストチャンク(デフォルト300トークン) エンティティ抽出の単位
Entity 抽出されたエンティティ(人物、場所、概念等) グラフのノード
Relationship エンティティ間の関係 グラフのエッジ
Covariate 時間制約付きの主張・事実(オプション) 詳細な事実情報
Community Leiden アルゴリズムで検出されたクラスタ 階層的なグループ化
CommunityReport 各コミュニティの LLM 生成サマリー Global Search で活用

2.2.2 コミュニティ検出による階層構造

GraphRAG の最も重要な特徴の一つが、階層的コミュニティ検出 です。これにより、グラフを異なる粒度で理解できます。

Leiden アルゴリズム は、グラフをモジュラリティ(同じコミュニティ内のエッジ密度)を最大化するようにクラスタリングします。GraphRAG では、これを再帰的に適用して階層構造を構築します。

Level 2: [全体のテーマ]
    ├── Level 1: [サブテーマ A]
    │   ├── Level 0: [詳細トピック A-1]
    │   └── Level 0: [詳細トピック A-2]
    └── Level 1: [サブテーマ B]
        ├── Level 0: [詳細トピック B-1]
        └── Level 0: [詳細トピック B-2]

コミュニティの階層レベル:

レベル 粒度 内容 使用場面
Level 0 最小 密接に関連するエンティティの小グループ 詳細な局所的質問
Level 1 関連するサブトピックのまとまり 中程度の範囲の質問
Level 2+ データセット全体の主要テーマ 全体像を問う質問

各コミュニティに対して、LLM が コミュニティレポート を生成します。

@dataclass
class CommunityReport(Named):
    community_id: str           # 対応するコミュニティの ID
    summary: str = ""           # レポートの要約
    full_content: str = ""      # レポートの全文
    rank: float | None = 1.0    # 重要度ランク
    full_content_embedding: list[float] | None = None  # レポートの埋め込み

このレポートには以下が含まれます。

  • エグゼクティブサマリー:コミュニティの概要
  • 主要エンティティ:コミュニティ内の重要な実体
  • 主要な関係性:エンティティ間の重要なつながり
  • 主要な主張・事実:コミュニティに関連する重要情報

2.2.3 Local / Global 検索の使い分け

GraphRAG は、クエリの性質に応じて異なる検索戦略を提供します。

Local Search(局所検索):

  • 目的:特定のエンティティに関する詳細な質問に回答
  • アプローチ:クエリに関連するエンティティを起点に、グラフを展開
  • 適したクエリ:「X とは何か?」「Y の特徴は?」

Global Search(大域検索):

  • 目的:データセット全体に関する包括的な質問に回答
  • アプローチ:コミュニティレポートを活用した Map-Reduce 処理
  • 適したクエリ:「主要なテーマは?」「全体的な傾向は?」

DRIFT Search(ハイブリッド検索):

  • 目的:Local と Global の利点を組み合わせた検索
  • アプローチ:コミュニティ情報を活用しつつ、詳細な探索を実施
  • 適したクエリ:広範な文脈と詳細な情報の両方が必要な質問

検索手法の選択指針:

クエリタイプ 推奨手法 理由
「X とは何か?」 Local Search 特定エンティティの詳細
「X と Y の関係は?」 Local Search / DRIFT 関係性の探索
「主要なテーマは?」 Global Search 全体像の把握
「分野の傾向は?」 Global Search 包括的な分析
「X について詳しく、かつ全体像も」 DRIFT Search 両方の視点

次章では、Microsoft GraphRAG の具体的なアーキテクチャとプロジェクト構造を解説します。

第3章 Microsoft GraphRAG アーキテクチャ概観

本章では、Microsoft GraphRAG プロジェクトの全体構造を俯瞰します。ディレクトリ構成、主要モジュールの役割、データモデル、そして処理パイプラインの全体像を理解することで、以降の詳細な解説への基盤を築きます。

3.1 プロジェクト構造

3.1.1 ディレクトリ構成

GraphRAG のソースコードは、機能ごとに明確に分離された構造になっています。

graphrag/
├── api/                    # 外部向け API(index, query, prompt_tune)
├── cache/                  # LLM キャッシュ管理
├── callbacks/              # パイプライン実行のコールバック
├── cli/                    # コマンドラインインターフェース
├── config/                 # 設定管理(YAML、環境変数)
├── data_model/             # データモデル定義(Entity, Relationship等)
├── factory/                # ファクトリパターン実装
├── index/                  # インデックス構築パイプライン
│   ├── input/              # 入力データ処理
│   ├── operations/         # 各種処理オペレーション
│   ├── run/                # パイプライン実行制御
│   ├── text_splitting/     # テキスト分割
│   ├── workflows/          # ワークフロー定義
│   └── update/             # インクリメンタル更新
├── language_model/         # LLM インターフェース
├── logger/                 # ロギング
├── prompt_tune/            # プロンプトチューニング
├── prompts/                # プロンプトテンプレート
├── query/                  # 検索エンジン
│   ├── context_builder/    # コンテキスト構築
│   └── structured_search/  # Local/Global/DRIFT Search
├── storage/                # ストレージ抽象化
├── tokenizer/              # トークナイザー
├── utils/                  # ユーティリティ
└── vector_stores/          # ベクトルストア連携

3.1.2 主要モジュールの役割

GraphRAG は大きく Index(インデックス構築)Query(検索) の2つのフェーズに分かれます。

Index フェーズのモジュール:

モジュール 役割
api/index.py インデックス構築の API エントリポイント
index/workflows/ 各ワークフロー(グラフ抽出、コミュニティ生成等)の実装
index/operations/ 低レベルの処理オペレーション
index/run/ パイプライン実行制御

Query フェーズのモジュール:

モジュール 役割
api/query.py 検索の API エントリポイント
query/structured_search/ Local/Global/DRIFT Search の実装
query/context_builder/ LLM に渡すコンテキストの構築
query/factory.py 検索エンジンの生成

共通モジュール:

モジュール 役割
config/ 設定の読み込み・管理
language_model/ OpenAI / Azure OpenAI との通信
cache/ LLM 応答のキャッシュ
storage/ ファイル / Blob / CosmosDB ストレージ
vector_stores/ LanceDB / Azure AI Search 等

3.2 データモデル

GraphRAG は Knowledge Model と呼ばれるデータモデルを採用しています。すべての出力テーブルはこのモデルに準拠し、Parquet 形式で保存されます。

3.2.1 Document / TextUnit

Document は入力文書を表します。

フィールド 説明
id str 一意識別子(UUID)
title str ファイル名またはタイトル
text str 文書の全文
text_unit_ids str[] この文書から生成された TextUnit の ID リスト
metadata dict 追加メタデータ(CSV インポート時)

TextUnit は分析単位のテキストチャンクです。

フィールド 説明
id str 一意識別子
text str チャンクのテキスト(デフォルト300トークン)
document_ids str[] 元の Document ID
entity_ids str[] このチャンクで抽出された Entity ID
relationship_ids str[] このチャンクで抽出された Relationship ID

3.2.2 Entity / Relationship

Entity は抽出されたエンティティを表します。

フィールド 説明
id str 一意識別子
title str エンティティ名
type str 種別(PERSON, ORGANIZATION, GEO, EVENT 等)
description str LLM が生成した説明文(複数出現時は要約)
text_unit_ids str[] 出現する TextUnit の ID
frequency int 出現した TextUnit の数
degree int グラフ上の次数(接続数)
x, y float 可視化用の座標(UMAP 使用時)

Relationship はエンティティ間の関係を表します。

フィールド 説明
id str 一意識別子
source str 起点エンティティ名
target str 終点エンティティ名
description str 関係の説明文
weight float エッジの重み(LLM が判定した強度の合計)
combined_degree int source と target の次数の合計
text_unit_ids str[] 出現する TextUnit の ID

3.2.3 Community / CommunityReport

Community は Leiden アルゴリズムで検出されたクラスタです。

フィールド 説明
id str 一意識別子
community int Leiden が生成したクラスタ ID
level int 階層の深さ(0 が最小粒度)
parent int 親コミュニティ ID
children int[] 子コミュニティ ID のリスト
entity_ids str[] メンバーエンティティ ID
relationship_ids str[] コミュニティ内の関係 ID
size int コミュニティサイズ(エンティティ数)

CommunityReport は各コミュニティの LLM 生成サマリーです。

フィールド 説明
id str 一意識別子
community int 対応するコミュニティ ID
level int コミュニティの階層レベル
title str LLM が生成したタイトル
summary str レポートの要約
full_content str レポートの全文
rank float 重要度ランキング
findings dict 主要な発見事項(summary, explanation)

3.2.4 Covariate

Covariate(共変量)は、オプションで抽出される時間制約付きの主張・事実です。

フィールド 説明
covariate_type str 種別(デフォルトは "claim")
type str 主張の性質
description str 行動の説明
subject_id str 主体となるエンティティ名
object_id str 対象となるエンティティ名
status str 評価結果(TRUE / FALSE / SUSPECTED)
start_date str 開始日時(ISO8601)
end_date str 終了日時(ISO8601)

※ Claim 抽出はデフォルトで無効。不正検出など特定のユースケースで有用。

3.3 処理パイプライン全体像

3.3.1 Indexing フェーズ

GraphRAG のインデックス構築は、ワークフロー の連鎖として実行されます。

Standard パイプライン(LLM ベース):

_standard_workflows = [
    "create_base_text_units",      # Phase 1: TextUnit 生成
    "create_final_documents",      # Phase 5: Document 処理
    "extract_graph",               # Phase 2: エンティティ・関係抽出(LLM)
    "finalize_graph",              # グラフ最終化
    "extract_covariates",          # Phase 2: Covariate 抽出(オプション)
    "create_communities",          # Phase 3: コミュニティ検出
    "create_final_text_units",     # TextUnit 最終化
    "create_community_reports",    # Phase 4: レポート生成
    "generate_text_embeddings",    # Phase 7: 埋め込み生成
]

Fast パイプライン(NLP ベース / LazyGraphRAG):

_fast_workflows = [
    "create_base_text_units",
    "create_final_documents",
    "extract_graph_nlp",           # NLP でグラフ抽出(LLM 不使用)
    "prune_graph",                 # グラフ枝刈り
    "finalize_graph",
    "create_communities",
    "create_final_text_units",
    "create_community_reports_text",  # テキストベースレポート
    "generate_text_embeddings",
]

処理フロー図:

3.3.2 Query フェーズ

Query フェーズでは、構築されたインデックスを使って検索を実行します。

API エントリポイント(api/query.py):

# Global Search
async def global_search(
    config: GraphRagConfig,
    entities: pd.DataFrame,
    communities: pd.DataFrame,
    community_reports: pd.DataFrame,
    community_level: int | None,
    dynamic_community_selection: bool,
    response_type: str,
    query: str,
    ...
) -> tuple[str, str | list[pd.DataFrame]]:
    """コミュニティレポートを活用した大域検索"""

# Local Search
async def local_search(
    config: GraphRagConfig,
    entities: pd.DataFrame,
    relationships: pd.DataFrame,
    text_units: pd.DataFrame,
    community_reports: pd.DataFrame,
    query: str,
    ...
) -> tuple[str, str | list[pd.DataFrame]]:
    """エンティティを起点とした局所検索"""

# DRIFT Search
async def drift_search(
    config: GraphRagConfig,
    entities: pd.DataFrame,
    relationships: pd.DataFrame,
    text_units: pd.DataFrame,
    community_reports: pd.DataFrame,
    query: str,
    ...
) -> tuple[str, str | list[pd.DataFrame]]:
    """Local + Global のハイブリッド検索"""

ファクトリパターンによる拡張性:

GraphRAG は各サブシステムでファクトリパターンを採用しており、カスタム実装を登録できます。

サブシステム 拡張ポイント デフォルト実装
Language Model language_model/factory.py OpenAI, Azure OpenAI
Cache cache/factory.py File, Blob, CosmosDB
Storage storage/factory.py File, Blob, CosmosDB
Vector Store vector_stores/factory.py LanceDB, Azure AI Search, CosmosDB
Workflows index/workflows/factory.py Standard, Fast, Update
# カスタムワークフローの登録例
from graphrag.index.workflows.factory import PipelineFactory

def custom_workflow(config, context):
    # カスタム処理
    pass

PipelineFactory.register("my_custom_workflow", custom_workflow)

次章では、インデックス構築(Indexing)の各ステップを詳細に解説します。

第4章 インデックス構築(Indexing)の仕組み

本章では、GraphRAG の核心であるインデックス構築パイプラインを詳細に解説します。テキスト分割からグラフ構築、コミュニティ検出、そしてレポート生成まで、各ステップの実装を見ていきます。

4.1 入力処理

4.1.1 ドキュメントローダー

GraphRAG は複数の入力形式をサポートしています。

形式 ファイル種別 用途
text .txt プレーンテキスト
csv .csv 構造化データ(text/title 列必須)
json .json メタデータ付きテキスト

入力設定(graphrag.yaml):

input:
  type: file                    # file または blob
  file_type: text               # text, csv, json
  base_dir: "input"             # 入力ディレクトリ
  file_pattern: ".*\\.txt$"     # 対象ファイルパターン
  file_filter:                  # フィルタ条件(オプション)
    - "*.md"
    - "*.txt"

4.1.2 テキストチャンキング

create_base_text_units ワークフローがテキストを分析単位(TextUnit)に分割します。

チャンキング戦略:

戦略 説明 用途
tokens トークン数ベースの分割 デフォルト、LLM 最適化
sentence 文単位の分割 自然な境界を維持

実装コード(chunk_text.py):

def chunk_text(
    input: pd.DataFrame,
    column: str,
    size: int,           # デフォルト: 1200 トークン
    overlap: int,        # デフォルト: 100 トークン
    encoding_model: str, # デフォルト: cl100k_base
    strategy: ChunkStrategyType,
    callbacks: WorkflowCallbacks,
) -> pd.Series:
    """テキストを小さなチャンクに分割する"""

設定パラメータ:

chunks:
  size: 1200              # チャンクサイズ(トークン数)
  overlap: 100            # オーバーラップ(文脈維持のため)
  group_by_columns: []    # グループ化カラム
  encoding_model: cl100k_base  # tiktoken エンコーディング
  strategy: tokens        # 分割戦略

なぜオーバーラップが重要か?
文書の論理的な区切りとチャンク境界は一致しないことが多い。100トークンのオーバーラップにより、境界付近のエンティティや関係も適切に抽出できる。

4.2 エンティティ・リレーション抽出

GraphRAG の核心は LLM を使ったエンティティと関係の抽出 です。

4.2.1 LLM によるエンティティ抽出

extract_graph ワークフローが各 TextUnit から知識グラフを構築します。

処理フロー:

実装コード(extract_graph.py):

async def extract_graph(
    text_units: pd.DataFrame,
    callbacks: WorkflowCallbacks,
    cache: PipelineCache,
    text_column: str,
    id_column: str,
    strategy: dict[str, Any] | None,
    async_mode: AsyncType = AsyncType.AsyncIO,
    entity_types=["organization", "person", "geo", "event"],
    num_threads: int = 4,
) -> tuple[pd.DataFrame, pd.DataFrame]:
    """LLM を使ってテキストからエンティティと関係を抽出"""
    
    async def run_strategy(row):
        text = row[text_column]
        id = row[id_column]
        result = await strategy_exec(
            [Document(text=text, id=id)],
            entity_types,
            cache,
            strategy_config,
        )
        return [result.entities, result.relationships, result.graph]
    
    # 並列処理で全 TextUnit を処理
    results = await derive_from_rows(
        text_units,
        run_strategy,
        callbacks,
        async_type=async_mode,
        num_threads=num_threads,
    )

4.2.2 プロンプト設計

GraphRAG のエンティティ抽出プロンプトは、構造化された出力を生成するよう設計されています。

抽出プロンプト(prompts/index/extract_graph.py):

-Goal-
Given a text document and a list of entity types, identify all entities 
of those types from the text and all relationships among them.

-Steps-
1. Identify all entities. For each identified entity, extract:
   - entity_name: Name of the entity, capitalized
   - entity_type: One of [{entity_types}]
   - entity_description: Comprehensive description
   
   Format: ("entity"{delimiter}<name>{delimiter}<type>{delimiter}<description>)

2. Identify all pairs of (source_entity, target_entity) that are clearly related:
   - source_entity: name of the source entity
   - target_entity: name of the target entity
   - relationship_description: why they are related
   - relationship_strength: numeric score (1-10)
   
   Format: ("relationship"{delimiter}<source>{delimiter}<target>{delimiter}<desc>{delimiter}<strength>)

3. Return output as a single list using {record_delimiter} as separator.

出力例:

("entity"|MICROSOFT|ORGANIZATION|A technology company headquartered in Redmond)
<|>
("entity"|GRAPHRAG|TECHNOLOGY|A RAG system using knowledge graphs for retrieval)
<|>
("relationship"|MICROSOFT|GRAPHRAG|Microsoft developed GraphRAG as an open-source project|8)

4.2.3 抽出結果のマージ

同一エンティティが複数の TextUnit で抽出される場合、それらをマージします。

マージ処理の流れ:

説明文の要約:

複数の説明文は LLM で要約されます(summarize_descriptions):

async def summarize_descriptions(
    entities_df: pd.DataFrame,
    relationships_df: pd.DataFrame,
    callbacks: WorkflowCallbacks,
    cache: PipelineCache,
    strategy: dict[str, Any] | None = None,
    num_threads: int = 4,
) -> tuple[pd.DataFrame, pd.DataFrame]:
    """複数の説明文を1つに要約"""

4.3 グラフ構築

4.3.1 NetworkX によるグラフ表現

抽出されたエンティティと関係から NetworkX グラフを構築します。

from graphrag.index.operations.create_graph import create_graph

# 関係データからグラフを構築
graph = create_graph(relationships, edge_attr=["weight"])
# graph: nx.Graph
# - ノード: エンティティ名(title)
# - エッジ: 関係(source → target)
# - エッジ属性: weight(関係の強度)

グラフの特徴:

属性 説明
無向グラフ 関係は双方向として扱う
重み付き relationship_strength の合計が weight
マルチグラフ非対応 同一ペアの複数関係は1エッジにマージ

4.3.2 エッジの重み付け

エッジの重みは、LLM が判定した relationship_strength(1-10)の合計です。

# 同一の(source, target)ペアが複数回出現した場合
edge_weight = sum(relationship_strengths)
# 例: 3回出現で strength が 7, 8, 6 → weight = 21

4.4 コミュニティ検出

4.4.1 Leiden アルゴリズム

GraphRAG は Leiden アルゴリズム を使ってコミュニティを検出します。

なぜ Leiden か?

アルゴリズム 利点 欠点
Louvain 高速 品質にばらつき
Leiden 高品質、保証付き収束 やや低速
Infomap 情報理論的に最適 計算コスト大

実装(cluster_graph.py):

from graspologic.partition import hierarchical_leiden

def cluster_graph(
    graph: nx.Graph,
    max_cluster_size: int,    # デフォルト: 10
    use_lcc: bool,            # 最大連結成分のみ使用
    seed: int | None = None,
) -> Communities:
    """階層的 Leiden クラスタリングを適用"""
    
    # graspologic の hierarchical_leiden を使用
    community_mapping = hierarchical_leiden(
        graph, 
        max_cluster_size=max_cluster_size, 
        random_seed=seed
    )
    
    # 結果を (level, cluster_id, parent_id, nodes) 形式に変換
    results: Communities = []
    for level in clusters:
        for cluster_id, nodes in clusters[level].items():
            results.append((level, cluster_id, parent_mapping[cluster_id], nodes))
    return results

4.4.2 階層的コミュニティ構造

Leiden は 階層的なコミュニティ を生成します:

設定パラメータ:

cluster_graph:
  max_cluster_size: 10    # これ以上大きいクラスタは再分割
  use_lcc: true           # 最大連結成分のみ処理
  seed: null              # 再現性のためのシード値

4.4.3 コミュニティレポート生成

各コミュニティに対して、LLM がサマリーレポートを生成します。

レポート生成の流れ(create_community_reports.py):

async def create_community_reports(
    edges_input: pd.DataFrame,
    entities: pd.DataFrame,
    communities: pd.DataFrame,
    claims_input: pd.DataFrame | None,
    callbacks: WorkflowCallbacks,
    cache: PipelineCache,
    summarization_strategy: dict,
    async_mode: AsyncType = AsyncType.AsyncIO,
    num_threads: int = 4,
) -> pd.DataFrame:
    """各コミュニティのレポートを生成"""
    
    # 1. コミュニティごとのローカルコンテキストを構築
    local_contexts = build_local_context(
        nodes, edges, claims, tokenizer, callbacks, max_input_length
    )
    
    # 2. レベルごとのコンテキストを構築(階層情報を追加)
    level_contexts = build_level_context(...)
    
    # 3. LLM でサマリー生成
    reports = await summarize_communities(...)

レポートの構造:

フィールド 内容
title コミュニティのタイトル(LLM 生成)
summary 要約文(1-2段落)
full_content 詳細な説明文
findings 主要な発見事項のリスト
rank 重要度スコア

4.5 埋め込み生成

4.5.1 テキスト埋め込み

generate_text_embeddings ワークフローが各種テキストの埋め込みを生成します。

埋め込み対象:

対象 フィールド 用途
TextUnit text Local Search のテキスト検索
Entity title, description エンティティ検索
Relationship description 関係検索
CommunityReport title, summary, full_content Global Search
Document text 文書検索

実装コード(generate_text_embeddings.py):

async def run_workflow(
    config: GraphRagConfig,
    context: PipelineRunContext,
) -> WorkflowFunctionOutput:
    """テキスト埋め込みを生成"""
    
    embedded_fields = config.embed_text.names
    # デフォルト: ["entity_description", "text_unit_text"]
    
    # 設定に応じて必要なテーブルをロード
    if entity_description_embedding in embedded_fields:
        entities = await load_table_from_storage("entities", storage)
    
    # 埋め込み生成
    output = await generate_text_embeddings(
        entities=entities,
        text_embed_config=text_embed,
        ...
    )

4.5.2 埋め込みモデル設定

embeddings:
  model: text-embedding-3-small  # OpenAI 埋め込みモデル
  dimensions: 1536                # ベクトル次元数
  batch_size: 16                  # バッチサイズ

4.6 ストレージ

4.6.1 Parquet 形式での保存

すべての出力テーブルは Apache Parquet 形式で保存されます。

output/
├── documents.parquet           # 入力文書
├── text_units.parquet          # テキストチャンク
├── entities.parquet            # エンティティ
├── relationships.parquet       # 関係
├── communities.parquet         # コミュニティ
├── community_reports.parquet   # レポート
└── stats.json                  # 統計情報

Parquet の利点:

特徴 利点
列指向 必要な列のみ読み込み可能
圧縮 ファイルサイズ削減
スキーマ 型情報を保持
並列読み込み 大規模データに対応

4.6.2 ベクトルストア連携

埋め込みベクトルは ベクトルストア に保存できます。

対応ベクトルストア:

ストア 設定値 特徴
LanceDB lancedb デフォルト、ローカル
Azure AI Search azure_ai_search スケーラブル、マネージド
CosmosDB cosmosdb NoSQL との統合

設定例:

vector_store:
  type: lancedb
  db_uri: "output/lancedb"      # LanceDB のパス
  
# Azure AI Search の場合
vector_store:
  type: azure_ai_search
  url: "https://<name>.search.windows.net"
  api_key: "${AZURE_SEARCH_API_KEY}"

次章では、LazyGraphRAG の軽量グラフ構築アプローチを解説します。

第5章 LazyGraphRAG:軽量グラフ構築の仕組み

GraphRAG の強力な検索品質は魅力的ですが、インデックス構築に莫大な LLM コストがかかります。LazyGraphRAG は、この課題に対する Microsoft Research の回答です。本章では、OSS GraphRAG に統合された NLP ベースのグラフ構築機能を解説します。

5.1 LazyGraphRAG の設計思想

5.1.1 GraphRAG のコスト課題

従来の GraphRAG には以下のコスト課題があります。

処理 LLM 呼び出し コスト要因
エンティティ抽出 全 TextUnit × 1回 入力トークン数
説明文要約 エンティティ数 × 1回 重複説明の統合
コミュニティレポート コミュニティ数 × 1回 出力トークン数

具体的なコスト例:

1,000 文書(100,000 トークン)の場合:
├── TextUnits: 約 300 チャンク(300回の LLM 呼び出し)
├── Entities: 約 1,000 個(重複要約で 500回以上)
├── Communities: 約 50 個(50回の LLM 呼び出し)
└── 合計: 800+ 回の LLM 呼び出し

GPT-4o の場合、数十〜数百ドルのコストに

Microsoft Research の報告:
LazyGraphRAG は GraphRAG と比較して インデックス構築コストが 0.1% に削減されます。100万トークンのコーパスで GraphRAG が $100 かかるなら、LazyGraphRAG は $0.10 で済みます。

5.1.2 「遅延評価」アプローチとは

LazyGraphRAG の名前の由来は Lazy Evaluation(遅延評価) です。

設計哲学の違い:

アプローチ インデックス時 クエリ時 適したユースケース
GraphRAG(Eager) 重い(LLM 多用) 軽い 頻繁にクエリされるデータ
LazyGraphRAG(Lazy) 軽い(NLP のみ) 重め 探索的分析、プロトタイピング

5.1.3 LLM 使用の最小化

LazyGraphRAG は以下の原則でコストを削減します。

  1. インデックス構築時の LLM ゼロ:名詞句抽出は spaCy 等の NLP ライブラリで実行
  2. クエリ時のみ LLM 使用:必要な情報だけを LLM で評価
  3. Best-First 探索:最も関連性の高い部分から順に評価

5.2 NLP ベースのグラフ構築(extract_graph_nlp)

OSS GraphRAG では、extract_graph_nlp ワークフローとして LazyGraphRAG のグラフ構築が実装されています。

5.2.1 名詞句抽出(Noun Phrase Extraction)

実装コード(extract_graph_nlp.py):

async def extract_graph_nlp(
    text_units: pd.DataFrame,
    cache: PipelineCache,
    extraction_config: ExtractGraphNLPConfig,
) -> tuple[pd.DataFrame, pd.DataFrame]:
    """NLP を使ってテキストからグラフを構築"""
    
    # 名詞句抽出器を生成
    text_analyzer = create_noun_phrase_extractor(text_analyzer_config)
    
    # 名詞グラフを構築
    extracted_nodes, extracted_edges = await build_noun_graph(
        text_units,
        text_analyzer=text_analyzer,
        normalize_edge_weights=extraction_config.normalize_edge_weights,
        num_threads=extraction_config.concurrent_requests,
    )
    
    # 下流ワークフロー互換のためのカラム追加
    extracted_nodes["type"] = "NOUN PHRASE"
    extracted_nodes["description"] = ""  # LLM 生成の説明文なし
    extracted_edges["description"] = ""
    
    return (extracted_nodes, extracted_edges)

名詞句抽出器の種類:

抽出器 実装 特徴
CFGNounPhraseExtractor spaCy + CFG 高精度、文法ベース
RegexExtractor 正規表現 高速、シンプル
SyntacticParsingExtractor 依存構文解析 最高精度、低速

CFG 抽出器の実装(cfg_extractor.py):

class CFGNounPhraseExtractor(BaseNounPhraseExtractor):
    """CFG ベースの名詞句抽出器"""
    
    def __init__(
        self,
        model_name: str,                    # "en_core_web_sm" など
        max_word_length: int,               # 最大単語長
        include_named_entities: bool,       # 固有表現を含めるか
        exclude_entity_tags: list[str],     # 除外する固有表現タグ
        exclude_pos_tags: list[str],        # 除外する品詞タグ
        noun_phrase_grammars: dict,         # CFG 文法規則
    ):
        # spaCy モデルをロード
        self.nlp = self.load_spacy_model(model_name, exclude=[...])
    
    def extract(self, text: str) -> list[str]:
        """テキストから名詞句を抽出"""
        doc = self.nlp(text)
        
        # 固有表現を抽出
        entities = [(ent.text, ent.label_) for ent in doc.ents]
        
        # CFG マッチングで名詞句を抽出
        cfg_matches = self.extract_cfg_matches(doc)
        
        # フィルタリングして返す
        return filtered_noun_phrases

5.2.2 共起関係によるエッジ構築

LazyGraphRAG では、同一 TextUnit 内に出現する名詞句ペア をエッジとして接続します。

実装コード(build_noun_graph.py):

def _extract_edges(
    nodes_df: pd.DataFrame,
    normalize_edge_weights: bool = True,
) -> pd.DataFrame:
    """名詞句の共起からエッジを構築"""
    
    # 各 TextUnit 内の名詞句リストを取得
    text_units_df = nodes_df.explode("text_unit_ids")
    
    # TextUnit ごとに名詞句をグループ化
    text_units_df = text_units_df.groupby("text_unit_id").agg({
        "title": lambda x: list(x) if len(x) > 1 else np.nan
    })
    
    # 全ペアの組み合わせを生成
    from itertools import combinations
    all_edges = [list(combinations(titles, 2)) for titles in titles_list]
    
    # エッジの重み = 共起回数
    grouped_edge_df["weight"] = grouped_edge_df["text_unit_ids"].apply(len)
    
    # PMI で正規化(オプション)
    if normalize_edge_weights:
        grouped_edge_df = calculate_pmi_edge_weights(nodes_df, grouped_edge_df)
    
    return grouped_edge_df

共起グラフの構築イメージ:

5.2.3 PMI(相互情報量)による重み付け

単純な共起回数では、高頻度語がノイズになります。PMI(Pointwise Mutual Information) で正規化します。

PMI の数学的定義:

$$
PMI(x, y) = p(x, y) \cdot \log_2 \frac{p(x, y)}{p(x) \cdot p(y)}
$$

ここで:

  • $p(x, y)$ = エッジの重み / 全エッジ重みの合計
  • $p(x)$ = ノード x の出現頻度 / 全出現頻度の合計

実装コード(graphs.py):

def calculate_pmi_edge_weights(
    nodes_df: pd.DataFrame,
    edges_df: pd.DataFrame,
) -> pd.DataFrame:
    """PMI でエッジの重みを正規化"""
    
    total_edge_weights = edges_df["weight"].sum()
    total_freq_occurrences = nodes_df["frequency"].sum()
    
    # p(x) = ノードの出現確率
    nodes_df["prop_occurrence"] = nodes_df["frequency"] / total_freq_occurrences
    
    # p(x,y) = エッジの出現確率
    edges_df["prop_weight"] = edges_df["weight"] / total_edge_weights
    
    # PMI = p(x,y) * log2(p(x,y) / (p(x) * p(y)))
    edges_df["weight"] = edges_df["prop_weight"] * np.log2(
        edges_df["prop_weight"] / 
        (edges_df["source_prop"] * edges_df["target_prop"])
    )
    
    return edges_df

PMI の効果:

状況 共起回数 PMI 解釈
「the」と「is」 高い 低い 高頻度語のノイズ
「GraphRAG」と「Leiden」 中程度 高い 意味的に強い関連
「Microsoft」と「研究」 低い 中程度 適度な関連

5.2.4 グラフの枝刈り(prune_graph)

NLP 抽出では多くのノイズが含まれるため、枝刈り(Pruning) が重要です。

実装コード(prune_graph.py):

def prune_graph(
    entities: pd.DataFrame,
    relationships: pd.DataFrame,
    pruning_config: PruneGraphConfig,
) -> tuple[pd.DataFrame, pd.DataFrame]:
    """グラフを統計的にフィルタリング"""
    
    pruned = prune_graph_operation(
        graph,
        min_node_freq=pruning_config.min_node_freq,         # 最小出現頻度
        max_node_freq_std=pruning_config.max_node_freq_std, # 高頻度ノード除外
        min_node_degree=pruning_config.min_node_degree,     # 最小次数
        max_node_degree_std=pruning_config.max_node_degree_std,  # ハブ除外
        min_edge_weight_pct=pruning_config.min_edge_weight_pct,  # 低重みエッジ除外
        remove_ego_nodes=pruning_config.remove_ego_nodes,   # エゴノード除外
        lcc_only=pruning_config.lcc_only,                   # 最大連結成分のみ
    )
    
    return pruned_entities, pruned_relationships

設定パラメータ:

prune_graph:
  min_node_freq: 2              # 2回以上出現するノードのみ
  max_node_freq_std: 3.0        # 平均+3σ以上の高頻度ノードを除外
  min_node_degree: 1            # 孤立ノードを除外
  min_edge_weight_pct: 0.0      # 低重みエッジの除外閾値
  lcc_only: true                # 最大連結成分のみ保持

5.3 GraphRAG vs LazyGraphRAG 比較

5.3.1 インデックス構築の違い

パイプライン比較:

ステップ GraphRAG (Standard) LazyGraphRAG (Fast)
TextUnit 生成 create_base_text_units 同じ
グラフ抽出 extract_graph(LLM) extract_graph_nlp(NLP)
グラフ枝刈り なし prune_graph
グラフ最終化 finalize_graph 同じ
コミュニティ検出 create_communities 同じ
レポート生成 create_community_reports(グラフベース) create_community_reports_text(テキストベース)

フロー図:

5.3.2 コスト比較:LLM vs NLP

インデックス構築コスト(1M トークンのコーパス):

項目 GraphRAG LazyGraphRAG 削減率
LLM 呼び出し 800+ 回 0 回 100%
LLM トークン 2M+ 0 100%
コスト(GPT-4o) $50-100 $0.05-0.10 99.9%
処理時間 数時間 数分 95%+

Microsoft Research の報告値:
LazyGraphRAG のインデックス構築コストは GraphRAG の 0.1% 程度。これは NLP ライブラリ(spaCy)の計算コストのみで、クラウド API 呼び出しが不要なためです。

5.3.3 品質とトレードオフ

検索品質の比較(Microsoft Research 評価):

指標 GraphRAG LazyGraphRAG 評価
Comprehensiveness(包括性) 高い 同等〜やや低い
Diversity(多様性) 高い 同等
Empowerment(有用性) 高い 同等
Directness(直接性) 中程度 高い

トレードオフの考え方:

5.4 LazyGraphRAG の現状と将来

5.4.1 Microsoft Discovery への統合

LazyGraphRAG は Microsoft Discovery に統合されています。

  • Microsoft Discovery:AI for Science 向けのエンタープライズプラットフォーム
  • 用途:創薬、材料科学、バイオインフォマティクス
  • LazyGraphRAG の役割:大規模な科学文献の高速インデックス構築

Discovery での活用:

研究文献 (数百万文書)
    ↓
LazyGraphRAG でインデックス構築(数時間)
    ↓
研究者がインタラクティブにクエリ
    ↓
クエリ時に LLM で詳細評価

5.4.2 OSS GraphRAG での位置づけ

OSS GraphRAG v1.1+ では、Fast Pipeline として LazyGraphRAG 相当の機能が提供されています:

使用方法:

# Fast パイプライン(LazyGraphRAG 相当)でインデックス構築
graphrag index --method fast

# 従来の Standard パイプライン
graphrag index --method standard

設定ファイル(graphrag.yaml):

# Fast パイプライン用の設定
extract_graph_nlp:
  text_analyzer:
    extractor_type: cfg              # CFG ベースの名詞句抽出
    model_name: en_core_web_sm       # spaCy モデル
    include_named_entities: true     # 固有表現を含める
  normalize_edge_weights: true       # PMI 正規化を有効化
  concurrent_requests: 4             # 並列処理数

prune_graph:
  min_node_freq: 2
  max_node_freq_std: 3.0
  min_node_degree: 1
  lcc_only: true

5.4.3 ハイブリッドアプローチの可能性

将来的には、GraphRAG と LazyGraphRAG を組み合わせたハイブリッドアプローチが有望です。

段階的アプローチ:

  1. Phase 1:LazyGraphRAG で全コーパスをインデックス(低コスト)
  2. Phase 2:重要なコミュニティのみ LLM で精緻化(選択的コスト)
  3. Phase 3:クエリパターンに応じて追加精緻化(オンデマンド)

次章では、構築されたインデックスを使った検索手法(Local/Global/DRIFT Search)を詳細に解説します。

第6章 検索手法の詳細

本章では、GraphRAG が提供する3つの検索手法(Local Search、Global Search、DRIFT Search)の実装詳細を解説します。それぞれのアルゴリズム、コンテキスト構築方法、そして適切な使い分けを理解しましょう。

6.1 Local Search

Local Search は 特定のエンティティに焦点を当てた詳細な回答 を生成する検索手法です。

6.1.1 アルゴリズム概要

処理フロー:

主要なステップ:

  1. エンティティ抽出:クエリからエンティティ候補を抽出
  2. ベクトル検索:埋め込みベクトルで類似エンティティを検索
  3. グラフ探索:関連するエンティティ・関係を収集
  4. コンテキスト構築:LLM に渡すコンテキストを組み立て
  5. 回答生成:LLM でクエリに回答

6.1.2 コンテキスト構築

Local Search のコンテキストは複数のデータソースから構築されます。

コンテキストの構成要素:

データソース 役割 優先度
Entities クエリに関連するエンティティの情報
Relationships エンティティ間の関係
TextUnits 元テキストのチャンク
CommunityReports コミュニティの要約情報

コンテキスト構築のパラメータ:

local_context_params = {
    "text_unit_prop": 0.5,          # TextUnit の割合
    "community_prop": 0.1,          # CommunityReport の割合
    "top_k_mapped_entities": 10,    # マッピングするエンティティ数
    "top_k_relationships": 10,      # 取得する関係数
    "include_entity_rank": True,    # エンティティランクを含める
    "include_relationship_weight": True,  # 関係の重みを含める
    "max_context_tokens": 8000,     # 最大コンテキストトークン数
}

6.1.3 実装コード解説

LocalSearch クラス(local_search/search.py):

class LocalSearch(BaseSearch[LocalContextBuilder]):
    """Local Search の実装"""
    
    def __init__(
        self,
        model: ChatModel,
        context_builder: LocalContextBuilder,
        system_prompt: str | None = None,
        response_type: str = "multiple paragraphs",
        callbacks: list[QueryCallbacks] | None = None,
    ):
        self.system_prompt = system_prompt or LOCAL_SEARCH_SYSTEM_PROMPT
        self.response_type = response_type
    
    async def search(
        self,
        query: str,
        conversation_history: ConversationHistory | None = None,
        **kwargs,
    ) -> SearchResult:
        """検索を実行"""
        
        # 1. コンテキストを構築
        context_result = self.context_builder.build_context(
            query=query,
            conversation_history=conversation_history,
            **self.context_builder_params,
        )
        
        # 2. システムプロンプトを構築
        search_prompt = self.system_prompt.format(
            context_data=context_result.context_chunks,
            response_type=self.response_type,
        )
        
        # 3. LLM で回答生成(ストリーミング)
        async for response in self.model.achat_stream(
            prompt=query,
            history=[{"role": "system", "content": search_prompt}],
        ):
            full_response += response
        
        return SearchResult(
            response=full_response,
            context_data=context_result.context_records,
            ...
        )

システムプロンプト(local_search_system_prompt.py):

---Role---
You are a helpful assistant responding to questions about data in the tables provided.

---Goal---
Generate a response of the target length and format that responds to the user's question,
summarizing all information in the input data tables appropriate for the response length.

Points supported by data should list their data references as follows:
"This is an example sentence supported by data [Data: <dataset name> (record ids)]."

---Data tables---
{context_data}

---Target response length and format---
{response_type}

6.2 Global Search

Global Search は データセット全体に関する包括的な質問 に回答する検索手法です。

6.2.1 Map-Reduce アプローチ

Global Search は Map-Reduce パターン を採用しています。

Map フェーズ:

  • 各コミュニティレポートのバッチに対して並列で LLM を呼び出し
  • 各バッチから「キーポイント」と「重要度スコア」を抽出
  • JSON 形式で構造化された出力を生成

Reduce フェーズ:

  • 全バッチのキーポイントを重要度順にソート
  • 上位のキーポイントを統合して最終回答を生成

6.2.2 コミュニティレポートの活用

Global Search はコミュニティレポートを主要なコンテキストとして使用します。

コミュニティレポートの選択:

async def build_context(
    self,
    query: str,
    conversation_history: ConversationHistory | None = None,
    **kwargs,
) -> ContextBuilderResult:
    """Global Search 用のコンテキストを構築"""
    
    # コミュニティレベルでフィルタリング
    selected_reports = self._filter_by_level(community_level)
    
    # トークン制限内に収まるようにバッチ化
    batches = self._create_batches(
        reports=selected_reports,
        max_tokens=self.max_data_tokens,
    )
    
    return ContextBuilderResult(
        context_chunks=batches,
        context_records={"community_reports": selected_reports},
    )

6.2.3 動的コミュニティ選択

v1.0 以降、Dynamic Community Selection が追加されました。

# 従来:固定レベルのコミュニティを使用
community_level = 2  # Level 2 のコミュニティのみ

# 動的選択:クエリに応じて最適なレベルを選択
dynamic_community_selection = True

動的選択のメリット:

従来の固定選択 動的選択
設定したレベルのみ使用 クエリに最適なレベルを自動選択
粒度が固定 粗い〜細かい粒度を動的に調整
情報の過不足あり 関連性の高い情報を選択

6.2.4 実装コード解説

GlobalSearch クラス(global_search/search.py):

class GlobalSearch(BaseSearch[GlobalContextBuilder]):
    """Global Search の実装"""
    
    def __init__(
        self,
        model: ChatModel,
        context_builder: GlobalContextBuilder,
        map_system_prompt: str | None = None,
        reduce_system_prompt: str | None = None,
        json_mode: bool = True,
        concurrent_coroutines: int = 32,  # 並列度
    ):
        self.map_system_prompt = map_system_prompt or MAP_SYSTEM_PROMPT
        self.reduce_system_prompt = reduce_system_prompt or REDUCE_SYSTEM_PROMPT
    
    async def search(self, query: str, **kwargs) -> GlobalSearchResult:
        """Global Search を実行"""
        
        # Step 1: コンテキスト構築
        context_result = await self.context_builder.build_context(query=query)
        
        # Step 2: Map フェーズ(並列実行)
        map_responses = await asyncio.gather(*[
            self._map_response_single_batch(
                context_data=data,
                query=query,
            )
            for data in context_result.context_chunks
        ])
        
        # Step 3: Reduce フェーズ
        reduce_response = await self._reduce_response(
            map_responses=map_responses,
            query=query,
        )
        
        return GlobalSearchResult(
            response=reduce_response.response,
            map_responses=map_responses,
            ...
        )

Map プロンプト(global_search_map_system_prompt.py):

---Goal---
Generate a response consisting of a list of key points that responds to the user's question.

Each key point should have:
- Description: A comprehensive description of the point.
- Importance Score: An integer score (0-100) indicating importance.

The response should be JSON formatted:
{
    "points": [
        {"description": "Description [Data: Reports (ids)]", "score": 85},
        {"description": "Description [Data: Reports (ids)]", "score": 72}
    ]
}

---Data tables---
{context_data}

Reduce プロンプト(global_search_reduce_system_prompt.py):

---Role---
You are a helpful assistant responding to questions about a dataset 
by synthesizing perspectives from multiple analysts.

---Goal---
Generate a response that synthesizes all the reports from multiple analysts.
Note that the analysts' reports are ranked in descending order of importance.

---Analyst Reports---
{report_data}

6.3 DRIFT Search

DRIFT Search は Local と Global の利点を組み合わせた 高度な検索手法です。

6.3.1 DRIFT の設計思想

DRIFT = Dynamic Reasoning and Inference with Flexible Traversal

DRIFT の特徴:

特徴 説明
Primer コミュニティレポートで初期コンテキストを取得
Follow-up LLM が自動でフォローアップ質問を生成
Iterative 深さ制限まで反復的に探索
Best-First スコアの高いアクションを優先

6.3.2 Local + Global のハイブリッド

DRIFT は Global(コミュニティレポート)と Local(詳細探索)を組み合わせます。

処理の流れ:

# 1. Primer: コミュニティレポートで初期回答
primer_response = await self.primer.search(
    query=query, 
    top_k_reports=primer_context
)

# 2. 中間回答とフォローアップを抽出
init_action = self._process_primer_results(query, primer_response)
# init_action.intermediate_answer: "GraphRAG は..."
# init_action.follow_ups: ["Leiden の詳細は?", "コスト構造は?"]

# 3. 反復探索(深さ制限まで)
while epochs < config.n_depth:
    # 上位 k 個のアクションを選択
    actions = self.query_state.rank_incomplete_actions()
    actions = actions[:config.drift_k_followups]
    
    # Local Search で各アクションを実行
    results = await self._search_step(
        global_query=query,
        search_engine=self.local_search,
        actions=actions,
    )
    
    # 新しいフォローアップを追加
    for action in results:
        self.query_state.add_all_follow_ups(action, action.follow_ups)

6.3.3 実装コード解説

DRIFTSearch クラス(drift_search/search.py):

class DRIFTSearch(BaseSearch[DRIFTSearchContextBuilder]):
    """DRIFT Search の実装"""
    
    def __init__(
        self,
        model: ChatModel,
        context_builder: DRIFTSearchContextBuilder,
        query_state: QueryState | None = None,
    ):
        self.primer = DRIFTPrimer(
            config=context_builder.config,
            chat_model=model,
        )
        self.local_search = self.init_local_search()
        self.query_state = query_state or QueryState()
    
    async def search(
        self,
        query: str,
        reduce: bool = True,
        **kwargs,
    ) -> SearchResult:
        """DRIFT Search を実行"""
        
        # Step 1: Primer で初期コンテキスト
        if not self.query_state.graph:
            primer_context, _ = await self.context_builder.build_context(query)
            primer_response = await self.primer.search(
                query=query, 
                top_k_reports=primer_context
            )
            init_action = self._process_primer_results(query, primer_response)
            self.query_state.add_action(init_action)
        
        # Step 2: 反復的な Local Search
        epochs = 0
        while epochs < self.context_builder.config.n_depth:
            actions = self.query_state.rank_incomplete_actions()
            if len(actions) == 0:
                break
            
            results = await self._search_step(
                global_query=query,
                search_engine=self.local_search,
                actions=actions[:self.context_builder.config.drift_k_followups],
            )
            
            for action in results:
                self.query_state.add_action(action)
                self.query_state.add_all_follow_ups(action, action.follow_ups)
            epochs += 1
        
        # Step 3: 結果を統合
        if reduce:
            reduced_response = await self._reduce_response(
                responses=self.query_state.serialize(),
                query=query,
            )
        
        return SearchResult(response=reduced_response, ...)

DRIFTPrimer クラス(drift_search/primer.py):

class DRIFTPrimer:
    """DRIFT の初期フェーズ(Primer)"""
    
    async def search(
        self,
        query: str,
        top_k_reports: pd.DataFrame,
    ) -> SearchResult:
        """コミュニティレポートを使った初期検索"""
        
        # コミュニティレポートをコンテキストに
        context = self._build_primer_context(top_k_reports)
        
        # 中間回答とフォローアップを生成
        response = await self.chat_model.achat(
            prompt=DRIFT_PRIMER_PROMPT.format(
                query=query,
                context=context,
            )
        )
        
        return SearchResult(response=parsed_response, ...)

6.4 検索手法の比較

6.4.1 ユースケース別の選択指針

検索手法の選択フローチャート:

具体的なクエリ例:

クエリ 推奨手法 理由
「GraphRAG の開発者は?」 Local 特定エンティティの情報
「Microsoft と Google の関係は?」 Local エンティティ間の関係
「主要なトピックは?」 Global データセット全体の傾向
「最も影響力のある人物は?」 Global 全体的なランキング
「GraphRAG の詳細と業界への影響」 DRIFT 詳細 + 全体像

6.4.2 パフォーマンス特性

各検索手法の特性比較:

特性 Local Search Global Search DRIFT Search
LLM 呼び出し 1回 N+1回(Map-Reduce) 可変(反復回数依存)
レイテンシ 中〜高
回答の深さ 深い 広い 深く広い
コスト
適した質問 詳細・具体的 包括的・抽象的 複雑・多面的

LLM 呼び出し回数の目安:

Local Search:
  - コンテキスト構築: 0回(ベクトル検索のみ)
  - 回答生成: 1回
  → 合計: 1回

Global Search:
  - Map フェーズ: バッチ数回(通常 10-50回)
  - Reduce フェーズ: 1回
  → 合計: 11-51回

DRIFT Search:
  - Primer: 1回
  - 各反復: フォローアップ数 × 深さ回
  - Reduce: 1回
  → 合計: 1 + (k × depth) + 1 回
  (k=3, depth=3 の場合、約12回)

コスト vs 品質のトレードオフ:

次章では、GraphRAG の主要コンポーネント(Language Model、プロンプト設計、ベクトルストア等)を詳細に解説します。

第7章 主要コンポーネント詳解

本章では、GraphRAG を構成する主要コンポーネントを詳細に解説します。Language Model インターフェース、プロンプト設計、ベクトルストア、そして設定管理について理解を深めましょう。

7.1 Language Model インターフェース

GraphRAG は Protocol ベース のLLM インターフェースを採用しており、様々なプロバイダーに対応しています。

7.1.1 OpenAI / Azure OpenAI 対応

対応モデルタイプ:

モデルタイプ 用途 対応プロバイダー
ChatModel テキスト生成、抽出、要約 OpenAI, Azure OpenAI, LiteLLM
EmbeddingModel ベクトル埋め込み OpenAI, Azure OpenAI, LiteLLM

Protocol 定義(language_model/protocol/base.py):

class ChatModel(Protocol):
    """Chat モデルのプロトコル定義"""
    
    config: LanguageModelConfig
    
    async def achat(
        self, prompt: str, history: list | None = None, **kwargs
    ) -> ModelResponse:
        """非同期でチャット応答を生成"""
        ...
    
    async def achat_stream(
        self, prompt: str, history: list | None = None, **kwargs
    ) -> AsyncGenerator[str, None]:
        """ストリーミングでチャット応答を生成"""
        ...

class EmbeddingModel(Protocol):
    """埋め込みモデルのプロトコル定義"""
    
    async def aembed(self, text: str, **kwargs) -> list[float]:
        """単一テキストの埋め込みを生成"""
        ...
    
    async def aembed_batch(
        self, text_list: list[str], **kwargs
    ) -> list[list[float]]:
        """バッチで埋め込みを生成"""
        ...

モデル設定(graphrag.yaml):

models:
  # デフォルトの Chat モデル
  default_chat_model:
    type: openai_chat               # または azure_openai_chat
    model: gpt-4o-mini
    api_key: ${OPENAI_API_KEY}
    max_tokens: 4000
    temperature: 0.0
    
  # デフォルトの Embedding モデル
  default_embedding_model:
    type: openai_embedding          # または azure_openai_embedding
    model: text-embedding-3-small
    api_key: ${OPENAI_API_KEY}
    
  # Azure OpenAI の場合
  azure_chat_model:
    type: azure_openai_chat
    model: gpt-4o
    api_base: https://<resource>.openai.azure.com/
    api_version: "2024-02-15-preview"
    auth_type: api_key              # または azure_managed_identity
    api_key: ${AZURE_OPENAI_API_KEY}

プロバイダー実装:

graphrag/language_model/providers/
├── fnllm/              # OpenAI 直接接続
│   └── ...
└── litellm/            # LiteLLM 経由(多プロバイダー対応)
    └── ...

7.1.2 キャッシュ機構

GraphRAG は LLM 応答をキャッシュして、再実行時のコスト削減デバッグの効率化 を実現します。

キャッシュタイプ:

タイプ 設定値 説明
File file ローカルファイルに JSON で保存
Blob blob Azure Blob Storage に保存
CosmosDB cosmosdb CosmosDB に保存
Memory memory メモリ内キャッシュ(永続化なし)
None none キャッシュ無効

キャッシュファクトリ(cache/factory.py):

class CacheFactory:
    """キャッシュ実装のファクトリ"""
    
    _registry: ClassVar[dict[str, Callable[..., PipelineCache]]] = {}
    
    @classmethod
    def register(cls, cache_type: str, creator: Callable) -> None:
        """カスタムキャッシュを登録"""
        cls._registry[cache_type] = creator
    
    @classmethod
    def create_cache(cls, cache_type: str, kwargs: dict) -> PipelineCache:
        """キャッシュを生成"""
        return cls._registry[cache_type](**kwargs)

# 組み込みキャッシュの登録
CacheFactory.register(CacheType.file.value, create_file_cache)
CacheFactory.register(CacheType.blob.value, create_blob_cache)
CacheFactory.register(CacheType.cosmosdb.value, create_cosmosdb_cache)
CacheFactory.register(CacheType.none.value, create_noop_cache)

設定例:

cache:
  type: file                      # キャッシュタイプ
  base_dir: "cache"               # キャッシュディレクトリ

キャッシュの活用シーン:

  • 開発・デバッグ:同じクエリを繰り返しても LLM コストがかからない
  • インクリメンタル更新:変更のないチャンクは再処理をスキップ
  • 障害復旧:処理途中で失敗しても、キャッシュから再開可能

7.1.3 レート制限とリトライ

LLM API のレート制限に対応するため、GraphRAG は リトライ機構レートリミッター を提供します。

設定パラメータ:

models:
  default_chat_model:
    # リトライ設定
    retry_strategy: native          # native, exponential_backoff, none
    max_retries: 10                 # 最大リトライ回数
    max_retry_wait: 10.0            # 最大待機時間(秒)
    
    # レート制限設定
    rate_limit_strategy: token_bucket  # token_bucket, fixed_window, none
    requests_per_minute: 60         # RPM 制限
    tokens_per_minute: 90000        # TPM 制限
    concurrent_requests: 25         # 並行リクエスト数

レートリミッター実装:

# graphrag/language_model/providers/litellm/services/rate_limiter/
class RateLimiterFactory:
    """レートリミッターのファクトリ"""
    
    _registry = {
        "token_bucket": TokenBucketRateLimiter,
        "fixed_window": FixedWindowRateLimiter,
    }
    
    def create(self, strategy: str, rpm: int, tpm: int) -> RateLimiter:
        return self._registry[strategy](rpm=rpm, tpm=tpm)

7.2 プロンプト設計

GraphRAG のプロンプトは、高品質な情報抽出と要約を実現するよう設計されています。

7.2.1 エンティティ抽出プロンプト

エンティティ抽出プロンプトは、テキストから構造化された情報を抽出します。

プロンプト構造:

-Goal-
Given a text document and a list of entity types, identify all entities
and relationships among them.

-Steps-
1. Identify all entities:
   - entity_name: Name of the entity, capitalized
   - entity_type: One of [{entity_types}]
   - entity_description: Comprehensive description
   
   Format: ("entity"|<name>|<type>|<description>)

2. Identify all relationships:
   - source_entity / target_entity: Entity names
   - relationship_description: Why they are related
   - relationship_strength: Score 1-10
   
   Format: ("relationship"|<source>|<target>|<desc>|<strength>)

-Examples-
[Few-shot examples showing expected output format]

-Real Data-
Entity Types: {entity_types}
Text: {input_text}

プロンプトの特徴:

特徴 説明
構造化出力 パース可能な形式で出力を指定
Few-shot 具体例で期待する出力を示す
エンティティ型 抽出対象を PERSON, ORGANIZATION 等に限定
強度スコア 関係の重要度を数値化

7.2.2 要約生成プロンプト

コミュニティレポート生成プロンプト:

You are an AI assistant that helps a human analyst perform information
discovery within a dataset.

Given a list of entities and their relationships belonging to a community,
write a comprehensive report that includes:

1. Title: A descriptive title for the community
2. Summary: An executive summary of the community's key entities and themes
3. Full Content: A detailed description of all entities and relationships
4. Findings: Key findings with importance scores (1-100)

The report should be useful for understanding the community's structure
and for answering questions about the entities within it.

---Community Data---
{community_data}

説明文要約プロンプト(同一エンティティの複数説明をマージ):

Given multiple descriptions of the same entity from different sources,
write a comprehensive description that:
- Incorporates all unique information
- Resolves any contradictions
- Maintains factual accuracy

Entity: {entity_name}
Descriptions:
{descriptions}

7.2.3 プロンプトチューニング

GraphRAG は 自動プロンプトチューニング 機能を提供しています。

プロンプトチューニング API(api/prompt_tune.py):

async def generate_indexing_prompts(
    config: GraphRagConfig,
    chunk_size: int = 1200,
    limit: int = 15,
    selection_method: DocSelectionType = DocSelectionType.RANDOM,
    domain: str | None = None,           # 自動検出可能
    language: str | None = None,         # 自動検出可能
    discover_entity_types: bool = True,  # エンティティ型を自動発見
) -> tuple[str, str, str]:
    """
    インデックス用プロンプトを生成
    
    Returns:
        - entity_extraction_prompt: エンティティ抽出プロンプト
        - entity_summarization_prompt: 説明文要約プロンプト
        - community_summarization_prompt: コミュニティレポートプロンプト
    """

CLI でのプロンプトチューニング:

# 自動プロンプトチューニングを実行
graphrag prompt-tune --root ./ragtest --discover-entity-types

# 出力されるファイル
# ./ragtest/prompts/
# ├── entity_extraction.txt
# ├── summarize_descriptions.txt
# └── community_report.txt

チューニングプロセス:

7.3 ベクトルストア

GraphRAG は複数のベクトルストアをサポートし、検索時のエンティティマッチングに使用します。

7.3.1 組み込みベクトルストア

LanceDB(デフォルト):

class LanceDBVectorStore(BaseVectorStore):
    """LanceDB ベクトルストア実装"""
    
    def connect(self, **kwargs) -> Any:
        """接続"""
        self.db_connection = lancedb.connect(kwargs["db_uri"])
    
    def load_documents(
        self, documents: list[VectorStoreDocument], overwrite: bool = True
    ) -> None:
        """ドキュメントをロード"""
        # PyArrow テーブルを構築
        data = pa.table({
            self.id_field: pa.array(ids),
            self.text_field: pa.array(texts),
            self.vector_field: vector_column,
            self.attributes_field: pa.array(attributes),
        })
        
        # テーブル作成とインデックス構築
        self.document_collection = self.db_connection.create_table(
            self.index_name, data=data, mode="overwrite"
        )
        self.document_collection.create_index(
            vector_column_name=self.vector_field,
            index_type="IVF_FLAT"
        )
    
    def similarity_search_by_vector(
        self, query_embedding: list[float], k: int = 10
    ) -> list[VectorStoreSearchResult]:
        """ベクトル類似度検索"""
        results = self.document_collection.search(query_embedding).limit(k).to_list()
        return [VectorStoreSearchResult(...) for r in results]

LanceDB の特徴:

特徴 説明
組み込み型 外部サービス不要
高速 Rust 実装で高パフォーマンス
永続化 ローカルファイルに自動保存
インデックス IVF_FLAT 等のインデックスをサポート

7.3.2 外部ベクトルストア連携

対応ベクトルストア:

ストア 設定値 特徴
LanceDB lancedb ローカル、高速、デフォルト
Azure AI Search azure_ai_search スケーラブル、フルマネージド
CosmosDB cosmosdb グローバル分散、NoSQL 統合

ベクトルストアファクトリ(vector_stores/factory.py):

class VectorStoreFactory:
    """ベクトルストアのファクトリ"""
    
    @classmethod
    def register(cls, vector_store_type: str, creator: Callable) -> None:
        """カスタムベクトルストアを登録"""
        cls._registry[vector_store_type] = creator
    
    @classmethod
    def create_vector_store(
        cls,
        vector_store_type: str,
        vector_store_schema_config: VectorStoreSchemaConfig,
        **kwargs,
    ) -> BaseVectorStore:
        """ベクトルストアを生成"""
        return cls._registry[vector_store_type](
            vector_store_schema_config=vector_store_schema_config,
            **kwargs
        )

# 組み込みベクトルストアの登録
VectorStoreFactory.register(VectorStoreType.LanceDB.value, LanceDBVectorStore)
VectorStoreFactory.register(VectorStoreType.AzureAISearch.value, AzureAISearchVectorStore)
VectorStoreFactory.register(VectorStoreType.CosmosDB.value, CosmosDBVectorStore)

Azure AI Search 設定例:

vector_store:
  type: azure_ai_search
  url: https://<search-service>.search.windows.net
  api_key: ${AZURE_SEARCH_API_KEY}
  audience: https://search.azure.com

7.4 設定管理

GraphRAG は YAML 設定ファイル環境変数 で柔軟な設定が可能です。

7.4.1 YAML 設定ファイル

基本構造(graphrag.yaml):

# ルートディレクトリ
root_dir: .

# 入力設定
input:
  type: file
  file_type: text
  base_dir: input
  file_pattern: ".*\\.txt$"

# チャンキング設定
chunks:
  size: 1200
  overlap: 100
  encoding_model: cl100k_base

# モデル設定
models:
  default_chat_model:
    type: openai_chat
    model: gpt-4o-mini
    api_key: ${OPENAI_API_KEY}
  
  default_embedding_model:
    type: openai_embedding
    model: text-embedding-3-small
    api_key: ${OPENAI_API_KEY}

# グラフ抽出設定
extract_graph:
  enabled: true
  entity_types:
    - PERSON
    - ORGANIZATION
    - GEO
    - EVENT

# コミュニティ検出設定
cluster_graph:
  max_cluster_size: 10
  use_lcc: true

# 埋め込み設定
embed_text:
  enabled: true
  names:
    - entity_description
    - text_unit_text

# キャッシュ設定
cache:
  type: file
  base_dir: cache

# ストレージ設定
storage:
  type: file
  base_dir: output

# ベクトルストア設定
vector_store:
  type: lancedb
  db_uri: output/lancedb

GraphRagConfig クラス(config/models/graph_rag_config.py):

class GraphRagConfig(BaseModel):
    """GraphRAG の設定クラス"""
    
    root_dir: str = Field(default=".")
    
    models: dict[str, LanguageModelConfig] = Field(
        description="利用可能なモデル設定"
    )
    
    input: InputConfig = Field(default=InputConfig())
    chunks: ChunkingConfig = Field(default=ChunkingConfig())
    extract_graph: ExtractGraphConfig = Field(default=ExtractGraphConfig())
    cluster_graph: ClusterGraphConfig = Field(default=ClusterGraphConfig())
    cache: CacheConfig = Field(default=CacheConfig())
    storage: StorageConfig = Field(default=StorageConfig())
    vector_store: VectorStoreConfig = Field(default=VectorStoreConfig())
    
    # 検索設定
    local_search: LocalSearchConfig = Field(default=LocalSearchConfig())
    global_search: GlobalSearchConfig = Field(default=GlobalSearchConfig())
    drift_search: DRIFTSearchConfig = Field(default=DRIFTSearchConfig())

7.4.2 環境変数

環境変数は ${VAR_NAME} 構文で YAML 内に埋め込めます。

主要な環境変数:

環境変数 説明 必須
GRAPHRAG_API_KEY OpenAI API キー
GRAPHRAG_API_BASE API ベース URL Azure の場合
GRAPHRAG_API_VERSION Azure API バージョン Azure の場合
AZURE_STORAGE_CONNECTION_STRING Blob Storage 接続文字列 Blob 使用時
AZURE_SEARCH_API_KEY AI Search API キー AI Search 使用時

.env ファイル例:

# OpenAI
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxx

# Azure OpenAI
AZURE_OPENAI_API_KEY=xxxxxxxxxxxxxxxx
AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
AZURE_OPENAI_API_VERSION=2024-02-15-preview

# Azure Storage
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;...

設定の初期化(CLI):

# 設定ファイルを対話的に生成
graphrag init --root ./ragtest

# 生成されるファイル
# ./ragtest/
# ├── graphrag.yaml     # メイン設定ファイル
# ├── .env              # 環境変数(API キー等)
# └── prompts/          # プロンプトテンプレート

次章では、GraphRAG を実際に動かす実践的な手順を解説します。

第8章 実践:GraphRAG を動かす

本章では、GraphRAG を実際に動かすための手順を解説します。環境構築からインデックス作成、クエリ実行、そしてカスタマイズまで、実践的なワークフローを学びましょう。

8.1 環境構築

8.1.1 インストール

pip でのインストール:

# 基本インストール
pip install graphrag

# 開発版(最新機能)
pip install git+https://github.com/microsoft/graphrag.git

必要な依存関係:

パッケージ 用途
openai OpenAI API 接続
tiktoken トークンカウント
networkx グラフ操作
graspologic Leiden アルゴリズム
lancedb ベクトルストア
pandas データ処理
pyarrow Parquet I/O

8.1.2 設定ファイルの準備

プロジェクトの初期化:

# プロジェクトディレクトリを作成
mkdir ragtest && cd ragtest

# GraphRAG を初期化
graphrag init --root .

生成されるファイル構造:

ragtest/
├── settings.yaml           # メイン設定ファイル
├── .env                    # 環境変数(API キー)
├── prompts/                # プロンプトテンプレート
│   ├── extract_graph.txt
│   ├── summarize_descriptions.txt
│   ├── community_report_graph.txt
│   └── ...
└── input/                  # 入力ファイル(要作成)

環境変数の設定(.env):

# OpenAI
GRAPHRAG_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxx

# Azure OpenAI の場合
GRAPHRAG_API_KEY=xxxxxxxxxxxxxxxx
GRAPHRAG_API_BASE=https://your-resource.openai.azure.com/
GRAPHRAG_API_VERSION=2024-02-15-preview

入力ファイルの配置:

# input ディレクトリを作成
mkdir input

# テキストファイルを配置
cp /path/to/your/documents/*.txt input/

8.2 インデックス作成

8.2.1 CLI によるインデックス作成

基本的なインデックス構築:

# Standard パイプライン(LLM ベース)
graphrag index --root .

# Fast パイプライン(NLP ベース / LazyGraphRAG 相当)
graphrag index --root . --method fast

# 詳細ログを表示
graphrag index --root . --verbose

CLI オプション:

オプション 説明 デフォルト
--root プロジェクトルート カレントディレクトリ
--method インデックス手法(standard/fast) standard
--verbose 詳細ログ出力 false
--config 設定ファイルパス settings.yaml
--dry-run 実行せずに設定を検証 false
--output-dir 出力ディレクトリ output

インクリメンタル更新:

# 新しいファイルのみをインデックスに追加
graphrag update --root .

出力ファイル:

output/
├── documents.parquet           # 入力文書
├── text_units.parquet          # テキストチャンク
├── entities.parquet            # エンティティ
├── relationships.parquet       # 関係
├── communities.parquet         # コミュニティ
├── community_reports.parquet   # コミュニティレポート
├── lancedb/                    # ベクトルインデックス
│   └── ...
└── stats.json                  # 統計情報

8.2.2 API によるインデックス作成

Python API を使ったプログラマティックなインデックス構築:

import asyncio
import pandas as pd
from graphrag.config.load_config import load_config
from graphrag.config.enums import IndexingMethod
import graphrag.api as api

async def build_custom_index():
    # 設定をロード
    config = load_config(root_dir="./ragtest")
    
    # インデックス構築を実行
    results = await api.build_index(
        config=config,
        method=IndexingMethod.Standard,  # または IndexingMethod.Fast
        verbose=True,
    )
    
    # 結果を確認
    for result in results:
        print(f"Workflow: {result.workflow}")
        print(f"Status: {'Success' if not result.errors else 'Failed'}")
        if result.errors:
            for error in result.errors:
                print(f"  Error: {error}")
    
    return results

# 実行
asyncio.run(build_custom_index())

カスタム入力データの使用:

import pandas as pd

# 独自のデータフレームを入力として使用
documents = pd.DataFrame({
    "id": ["doc1", "doc2", "doc3"],
    "title": ["Document 1", "Document 2", "Document 3"],
    "text": [
        "GraphRAG is a powerful RAG system...",
        "Microsoft developed GraphRAG for...",
        "LazyGraphRAG reduces costs by...",
    ]
})

results = await api.build_index(
    config=config,
    method=IndexingMethod.Standard,
    input_documents=documents,  # カスタム入力
)

8.3 クエリ実行

8.3.1 Local Search の実行

CLI での実行:

# Local Search
graphrag query --root . --method local --query "GraphRAG の開発者は誰ですか?"

# ストリーミング出力
graphrag query --root . --method local --query "..." --streaming

# コミュニティレベルを指定
graphrag query --root . --method local --query "..." --community-level 2

API での実行:

import asyncio
import pandas as pd
from graphrag.config.load_config import load_config
import graphrag.api as api
from graphrag.utils.storage import load_table_from_storage

async def run_local_search():
    config = load_config(root_dir="./ragtest")
    
    # 必要なテーブルをロード
    storage = create_storage_from_config(config)
    entities = await load_table_from_storage("entities", storage)
    relationships = await load_table_from_storage("relationships", storage)
    text_units = await load_table_from_storage("text_units", storage)
    community_reports = await load_table_from_storage("community_reports", storage)
    
    # Local Search を実行
    response, context_data = await api.local_search(
        config=config,
        entities=entities,
        relationships=relationships,
        text_units=text_units,
        community_reports=community_reports,
        query="GraphRAG の主要な機能は?",
        community_level=2,
        response_type="multiple paragraphs",
    )
    
    print("Response:", response)
    print("Context Data:", context_data.keys())
    
    return response, context_data

asyncio.run(run_local_search())

8.3.2 Global Search の実行

CLI での実行:

# Global Search
graphrag query --root . --method global --query "データセットの主要なテーマは?"

# 動的コミュニティ選択を有効化
graphrag query --root . --method global --query "..." --dynamic-community-selection

API での実行:

async def run_global_search():
    config = load_config(root_dir="./ragtest")
    
    # 必要なテーブルをロード
    entities = await load_table_from_storage("entities", storage)
    communities = await load_table_from_storage("communities", storage)
    community_reports = await load_table_from_storage("community_reports", storage)
    
    # Global Search を実行
    response, context_data = await api.global_search(
        config=config,
        entities=entities,
        communities=communities,
        community_reports=community_reports,
        query="このコーパスの主要なテーマは何ですか?",
        community_level=2,
        dynamic_community_selection=True,
        response_type="multiple paragraphs",
    )
    
    print("Response:", response)
    return response, context_data

8.3.3 DRIFT Search の実行

CLI での実行:

# DRIFT Search
graphrag query --root . --method drift --query "GraphRAG の詳細と業界への影響は?"

API での実行:

async def run_drift_search():
    config = load_config(root_dir="./ragtest")
    
    # 必要なテーブルをロード(Local Search と同じ)
    entities = await load_table_from_storage("entities", storage)
    relationships = await load_table_from_storage("relationships", storage)
    text_units = await load_table_from_storage("text_units", storage)
    community_reports = await load_table_from_storage("community_reports", storage)
    
    # DRIFT Search を実行
    response, context_data = await api.drift_search(
        config=config,
        entities=entities,
        relationships=relationships,
        text_units=text_units,
        community_reports=community_reports,
        query="GraphRAG の技術的詳細と将来の展望は?",
    )
    
    print("Response:", response)
    return response, context_data

8.4 カスタマイズ

8.4.1 独自データソースの追加

CSV からのインポート:

# settings.yaml
input:
  type: file
  file_type: csv
  base_dir: input
  file_pattern: ".*\\.csv$"

CSV ファイルには texttitle カラムが必要:

title,text
"Document 1","This is the content of document 1..."
"Document 2","This is the content of document 2..."

Blob Storage からのインポート:

input:
  type: blob
  container_name: documents
  storage_account_blob_url: https://youraccount.blob.core.windows.net/
  connection_string: ${AZURE_STORAGE_CONNECTION_STRING}

8.4.2 プロンプトのカスタマイズ

プロンプトチューニングの実行:

# 自動プロンプトチューニング
graphrag prompt-tune --root . --discover-entity-types

# ドメインを指定
graphrag prompt-tune --root . --domain "Scientific Research"

# 言語を指定
graphrag prompt-tune --root . --language "Japanese"

手動でのプロンプト編集:

prompts/
├── extract_graph.txt           # エンティティ抽出
├── summarize_descriptions.txt  # 説明文要約
├── community_report_graph.txt  # コミュニティレポート
└── ...

カスタムエンティティ型の定義:

# settings.yaml
extract_graph:
  entity_types:
    - PERSON
    - ORGANIZATION
    - TECHNOLOGY      # カスタム追加
    - RESEARCH_TOPIC  # カスタム追加
    - PUBLICATION     # カスタム追加

第9章 アーキテクチャの設計思想

本章では、GraphRAG の設計思想と、その背後にある技術的決定の理由を解説します。

9.1 なぜグラフ構造なのか

従来の RAG はベクトル類似度に基づく検索を行いますが、これには本質的な限界があります。

ベクトル検索の限界:

課題 説明
局所的類似性 クエリと類似したチャンクしか取得できない
関係の欠如 エンティティ間の関係が失われる
全体像の不在 コーパス全体の構造を把握できない
推論の欠如 複数ホップの推論ができない

グラフ構造の利点:

グラフが提供する情報:

  1. エンティティ:コーパス内の重要な概念・人物・組織
  2. 関係:エンティティ間のつながりと意味
  3. コミュニティ:関連するエンティティのクラスタ
  4. 階層構造:粗い〜細かい粒度の情報

9.2 コミュニティベース要約の効果

GraphRAG の革新的な点は、コミュニティレポート による事前要約です。

コミュニティレポートの効果:

効果 説明
情報圧縮 多数のエンティティを1つのレポートに要約
文脈保持 関連情報をまとめて保持
スケーラビリティ LLM コンテキストウィンドウを効率利用
一貫性 事前に生成されたレポートで回答品質を安定化

従来 RAG vs GraphRAG の比較:

従来の RAG:
クエリ → ベクトル検索 → 上位 k チャンク → LLM で回答
問題: チャンク間の関係が失われる

GraphRAG:
クエリ → コミュニティレポート → Map-Reduce → LLM で回答
利点: 事前要約により全体像を把握可能

9.3 スケーラビリティへの配慮

GraphRAG は大規模データセットでもスケールするよう設計されています。

スケーラビリティの設計:

設計決定 効果
並列処理 Map フェーズで並列 LLM 呼び出し
階層的コミュニティ 粒度を選択して情報量を調整
LLM キャッシュ 再処理を回避してコスト削減
Parquet 形式 列指向で効率的な I/O
LazyGraphRAG インデックス構築コストを 99.9% 削減

スケーリングの目安:

文書数 Standard Fast (LazyGraphRAG)
100 数分 数秒
1,000 数十分 数分
10,000 数時間 数十分
100,000+ 数日 数時間

9.4 拡張性と将来展望

GraphRAG はファクトリパターンにより高い拡張性を持ちます。

拡張ポイント:

# カスタムワークフローの登録
from graphrag.index.workflows.factory import PipelineFactory

def my_custom_workflow(config, context):
    # カスタム処理
    pass

PipelineFactory.register("my_workflow", my_custom_workflow)

# カスタムベクトルストアの登録
from graphrag.vector_stores.factory import VectorStoreFactory

VectorStoreFactory.register("my_vector_store", MyVectorStoreClass)

# カスタムキャッシュの登録
from graphrag.cache.factory import CacheFactory

CacheFactory.register("my_cache", create_my_cache)

将来の展望:

  1. マルチモーダル対応:画像・音声・動画からの知識抽出
  2. リアルタイム更新:ストリーミングデータへの対応
  3. 分散処理:Spark / Dask との統合
  4. エージェント統合:AI エージェントのナレッジベースとして活用
  5. Microsoft Discovery 統合:AI for Science プラットフォームとの連携

おわりに

まとめ

本記事では、Microsoft GraphRAG の全体像を詳細に解説しました。

主要なポイント:

内容
第1章 AI for Science における GraphRAG の必要性
第2章 GraphRAG の基本概念(知識グラフ、コミュニティ検出、検索手法)
第3章 Microsoft GraphRAG のアーキテクチャと Knowledge Model
第4章 インデックス構築の詳細(エンティティ抽出、Leiden アルゴリズム)
第5章 LazyGraphRAG による軽量グラフ構築(NLP ベース、99.9% コスト削減)
第6章 検索手法の詳細(Local/Global/DRIFT Search)
第7章 主要コンポーネント(LLM、キャッシュ、ベクトルストア、設定)
第8章 実践的な使用方法(CLI、API、カスタマイズ)
第9章 アーキテクチャの設計思想

GraphRAG の価値:

  • 高品質な回答:コミュニティレポートによる包括的な文脈
  • 全体像の把握:Global Search でデータセット全体の傾向を把握
  • 詳細な探索:Local Search / DRIFT Search で深い情報を取得
  • 柔軟性:LazyGraphRAG でコストと品質のトレードオフを選択
  • 拡張性:ファクトリパターンによるカスタマイズ可能な設計

参考文献

  1. Microsoft GraphRAG GitHub Repository
  2. LazyGraphRAG: Setting a new standard for quality and cost
  3. Transforming R&D with agentic AI: Introducing Microsoft Discovery
  4. From Local to Global: A Graph RAG Approach to Query-Focused Summarization (原論文)

著者情報

この記事が GraphRAG の理解と実践に役立てば幸いです。質問やフィードバックがあれば、コメント欄でお知らせください。


6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?