本記事では、LLMアプリケーション開発で頻出するRAG(Retrieval-Augmented Generation)の実装パターンを整理し、検索精度に直結する「チャンク分割」のベストプラクティスを実務的な観点から解説します。基本構成の理解から、固定長・意味単位・階層型といった分割手法の使い分け、オーバーラップ設計やメタデータ活用まで、実装時に迷いがちなポイントを具体例とともに紹介します。
RAGの基本構成とよくある実装パターン
RAGは「外部知識を検索して回答生成に組み込む」仕組みで、一般的には以下のステップで構成されます。
- ドキュメントの前処理・チャンク分割
- Embeddingモデルによるベクトル化
- ベクトルDBへの格納(Pinecone、Weaviate、pgvectorなど)
- クエリ時の類似検索(Top-K取得)
- 取得したチャンクをプロンプトに埋め込みLLMへ渡す
パターン1:ナイーブRAG
最もシンプルな実装で、クエリをそのままベクトル化し類似検索するだけの構成です。PoC段階では有効ですが、質問とチャンクの表現が乖離していると検索漏れが起きやすい欠点があります。
パターン2:ハイブリッド検索RAG
ベクトル検索とBM25などのキーワード検索を組み合わせ、両方のスコアを統合するパターンです。固有名詞や型番など、意味的類似度だけでは拾いにくい情報の検索精度が向上します。
パターン3:リランキング付きRAG
一次検索でTop-20程度を広めに取得し、Cross-Encoder型のリランカー(例:bge-reranker)で再スコアリングしてからLLMに渡すパターンです。精度は高まりますが、レイテンシとコストが増える点に注意が必要です。
パターン4:クエリ拡張・HyDE
ユーザーの質問をLLMで言い換えたり、仮の回答文(Hypothetical Document)を生成してそれをベクトル化し検索するアプローチです。検索クエリが曖昧な場合に有効です。
チャンク分割のベストプラクティス
RAGの品質はチャンク分割の設計に大きく左右されます。チャンクが大きすぎるとノイズが混入し、小さすぎると文脈が失われます。
1. 固定長分割はまず試す価値があるが過信しない
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", " "]
)
chunks = splitter.split_text(document_text)
句読点や改行を優先的に分割点とするRecursiveCharacterTextSplitterは実務で最も使われる手法です。chunk_sizeは日本語の場合300〜600文字程度、overlapは10〜20%が目安です。
2. 意味単位(セマンティックチャンキング)
見出し・段落・箇条書きなど文書構造を尊重して分割する方法です。Markdownやhtmlの場合は構造タグを利用した分割が有効です。
from langchain.text_splitter import MarkdownHeaderTextSplitter
headers_to_split_on = [("##", "section")]
md_splitter = MarkdownHeaderTextSplitter(headers_to_split_on)
chunks = md_splitter.split_text(markdown_text)
FAQやマニュアルなど構造化された文書はこの方式が特に効果を発揮します。
3. 階層型チャンキング(Parent-Child)
小さなチャンクで検索精度を担保しつつ、ヒットしたチャンクの親チャンク(前後の文脈を含む大きな単位)を実際にLLMへ渡す手法です。検索はピンポイントに、回答生成は文脈豊富にという両立が可能です。
実務Tips: 検索用チャンクは200〜400文字、生成用の親チャンクは1000〜1500文字に設定すると、精度とコストのバランスが取りやすいです。
4. メタデータの付与を怠らない
チャンクには出典URL、更新日、セクション名などのメタデータを必ず付与しましょう。フィルタリング検索や回答の引用表示に利用でき、ハルシネーション対策にも直結します。
chunk_metadata = {
"source": "product_manual_v2.pdf",
"section": "トラブルシューティング",
"updated_at": "2024-10-01"
}
実務での検証ポイント
- chunk_sizeとoverlapは必ずA/Bテストし、検索再現率(Recall@K)で評価する
- 日本語は形態素の区切りが曖昧なため、句読点ベースのseparatorsを調整する
- 画像やテーブルを含むPDFは専用パーサー(Unstructured、PyMuPDFなど)で前処理する
- 定期的に古いチャンクを再埋め込みし、embeddingモデルのバージョン差分を管理する
まとめ
RAGは「とりあえず動く」段階から「実運用に耐える精度」まで引き上げるには、検索パターンの選定とチャンク分割設計の両方を丁寧にチューニングする必要があります。まずはナイーブRAGで動作確認し、ハイブリッド検索・リランキング・階層型チャンキングと段階的に高度化していくアプローチが実務では現実的です。