はじめに
企業のIR資料や技術文書、ビジネス文書には、財務数値、因果関係、時系列イベント、人物・組織の関係性、意味的な概念のつながりなど、多次元の情報が含まれています。従来のRAG(Retrieval-Augmented Generation)はドキュメントをテキストチャンクに分割してベクトル検索を行いますが、この方法ではこれらの構造的な情報が失われてしまいます。
Graph RAG やナレッジグラフは構造化情報を保持する手段として注目されていますが、すべての情報を1つのグラフに詰め込もうとすると、ノードとエッジの爆発的増加、異種情報の混在、クエリの複雑化といった問題に直面します。
本記事では、この課題に対するアプローチとして**Temporal Multimodal Multigraph RAG(TM2RAG)**を紹介します。ドキュメントから抽出した情報を5種類の専用グラフ(エンティティ、因果、時系列、セマンティック、ペルソナ)に分割し、メタグラフとインタードキュメントグラフで統合する設計です。この記事を読むと、以下のことが理解できます。
Temporal Multimodal Multigraph RAG(TM2RAG)のレポジトリ
https://github.com/shibuiwilliam/temporal-multimodal-multigraph-rag
- 単一ナレッジグラフの限界と、マルチグラフ分割が必要な理由
- 5種類のグラフそれぞれの役割と構造
- メタグラフ・インタードキュメントグラフによるグラフ間連携の仕組み
- マルチモーダル対応のドキュメント取り込みパイプライン
- インテント(意図)ベースのAgentic RAGによる検索・回答生成
参考論文
- MAMGA — Multimodal Attributed Multigraph Attention
- DeepImageSearch — Deep Image Search framework
単一ナレッジグラフの限界
すべてを1つのグラフに入れる問題
ナレッジグラフでドキュメントの情報を管理する場合、最も直感的なアプローチは1つの大きなグラフにすべてを格納することです。しかし、実際の業務ドキュメントに適用すると、以下の問題が顕在化します。
ノードとエッジの型の爆発
1つのグラフにエンティティ(人、組織、製品)、因果関係(原因→結果)、時系列イベント、抽象的な概念、ペルソナごとの洞察をすべて入れると、ノードの型とエッジの型が膨大になります。トヨタのIR資料1つを処理するだけでも、数百のエンティティ、数十の因果関係、数百の時系列イベント、数十の概念が抽出されます。これらを単一グラフに入れると、「組織→owns→製品」と「原因→causes→結果」と「年→contains→イベント」が同居し、グラフの可読性と検索効率が著しく低下します。
クエリの意味的曖昧性
「トヨタの売上はなぜ増加したか?」という質問には因果グラフが最適です。「2023年に何が起きたか?」には時系列グラフが最適です。単一グラフでは、クエリの意図に応じた探索戦略を切り替えることが困難です。すべてのノードとエッジを対象にBFS/DFSを行うと、無関係な情報が大量にヒットします。
グラフ制約の矛盾
因果グラフはDAG(有向非巡回グラフ)であるべきです。「A が B を引き起こし、B が A を引き起こす」は論理的に矛盾するため、サイクルは排除する必要があります。一方、エンティティグラフでは「A が B を所有し、B が A に出資する」のような双方向関係は正当です。時系列グラフは木構造(年→月→イベント)が自然です。これらの異なる制約を単一グラフで同時に満たすことはできません。
スケーラビリティの壁
ドキュメント数が増えるに従い、単一グラフのノード数は線形以上に増加します。全ノードに対するグラフ探索のコストが上がり、応答時間が悪化します。
マルチグラフによる解決
TM2RAGでは、1つのドキュメントから抽出した情報を5種類の専用グラフに分割します。各グラフは特定の質問タイプに最適化された構造を持ちます。
| グラフ種別 | 回答する質問 | グラフ構造 |
|---|---|---|
| エンティティグラフ | Who / What(誰が・何が) | 有向グラフ |
| 因果グラフ | Why(なぜ) | DAG(有向非巡回グラフ) |
| 時系列グラフ | When(いつ) | 木構造(年→月→イベント) |
| セマンティックグラフ | How related(どう関連) | 有向グラフ |
| ペルソナグラフ | For whom(誰向け) | 有向グラフ |
これらのグラフをメタグラフで文書内統合し、インタードキュメントグラフで文書間統合することで、単一グラフの問題を回避しつつ、グラフ間の横断的な検索を可能にしています。
5種類のグラフの役割と構造
エンティティグラフ — Who / What を捉える
エンティティグラフは、ドキュメント中に登場する名前付きエンティティ(人物、組織、製品、場所、数値など)とその関係性を格納します。
上の図は、企業IR資料から抽出されたエンティティグラフの例です。組織(企業)を中心に、所有関係(owns)、生産関係(produces)、財務指標(has_financial_metric)、提携関係(partners_with)などのエッジでエンティティが結ばれています。
ノードには8種類の型があります。
-
PERSON— 人物(CEO、役員など) -
ORGANIZATION— 組織(企業、団体など) -
LOCATION— 場所 -
DATE— 日付 -
PRODUCT— 製品・サービス -
EVENT— イベント -
CONCEPT— 概念 -
NUMERIC— 数値(売上高、利益率など)
各ノードは source_chunk_ids と source_page_numbers を保持しており、元ドキュメントのどのページから抽出されたかを追跡できます。エッジには relation_type、description、confidence(信頼度スコア)が付与されます。
以下はエンティティグラフのノード検索の実装例です。完全一致のほか、あいまい検索(部分文字列マッチ)にも対応しています。
def find_entity_by_name(self, name: str) -> Optional[str]:
"""名前でエンティティを検索する(大文字小文字を無視)"""
name_lower = name.lower()
for node_id, attrs in self.graph.nodes(data=True):
if attrs.get("name", "").lower() == name_lower:
return node_id
return None
def find_entities_by_name_fuzzy(self, name: str) -> list[str]:
"""部分文字列でエンティティをあいまい検索する"""
name_lower = name.lower()
results = []
for node_id, attrs in self.graph.nodes(data=True):
node_name = attrs.get("name", "").lower()
if name_lower in node_name or node_name in name_lower:
results.append(node_id)
return results
因果グラフ — Why を捉える
因果グラフは、ドキュメント中の因果関係(原因→結果)をDAG(有向非巡回グラフ)として格納します。「なぜ売上が増加したのか」「この施策はどのような影響をもたらしたか」といった質問に対応します。
上の図では、各ノードがイベントや状態を表し、causes エッジで因果関係が接続されています。たとえば「資源価格の上昇および円安が進行した」→ causes →「2022年度の在庫影響が前期比で3,355億円減少した」のような因果チェーンが構築されます。
因果グラフの重要な制約はDAGの強制です。ビルド時にサイクル検出を行い、循環が見つかった場合は GraphCycleError を発生させます。
import networkx as nx
# 因果グラフ構築後にDAG検証
if not nx.is_directed_acyclic_graph(self.graph):
raise GraphCycleError("因果グラフにサイクルが検出されました")
因果チェーンの探索では、2点間のすべての単純パスを取得する find_causal_chain や、根本原因を特定する get_root_causes(入次数0の祖先ノード)が利用できます。各エッジには strength(0.0〜1.0)と conditions(条件のリスト)が付与され、因果関係の強さと条件を表現します。
時系列グラフ — When を捉える
時系列グラフは、ドキュメント中の時間的イベントを木構造で管理します。ROOT → YEAR → MONTH → EVENT の階層を持ち、イベントを時間軸で整理します。
上の図では、年ノード(例: 2022)から月ノード(例: 2022-04)へ、さらに個々のイベントノードへと contains エッジで分岐する木構造が見て取れます。この構造により、「2022年に何が起きたか」「2022年4月のイベント」といった時間範囲クエリを効率的に処理できます。
def query_time_range(self, start: datetime, end: datetime) -> list[str]:
"""指定した期間内のイベントを取得する"""
results = []
for node_id, attrs in self.graph.nodes(data=True):
timestamp = attrs.get("timestamp")
if timestamp and start <= timestamp <= end:
results.append(node_id)
return sorted(results, key=lambda n: self.graph.nodes[n]["timestamp"])
日付が特定できないイベントは __undated__ ブランチに格納されます。各イベントノードには timestamp、description、related_entities(関連エンティティのID)が含まれ、エンティティグラフとの紐付けが可能です。
セマンティックグラフ — How related を捉える
セマンティックグラフは、ドキュメント中の概念的な関係性を格納します。「電気自動車」と「自動運転」の関係、「クラウド」と「オンプレミス」の対比のような意味的つながりを表現します。
エッジには以下の5種類の関係型があります。
| 関係型 | 意味 | 例 |
|---|---|---|
IS_A |
上位-下位関係 | 電気自動車 IS_A 自動車 |
PART_OF |
部分-全体関係 | バッテリー PART_OF 電気自動車 |
SIMILAR_TO |
類似関係 | ハイブリッド車 SIMILAR_TO 電気自動車 |
RELATED_TO |
一般的な関連 | 充電インフラ RELATED_TO 電気自動車 |
OPPOSITE_OF |
対立関係 | ガソリン車 OPPOSITE_OF 電気自動車 |
各エッジには similarity_score が付与され、概念間の類似度を数値で表現します。概念の階層構造を辿る get_concept_hierarchy メソッドにより、IS_A / PART_OF 関係を通じた上位・下位概念の探索が可能です。
ペルソナグラフ — For whom を捉える
ペルソナグラフは、同じ情報でも読者の立場(ペルソナ)によって重要度や解釈が異なることを捉えるグラフです。6種類のペルソナ(経営層、コンサルタント、アナリスト、ライター、エコノミスト、一般)それぞれの視点から、洞察(Insight)、優先事項(Priority)、アクションアイテム(Action Item)を格納します。
上の図では、ペルソナノード(例: analyst)から HAS_INSIGHT、INTERESTED_IN、RANKS などのエッジで洞察ノードやアクションアイテムノードに接続されています。さらに DERIVED_FROM エッジで他のグラフ(因果グラフや時系列グラフ)のノードを参照しています。
ペルソナグラフの特徴的な点は他のグラフへの参照です。クロスグラフ参照には名前空間付きID({graph_type}::{node_id})を使用します。たとえば causal::node_123 は因果グラフのノード123を指します。INTERESTED_IN エッジには relevance スコア(0.3以上のみ保存)が付与され、そのペルソナにとっての関連度を示します。
メタグラフとインタードキュメントグラフによる統合
メタグラフ — 文書内のグラフ統合
5種類のグラフはそれぞれ独立していますが、実際のクエリではグラフ横断的な検索が必要です。たとえば「トヨタの売上増加の原因となった2023年のイベント」という質問は、エンティティグラフ(トヨタ)、因果グラフ(売上増加の原因)、時系列グラフ(2023年)の3つにまたがります。
メタグラフは、1つのドキュメントに属する5種類のグラフを統合するレイヤーです。
メタグラフは5種類のクロスグラフエッジを生成します。
| エッジ型 | 接続元 → 接続先 | 生成条件 |
|---|---|---|
INVOLVES |
エンティティ → 因果ノード | 因果ノードの related_entity_ids にエンティティが含まれる |
PARTICIPATES_IN |
エンティティ → 時系列イベント | イベントの related_entities にエンティティが含まれる |
TEMPORALLY_RELATED |
因果ノード → 時系列イベント | 共通のエンティティを介して関連 |
CORRESPONDS_TO |
セマンティック概念 → エンティティ | 名前の一致 |
INTERESTED_IN |
ペルソナ → 他グラフノード | ペルソナの関連度マップに基づく |
名前空間付きIDにより、ノードの衝突を回避しています。クロスグラフクエリは BFS で実装されており、起点グラフ・ノードからターゲットグラフのノードへの到達パスを max_hops 以内で探索します。
def cross_graph_query(
self,
start_graph_type: str,
start_node_id: str,
target_graph_type: str,
max_hops: int = 3,
) -> list[str]:
"""起点ノードからターゲットグラフのノードをBFSで探索する"""
start_meta_id = f"{start_graph_type}::{start_node_id}"
if start_meta_id not in self.graph:
return []
visited = set()
queue = [(start_meta_id, 0)]
results = []
while queue:
current, depth = queue.pop(0)
if current in visited or depth > max_hops:
continue
visited.add(current)
if current.startswith(f"{target_graph_type}::"):
results.append(current)
for neighbor in self.graph.neighbors(current):
if neighbor not in visited:
queue.append((neighbor, depth + 1))
return results
インタードキュメントグラフ — 文書間のグラフ統合
複数のドキュメント(たとえばトヨタの2022年と2023年のIR資料)を取り込んだ場合、文書をまたいだエンティティの同一性や概念の共通性を捉える必要があります。インタードキュメントグラフがこの役割を担います。
ノードIDは3階層の名前空間を持ちます: {document_id}::{graph_type}::{original_node_id}
文書間のリンクは5種類のエッジで表現されます。
| エッジ型 | 接続条件 |
|---|---|
SAME_ENTITY |
異なる文書で同一エンティティが出現 |
SHARED_CONCEPT |
セマンティックグラフで同一概念文字列が出現 |
CAUSAL_OVERLAP |
因果ノードが同一の解決済みエンティティを参照 |
TEMPORAL_PROXIMITY |
同月(YYYY-MM)のイベントで共通エンティティあり |
PERSONA_CROSS_REF |
同一ペルソナの洞察ノードが文書間に存在 |
エンティティの同一判定は3段階で行われます。
-
完全一致 —
(name_lower, entity_type)の組み合わせが一致 - 部分文字列マッチ — 3文字以上の部分一致
- LLMベース照合 — Embedding のコサイン類似度(閾値0.75)で候補を絞り、Gemini で最終確認
この3段階マッチングにより、「トヨタ自動車」と「トヨタ」、「Toyota Motor Corporation」と「トヨタ」のような表記揺れに対応しています。
マルチモーダルドキュメント処理パイプライン
TM2RAGはPDFとPPTXをサポートしています。ドキュメントの取り込みは5ステージのパイプラインで処理されます。
ステージ1: パース
PdfParser / PptxParser がドキュメントを解析し、ページごとに TextBlock、ImageBlock、TableBlock を抽出します。テキストだけでなく、画像やテーブルもブロック単位で構造的に取得します。
ステージ2: メタデータ抽出
LLMを使ってタイトル、著者などのメタデータを推論します。ファイルのメタデータに含まれていない場合でも、ドキュメントの内容からLLMが推定します。
ステージ3: LLM/VLM抽出(並列処理)
3種類の抽出を asyncio.gather で並列実行します。
- ImageExtractor — VLM(Vision Language Model)が画像の説明文を生成
- TableExtractor — LLMがテーブルを正規化(ヘッダー、行、キャプション)
- TextExtractor — LLMがテキスト構造を分析
PDFのグラフや図表も、VLMにより自然言語の説明に変換されます。これにより、テキストだけでなく視覚的な情報もグラフ構築の入力として利用できます。
ステージ4: セマンティックチャンキング
文境界でテキストを分割し、オーバーラップ付きのチャンクを生成します。各チャンクには content、chunk_type(text / table / image_description)、page_number、position が付与されます。
ステージ5: Embedding生成
Gemini の gemini-embedding-001 モデルで768次元のベクトルを生成します。生成されたベクトルはPostgreSQL(pgvector)とFAISSインデックスの両方に保存されます。
9エージェントによるグラフ構築ワークフロー
ドキュメントの取り込みが完了すると、9つの専門エージェントが段階的にグラフを構築します。
Phase 1 ではエンティティの抽出とWeb情報による補強を逐次実行します。エンティティが確定した後、Phase 2 で関係・因果・時系列・セマンティックの4エージェントを並列実行します。これらのエージェントは互いに独立しているため、並列化によりレイテンシを削減できます。
Phase 3 の ValidationAgent は重複排除、IDの再マッピング、因果グラフのサイクル除去を行います。Phase 4 の PersonaAgent は6種類のペルソナそれぞれについてLLMを呼び出し、洞察と優先事項を生成します。
エージェントの失敗は致命的ではありません。1つのエージェントが失敗しても、オーケストレーターは部分的な結果で処理を続行します。この耐障害設計により、LLMの一時的なエラーでパイプライン全体が失敗することを防いでいます。
インテントベースのAgentic RAG
TM2RAGのRAGパイプラインは、クエリの意図を解析し、意図に応じて探索するグラフを動的にルーティングします。
クエリ解析とインテント分類
最初にLLMがクエリを解析し、6種類のインテントに分類します。
# クエリ解析の出力構造
class QueryAnalysis:
intent: str # FACTUAL / CAUSAL / TEMPORAL / COMPARATIVE / EXPLORATORY / AGGREGATE
entities: list # クエリ中のエンティティ
temporal_reference: str # 時間参照(例: "2023年", "直近3年")
sub_queries: list # 分解されたサブクエリ
インテントルーティング
分類されたインテントに基づいて、各検索エンジンの重みが動的に設定されます。
| インテント | 主要検索対象 | 重み配分 |
|---|---|---|
| FACTUAL | エンティティグラフ | entity(0.4), vector(0.3), text(0.3) |
| CAUSAL | 因果グラフ | causal(0.5), entity(0.2), vector(0.3) |
| TEMPORAL | 時系列グラフ | temporal(0.5), entity(0.2), vector(0.3) |
| COMPARATIVE | エンティティ + セマンティック | entity(0.25), semantic(0.25), vector(0.3), inter_doc(0.2) |
| EXPLORATORY | メタグラフ | meta(0.3), vector(0.3), text(0.2), semantic(0.2) |
| AGGREGATE | メタグラフ + 時系列 | meta(0.3), temporal(0.25), vector(0.25), inter_doc(0.2) |
さらにペルソナ(読者の立場)による重み補正が適用されます。たとえば経営層(EXECUTIVE)ペルソナでは因果グラフの重みが1.3倍、時系列グラフの重みが1.2倍に補正されます。
ハイブリッド検索とRRFフュージョン
ルーティングされた検索戦略に基づき、複数の検索エンジンが asyncio.gather で並列実行されます。
- FAISSベクトル検索 — クエリEmbeddingとチャンクEmbeddingのコサイン類似度
- HyDE(Hypothetical Document Embeddings) — LLMが仮想的な回答文書を生成し、そのEmbeddingで検索。「なぜ売上が増加したか?」のようなクエリと回答文の語彙ギャップを埋める
- グラフ検索(9種類) — エンティティ近傍探索、因果チェーン探索、時間範囲検索、クロスグラフ探索、文書間検索など
各検索エンジンの結果は Reciprocal Rank Fusion(RRF) で統合されます。
RRF_score(d) = Σ 1 / (k + rank + 1) # k=60, rank は 0 始まり
RRFはスコアのキャリブレーション(FAISSのコサイン類似度とグラフ探索のスコアを直接比較できない問題)を不要にする手法です。各検索エンジンのランキング順位だけを使って統合スコアを算出します。
多様性確保とセレンディピティ
RRFで統合された結果に対して、2つの追加処理が行われます。
MMR(Maximal Marginal Relevance)による多様性選択
MMR(d) = λ × relevance(d) - (1-λ) × max_sim(d, selected)
λ=0.7 で関連性を重視しつつ、選択済み結果との類似性が高い結果を抑制します。これにより、似たような内容の結果が重複して返されることを防ぎます。
セレンディピティインジェクション
メタグラフの媒介中心性(Betweenness Centrality)が高いノード、つまりグラフ間の「橋渡し」をしているノードの中から、既存の検索結果に含まれていないものを最大2件注入します。ユーザーが意図しなかった有用な情報を発見する機会を提供します。
コンテキスト構築と回答生成
検索結果はタイプ別にフォーマットされ、8000トークン(日本語で約16000文字)のバジェット内でコンテキストが構築されます。因果関係は「原因 → 結果」の形式で、時系列は時系列順に、エンティティ関係はグラフ構造を保持した形式で整理されます。
最後にLLMがコンテキストを基に回答を生成します。ペルソナに応じたトーン指示が注入されます(経営層向けなら戦略的フレーミング、アナリスト向けなら定量的フレーミングなど)。
従来のGraph RAGとの比較
TM2RAGと従来のGraph RAG / ナレッジグラフの違いを整理します。
| 比較軸 | 従来のGraph RAG | TM2RAG |
|---|---|---|
| グラフ構造 | 単一のナレッジグラフ | 5種類 + メタ + インタードキュメント |
| 時系列対応 | タイムスタンプ属性 | 専用の木構造グラフ |
| 因果関係 | エッジの1種類として扱う | DAG制約付き専用グラフ |
| ペルソナ対応 | なし | 6ペルソナの専用グラフ |
| クエリルーティング | 固定的な検索戦略 | インテント分類 + ペルソナ補正 |
| マルチモーダル | テキストのみ | PDF/PPTX + VLMによる画像・表解析 |
| 文書間リンク | 手動またはなし | 3段階エンティティマッチング |
| 検索統合 | ベクトル検索 or グラフ探索 | RRF + MMR + セレンディピティ |
| 耐障害性 | パイプライン失敗で中断 | エージェント失敗時も部分結果で続行 |
主要な改善点
1. 質問の種類に応じた最適なグラフ構造
「なぜ?」には因果DAG、「いつ?」には時系列ツリー、「誰が?」にはエンティティグラフと、質問の意図に最適化された構造を持つグラフに対して検索を行います。単一グラフでは不可能な、構造レベルでの最適化です。
2. グラフ制約の明確化
因果グラフにDAG制約を課すことで、循環的な因果関係を排除します。時系列グラフに木構造を課すことで、時間の階層構造を自然に表現します。単一グラフではこれらの異なる制約を同時に適用できません。
3. ペルソナによるパーソナライズ
同じドキュメントでも、経営層が注目する情報とアナリストが注目する情報は異なります。ペルソナグラフにより、読者の立場に応じた情報の優先度付けと回答のトーン調整が実現されます。
4. スケーラブルな文書間統合
インタードキュメントグラフにより、文書数が増えても文書間の関係を効率的に管理できます。3段階エンティティマッチング(完全一致→部分一致→LLM照合)により、表記揺れにも対応した文書横断検索が可能です。
まとめ
本記事では、Temporal Multimodal Multigraph RAG(TM2RAG)のアーキテクチャと実装を紹介しました。要点を振り返ります。
- 単一ナレッジグラフの限界: すべての情報を1つのグラフに入れると、ノード型の爆発、クエリの曖昧性、矛盾するグラフ制約の問題が生じる
- マルチグラフ分割: エンティティ、因果、時系列、セマンティック、ペルソナの5種類に分割し、それぞれに最適な構造と制約を適用する
- メタグラフ・インタードキュメントグラフ: 名前空間付きIDとクロスグラフエッジにより、分割されたグラフを再統合し、横断的な検索を可能にする
- インテントベースルーティング: クエリの意図とペルソナに基づいて検索戦略を動的に最適化し、RRF・MMR・セレンディピティで結果を統合する
- マルチモーダル対応: PDF/PPTXの画像や表もVLMで処理し、テキスト以外の情報もグラフに取り込む
ドキュメントの情報は本質的に多次元です。エンティティの関係、因果の連鎖、時間の流れ、概念のつながり、読者の視点。これらを適切に分離し、必要に応じて統合するマルチグラフアプローチは、従来の単一グラフや単純なチャンクベースのRAGでは到達できない精度と柔軟性を実現します。
ソースコード全体はレポジトリで確認できます。
Docker Compose による環境構築、Streamlit によるUI、FastAPI によるAPIが提供されており、実際にドキュメントをアップロードしてグラフ構築からRAG検索までを試すことができます。




