スキップしてメイン コンテンツに移動

RAGの精度が劇的向上:ParentDocumentRetrieverで「文脈切れ」を防ぐ技術

RAGの精度が劇的向上:ParentDocumentRetrieverで「文脈切れ」を防ぐ技術

はじめに:「断片的な報告」に怒る上司の気持ち、今ならわかる

「部長、トラブルです!」「詳しく話せ」
これ、RAG(検索拡張生成)でも同じことが起きています。単純なベクトル検索だと、AIは文書の断片(チャンク)しか読みません。だから「契約解除について」という章は見つけられても、それが「いつ」「誰が」対象なのか、文脈を見失ってしまうのです。

今回は、この問題を解決する ParentDocumentRetriever について解説します。これを入れるだけで、AIの回答が「気が利くベテラン社員」のように的確になります。

基礎知識:なぜ通常のRAGでは精度が出ないのか

通常のRAGは、文書を小さく切って(例:500文字)、それぞれをベクトル化します。
検索時には「類似した断片」は見つかりますが、その断片だけでは答えを作るための情報が欠けていることが多いのです。

ParentDocumentRetrieverは、「検索は小さい単位で行い、AIに読ませるときは親(元の大きな単位)を渡す」という手法を取ります。検索ヒット率を保ちつつ、文脈理解も両立させる、まさに「いいとこ取り」のアプローチです。

実装・設定:ParentDocumentRetrieverの導入手順

実装には「ベクトルストア(検索用)」と「ドキュメントストア(本文保存用)」の2つが必要です。

from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain_chroma import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 1. 小さなチャンクと大きな親ドキュメントを作る設定
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)

# 2. 保存先の用意
vectorstore = Chroma(embedding_function=local_embeddings)
store = InMemoryStore() # 本番ではRedis等を推奨

# 3. Retrieverの作成
retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=store,
    child_splitter=child_splitter,
    parent_splitter=parent_splitter,
)

retriever.add_documents(docs)

これで検索を実行すると、自動的に「親ドキュメント」が返ってきます。

応用テクニック:チャンクサイズの黄金比を探る

「Childサイズ」と「Parentサイズ」のバランスが肝です。
私の経験則ですが、Childは思い切って小さく(200〜400トークン)、ParentはLLMの入力上限が許す限り広く(2000〜4000トークン)取るのが効果的でした。

トラブルシューティング:ストレージ圧迫問題

ParentDocumentRetrieverは、元の文書をそのまま保存するため、ストレージ容量を食います。
メモリ上の InMemoryStore だと、大量のドキュメントを読み込ませた瞬間にサーバーがOOM(メモリ不足)で落ちます。本番環境では必ずRedis等の永続化ストレージを使いましょう。

まとめ:木を見て森も見るAIを作ろう

ParentDocumentRetrieverを導入することで、AIは「木(キーワード)」を見つけ出し、「森(文脈)」を理解して回答できるようになります。

RAGの精度向上に悩んでいるなら、プロンプトをいじくり回す前に、まずこの仕組みを試してみてください。

この記事はAI技術を活用して作成されましたが、内容は慎重に確認されています。

コメント