論理的な情報検索を実現:PageIndexによる次世代RAGシステムの構築
PageIndexは、従来のVector-Based RAG (Retrieval-Augmented Generation)とは一線を画す、新しい推論ベースのRAGフレームワークです。
従来のRAGでは、ドキュメントを一定のサイズでチャンク(断片)に区切り、それをベクトル化(埋め込み)してデータベースに保存し、質問のベクトルと類似度の高いチャンクを検索していました。
逆に、PageIndexはベクトルを使いません!
ドキュメントの自然な構造(章、節、項など)を活かした階層的なツリー構造(目次のようなもの)を生成します。
このツリー構造に対して、LLMが推論を行いながら、人間が目次や索引をたどるように最も関連性の高い情報(ノード)を検索します。
つまり、「意味が似ているから」ではなく、「論理的にたどってここが一番適切だ」という人間のような検索を実現するわけです。
PageIndexを導入することで、特に長大で構造化されたドキュメント(例
企業の年次報告書、技術マニュアル、法律文書、学術論文)を扱うRAGシステム開発において、以下のようなメリットがあります。
従来のRAGは、ハードチャンキングによって文脈が途中で切断されがちでした。 逆に、PageIndexはドキュメントの論理的な区切り(セクションなど)をノードとして利用するため、文脈の断片化(コンテキストロスト)を防げます。
「類似度 ≠ 関連性」という、ベクトル検索の根本的な課題を克服できます。 例えば、金融レポートで「リスク」という言葉が何度も出てきても、質問の文脈に合った特定のセクションだけをLLMが推論で選び出せます。 逆に、これにより、より正確で根拠に基づいた回答をユーザーに提供できるようになります。
専用のベクトルデータベースや、大規模な埋め込みモデルの運用・管理コストが不要になります。 逆に、これでインフラがシンプルになり、開発者はRAGのロジックそのものに集中できますね!
PageIndexはPythonで利用できます。まずはインストールから。
# PageIndexライブラリをインストールします
%pip install -q --upgrade pageindex
PageIndexのクライアントと、推論に使うLLM(ここではOpenAIの例)を設定します。
import openai
from pageindex import PageIndexClient
# APIキーを安全に設定してください
# PAGEINDEX_API_KEY = "YOUR_PAGEINDEX_API_KEY"
# OPENAI_API_KEY = "YOUR_OPENAI_API_KEY"
pi_client = PageIndexClient(api_key=PAGEINDEX_API_KEY)
# LLM呼び出し関数(例:GPT-4oで推論)
async def call_llm(prompt, model="gpt-4o", temperature=0):
client = openai.AsyncOpenAI(api_key=OPENAI_API_KEY)
response = await client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
temperature=temperature,
)
return response.choices[0].message.content.strip()
PDFなどのドキュメントから、推論に最適なツリー構造(インデックス)を生成します。
# ファイルパスを指定してインデックスを生成
# 例としてPDFファイルを使う場合
pdf_path = "/path/to/your/document.pdf"
print(" ドキュメントのツリー構造生成を開始...")
tree_id = pi_client.generate_tree(
file_path=pdf_path,
# 推論に使うLLMを設定
llm_call=call_llm
)
print(f" ツリー生成が完了しました!ID: {tree_id}")
逆に、このtree_idを使えば、あとはいつでも検索できますね!
生成したツリー構造に対して、質問に基づいた推論ベースの検索と回答生成を行います。
query = "この文書において、2025年の市場リスクについて具体的に言及しているセクションはどこですか?"
print(f"\n 質問: {query}")
# ツリー検索を実行
retrieved_context = pi_client.retrieve(
tree_id=tree_id,
query=query,
llm_call=call_llm,
# 検索でトップKのようなパラメータ設定が不要になるのも特徴です
)
# 取得したコンテキストを使って最終回答を生成
# (ここでは簡略化のためLLM呼び出し関数を流用)
prompt_for_answer = f"以下のコンテキストに基づいて、質問に答えてください。\n\n質問: {query}\n\nコンテキスト:\n{retrieved_context['final_context']}"
final_answer = await call_llm(prompt_for_answer)
print("\n\n 最終回答:")
print(final_answer)
# オプション: 検索に使われたノードやページ情報を表示
print("\n--- 検索詳細 ---")
# 取得されたコンテキストには、関連ページなどの情報も含まれます
for item in retrieved_context['relevant_nodes']:
print(f"- ノードタイトル: {item['title']} (ページ: {item['page_index']})")
このコードでは、LLMが目次をたどるように適切なセクションを探し出し、そのセクションの情報を利用して質問に答えます。
逆に、この「推論ベース」の検索のおかげで、エンジニアはRAGの品質向上に大きく貢献できますよ!