初めに
PageIndexは、ベクトルDBやチャンキングを使わず、LLMの推論によって階層ツリーインデックスを構築するRAGシステムです。PDF/Markdownから目次のようなツリー構造を自動生成し、ツリー検索で関連ページを特定します。
従来のRAGはベクトル類似度検索に依存しますが、「類似度≠関連度」という問題があります。PageIndexはLLMの推論能力を活用し、人間の専門家がドキュメントをナビゲートするように関連箇所を特定します。
PageIndexの処理は大きく2ステップに分かれます。
- ツリーインデックス構築 — ドキュメントから階層ツリーを生成する
- ツリー検索 — 構築済みツリーをたどって関連ページを特定する
このリポジトリは①のツリーインデックス構築を担当します。本記事ではPDFとMarkdownそれぞれの構築方法を紹介します。
開発環境
環境構築
リポジトリをクローンします。
git clone https://github.com/VectifyAI/PageIndex.git cd PageIndex
依存関係をインストールします。
uv add -r requirements.txt
.envファイルを作成してOpenAI APIキーを設定します。
CHATGPT_API_KEY=your_openai_key_here
PDFの処理
PDF処理では、LLMを複数段階で活用してツリーを構築します。
- PDFからテキストを抽出
- LLMで目次(TOC)ページを検出
- TOCをJSON構造に変換(LLM)
- 各セクションにページ番号を割当(LLM)
- 割当結果を検証・修正(LLM)
- 各ノードのサマリを非同期並列生成(LLM)
各ノードは以下のような構造を持ちます。
{ "title": "INTRODUCTION", "node_id": "0000", "start_index": 1, "end_index": 2, "summary": "This section introduces...", "nodes": [] }
start_index/end_indexがページ範囲、nodesが子ノードの配列です。
サンプルPDF(earthmover.pdf)でツリー構造を生成します。
python3 run_pageindex.py --pdf_path tests/pdfs/earthmover.pdf
results/earthmover_structure.jsonが生成されます。可視化スクリプトで確認すると以下のようなツリーが得られます。
[doc] earthmover.pdf (19 nodes) [0000] INTRODUCTION (p.1-2) [S] [0001] PRELIMINARIES (p.2) [S] ├── [0002] Computing the EMD (p.3) [S] └── [0003] Filter-and-Refinement Framework (p.3-4) [S] [0004] SCALING UP SSP (p.4-5) [S] [0005] BOOSTING THE REFINEMENT PHASE (p.5) [S] ├── [0006] Analysis of EMD Calculation (p.5-8) [S] ├── [0007] Progressive Bounding (p.8-6) [S] ├── [0008] Sensitivity to Refinement Order (p.6-9) [S] ├── [0009] Dynamic Refinement Ordering (p.9-7) [S] └── [0010] Running Upper Bound (p.7-8) [S] [0011] EXPERIMENTAL EVALUATION (p.8) [S] ├── [0012] Performance Improvement (p.8-10) [S] ├── [0013] Scalability Experiments (p.10-11) [S] └── [0014] Parameter Tuning in DRO (p.11-12) [S] [0015] RELATED WORK (p.12) [S] [0016] CONCLUSION (p.12) [S] [0017] ACKNOWLEDGMENT (p.12) [S] [0018] REFERENCES (p.12) [S]
12ページのPDFから19ノードのツリーが構築され、各ノードにページ範囲とサマリ([S])が付与されています。
Markdownの処理
Markdownファイルでもツリー構造を生成できます。PDFとは異なり、ヘッダ階層(#, ##, ###...)から直接ツリーを構築するため、TOC検出やページ割当のステップは不要です。LLMはサマリ生成時のみ使用されます。
python3 run_pageindex.py --md_path tests/md/japanese_sample.md
Markdownの場合はヘッダ階層(#, ##, ###...)からツリーを構築します。
[doc] japanese_sample (25 nodes)
[0000] Webアプリケーション開発ガイド (L1) [S]
├── [0001] 第1章 フロントエンド開発 (L5) [S]
│ ├── [0002] 1.1 HTML/CSSの基礎 (L9) [S]
│ └── [0003] 1.2 JavaScriptフレームワーク (L22) [S]
│ ├── [0004] 1.2.1 React (L26) [S]
│ ├── [0005] 1.2.2 Vue.js (L38) [S]
│ └── [0006] 1.2.3 パフォーマンス最適化 (L42) [S]
├── [0007] 第2章 バックエンド開発 (L51) [S]
│ ├── [0008] 2.1 API設計 (L55) [S]
│ │ ├── [0009] 2.1.1 RESTful API (L59) [S]
│ │ └── [0010] 2.1.2 GraphQL (L69) [S]
│ ├── [0011] 2.2 データベース設計 (L85) [S]
│ │ ├── [0012] 2.2.1 リレーショナルデータベース (L89) [S]
│ │ └── [0013] 2.2.2 NoSQLデータベース (L93) [S]
│ └── [0014] 2.3 認証・認可 (L97) [S]
├── [0015] 第3章 インフラストラクチャ (L106) [S]
│ ├── [0016] 3.1 コンテナ技術 (L110) [S]
│ │ ├── [0017] 3.1.1 Docker (L114) [S]
│ │ └── [0018] 3.1.2 Kubernetes (L128) [S]
│ ├── [0019] 3.2 CI/CDパイプライン (L132) [S]
│ └── [0020] 3.3 監視とオブザーバビリティ (L151) [S]
└── [0021] 第4章 開発プラクティス (L160) [S]
├── [0022] 4.1 テスト戦略 (L164) [S]
├── [0023] 4.2 コードレビュー (L174) [S]
└── [0024] 4.3 アジャイル開発 (L185) [S]
Markdownのヘッダ構造がそのままツリーに反映され、25ノードが生成されています。PDFと異なりページ番号ではなく行番号(L)で位置が示されます。
主なオプション
run_pageindex.pyにはいくつかのオプションがあります。
--if-add-node-summary yes/no— 各ノードにLLM生成のサマリを付与(デフォルト: yes)--if-add-doc-description yes/no— ドキュメント全体の説明文を生成(デフォルト: no)--if-thinning yes/no— Markdown用。トークン数の少ないノードを親に統合してツリーを簡略化(デフォルト: no)--model— 使用するOpenAIモデル(デフォルト: gpt-4o-2024-11-20)--max-pages-per-node— PDF用。1ノードあたりの最大ページ数(デフォルト: 10)--max-tokens-per-node— PDF用。1ノードあたりの最大トークン数(デフォルト: 20000)