LightRAGとPythonで構築する軽量なグラフベースRAGシステム

AI tutorial - IT technology blog
AI tutorial - IT technology blog

現実の課題:全体像を見落とすベクトル検索の限界

標準的なベクトル検索は、単一・独立した情報の検索を高速に処理できます。例えばエラーコードの定義を問い合わせれば、50ミリ秒未満で適切なスニペットを返してくれます。しかし、複数のシステムやサービスにまたがる複雑な質問となると途端に精度が低下します。

例えば、社内ドキュメントボットに「第3四半期の認証アップデートはStripeのWebhookハンドラーにどのような影響を与えますか?」と質問したとしましょう。従来のベクトルパイプラインはクエリを埋め込みベクトル化し、類似するテキストチャンクを探索します。しかし問題は、その答えが単一のファイル内には存在しないことです。答えはPull Requestのディスカッション、アーキテクチャのRFC、そしてダウンストリームのワーカーハンドラーに分散しています。コサイン類似度によって抽出されるのは文脈の途切れた3つの段落にすぎず、共有される依存関係が見落とされるため、LLMは欠落した関係性を推測せざるを得なくなります。

根本原因:フラットな埋め込みがシステム間の関係性を無視してしまう理由

一般的なRAG構成では、取り込んだドキュメントを固定の500トークン程度のチャンクに分割し、それぞれを個別に埋め込みベクトル化します。これにより、主に2つの盲点が生じます。

  • エンティティトポロジーの喪失:チャンク分割によってコンポーネント間の明示的な関係性が断ち切られます。ベクトル検索では「JWT」と「RS256」が同じ段落に含まれていることは特定できますが、AuthServiceでの無効な署名が3ホップ離れたBillingWorkerにどのように波及するかを確実に追跡することはできません。
  • コーパス全体にまたがる問いに対する盲点:フラットなベクトルは局所的な検索を得意としますが、「全14のマイクロサービス全体で最も頻発しているタイムアウト原因のトップ3は何か?」といった集約的な質問には対応できません。そのような全体像を含む単一のチャンクは存在しないためです。

アーキテクチャ比較:Vector RAG vs. GraphRAG vs. LightRAG

ナレッジグラフは、関係性をエンティティグラフに直接マッピングすることで、こうした可視性のギャップを解消します。以下は、主要な3つの検索アーキテクチャの実用上の違いです。

アーキテクチャ 関係性のトラバーサル インデックス作成コスト(10万トークンあたり) 増分更新
Standard Vector RAG なし(独立したチャンク) 約$0.002(埋め込みのみ) 即時追加可能
Microsoft GraphRAG 階層構造(Leidenコミュニティ検出) 約$8.00〜$25.00+(多数のLLMパスが必要) グラフ階層の再クラスタリングが必要
LightRAG 2段階(エンティティ+テーマグラフ) 約$0.15〜$0.40(シングルパスでの抽出) ネイティブなドキュメント単位の追加に対応

Microsoft GraphRAGは、データセット全体にわたって包括的かつ階層的なコミュニティサマリーを生成します。その深層的な分析力はマクロ経済分析などに適していますが、500ページのエンジニアリングWikiに対して再帰的クラスタリングを実行すると、1回のビルドで数百万トークンを消費してしまうことがあります。さらに不都合なことに、新たに5件のPull Requestを追加するだけでも、その高コストなクラスタリング手順を最初から再実行しなければならない場合が多いのです。

一方、LightRAGはより無駄のないアプローチを採用しています。主要なエンティティと関係性をシングルパスで抽出し、検索を「低レベルのエンティティ検索」と「高レベルのテーマ検索」の2層に分割します。これにより、API予算を浪費することなく、コンポーネント間の関係性を考慮した高精度な回答を得ることができます。

実装:PythonでLightRAGパイプラインを構築する

PythonとOpenAI互換のエンドポイントを使用すれば、実用的なGraph RAGプロトタイプを構築できます。以下の手順に従って、ストレージの設定、リレーショナルデータのインデックス作成、および2段階クエリのテストを行いましょう。

ステップ1:依存関係のインストール

専用の仮想環境を作成し、公式のOpenAI SDKとともにlightrag-hkuパッケージをインストールします。

python3 -m venv venv
source venv/bin/activate
pip install lightrag-hku openai

ステップ2:ナレッジグラフエンジンの初期化

以下のスクリプトをgraph_rag.pyとして保存します。関係性の抽出用には非同期LLMラッパーを、ノードマッチング用には標準の埋め込み関数を設定してLightRAGを構成します。

import os
import asyncio
from lightrag import LightRAG, QueryParam
from lightrag.llm import openai_complete_if_cache, openai_embedding
from lightrag.utils import EmbeddingFunc

# API認証情報の設定
os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "sk-your-key-here")

WORKING_DIR = "./lightrag_storage"
os.makedirs(WORKING_DIR, exist_ok=True)

async def llm_model_func(prompt, system_prompt=None, history_messages=[], **kwargs):
    return await openai_complete_if_cache(
        "gpt-4o-mini",
        prompt,
        system_prompt=system_prompt,
        history_messages=history_messages,
        **kwargs
    )

async def embedding_func(texts: list[str]):
    return await openai_embedding(
        texts,
        model="text-embedding-3-small"
    )

rag = LightRAG(
    working_dir=WORKING_DIR,
    llm_model_func=llm_model_func,
    embedding_func=EmbeddingFunc(
        embedding_dim=1536,
        max_token_size=8192,
        func=embedding_func
    )
)

ステップ3:相互に関連するテキストのインデックス作成

次に、相互依存関係を持つアーキテクチャドキュメントをエンジンに投入します。LightRAGはAuthServiceやPaymentGatewayなどのエンティティを抽出し、それらの関係性を推論して、グラフデータをローカルに永続化します。

async def index_docs():
    sample_doc = """
    AuthServiceはOAuth2トークンとセッション検証を処理します。第3四半期にAuthServiceがアップデートされ、
    非対称RS256署名による有効期間の短いJWTを発行するようになりました。
    
    PaymentGatewayはStripeからWebhookイベントを受信します。イベントの発信元を検証するため、PaymentGatewayは
    請求イベントをディスパッチする前にAuthServiceの公開鍵を介してユーザーの身元を検証します。
    
    BillingServiceはPaymentGatewayのイベントをリッスンします。AuthServiceがセッションを拒否した場合、
    BillingServiceは手動の不正レビュー対象としてトランザクションにフラグを立てます。
    """
    
    print("ドキュメントをインデックス化し、グラフノードを抽出中...")
    await rag.ainsert(sample_doc)
    print("グラフの抽出が完了しました。")

ステップ4:ローカルおよびグローバルコンテキストを横断するクエリ実行

LightRAGには、質問の種類に応じた4つのクエリモードが用意されています。

  • local: 特定のエンティティを中心とした直接的な1ホップの接続を走査します(例:「AuthServiceはどの署名アルゴリズムを使用していますか?」)。
  • global: コーパス全体にわたる広範な関係性クラスターをスキャンします(例:「支払い処理中に認証が失敗した場合のフォールバックフローの概要を説明してください。」)。
  • hybrid: エンティティグラフのトラバーサルと標準的なベクトル検索を統合し、バランスの取れたカバレッジを実現します。
  • naive: パフォーマンス比較(A/Bテスト)のための基本的なチャンクベースのベクトル検索を実行します。
async def query_pipeline():
    question = "Stripe決済中にAuthServiceがセッションを拒否した場合、すべてのダウンストリームサービスで何が起こりますか?"
    
    print("\n--- ハイブリッドモードでクエリを実行中 ---")
    response = await rag.aquery(
        question,
        param=QueryParam(mode="hybrid")
    )
    print(response)

async def main():
    await index_docs()
    await query_pipeline()

if __name__ == "__main__":
    asyncio.run(main())

ステップ5:実行と検証

ターミナルからスクリプトを実行します。

python graph_rag.py

初回の実行時、LightRAGは指定された作業ディレクトリ内にグラフデータをkv_store_*.jsonおよびgraph_chunk_entity_relation.graphmlとして書き出します。

本番環境への導入における考慮事項

ローカルプロトタイプから本番環境のデプロイへと移行する際には、インフラストラクチャにおけるいくつかのトレードオフを検討する必要があります。

  • グラフストレージの制限:LightRAGはデフォルトでJSONとして保存されるインメモリのNetworkXグラフを使用します。データセットが50,000エンティティを超える場合は、Neo4jやMemgraphなどの外部グラフエンジンへの移行と、QdrantやMilvusなどのベクトルストアの併用を検討してください。
  • 抽出コストの最適化:ノードの抽出処理は、データ取り込みにかかるコストの85%以上を占めます。gpt-4o-miniのような軽量モデルや、Ollama経由でセルフホストするQwen 2.5 14Bインスタンスを採用することで、ドキュメント1件あたりのインデックス作成コストを1セント未満に抑えることができます。
  • 完全な増分書き込み:LightRAGは階層的なコミュニティの再インデックス化を回避するため、新規データに対してrag.ainsert()を呼び出すだけで、パイプラインをロックすることなく新しいノードとエッジを既存のグラフに直接マージできます。
Share: