RAGの戦略選び:チャンク分割、エージェント型RAG、GraphRAGを使い分けるための完全な意思決定ガイド

Dev.to / 2026/5/21

💬 オピニオンSignals & Early TrendsIdeas & Deep AnalysisTools & Practical Usage

要点

  • この記事では、RAGの回答が不正確だったり幻覚が出たりする主因は、埋め込みモデルやLLMではなく、文書のチャンク分割とリトリーバル設計の不整合にあると主張しています。
  • 200ページ超の法務契約、密度の高い研究論文、大規模な製品マニュアル、長年の企業ナレッジのような「象(エレファント)」は、そのまま扱えません(コンテキスト上限、検索精度、レイテンシ、トークンコスト、検索ノイズが理由です)。
  • 有効なチャンク分割は意味と文脈を保持する必要があり、無作為に切り刻んで意味を壊すのではなく、「象をLEGOの部品にする」考え方が重要だと述べています。
  • このガイドは、主要なチャンク分割手法と、それらのチャンクの上で動くリトリーバル/オーグメンテーション、さらにAgentic RAGやGraphRAGといった高度なアーキテクチャまで含めた意思決定フレームワークを提供するとしています。

はじめに

次のようなシナリオは、多くのRAGビルダーがよく知っています。 パイプラインを組み、ドキュメントを読み込み、質問をすると、答えが間違っている、曖昧だ、あるいは自信満々に幻覚(hallucination)を起こす。情報は知識ベースの中にちゃんとあったはずです。では、何がうまくいかなかったのでしょうか?

ほとんどの場合、問題は埋め込みモデルではありません。それはあなたのLLMでもありません。格納する前にドキュメントをどのように分割したか——あまり評価されていない技術である「チャンク化(chunking)」、そして選んだ検索(retrieval)アーキテクチャが、実際にあなたのクエリの複雑さと適合しているかどうかです。

このブログでは、主要なチャンク化戦略をすべて取り上げます。それらのチャンクの上で検索拡張(augmentation)がどのように機能するかを解説し、さらに高度な2つのアーキテクチャであるAgentic RAGGraphRAGを扱います。そして何よりも、用途に最適な組み合わせが何かを正確に判断できるように、完全な意思決定フレームワークを提供します。

ゾウとレゴのピース

あなたのドキュメントはゾウです。

200ページ以上法律契約、密度の高い調査論文、巨大な製品マニュアル、あるいは何年分ものエンタープライズ知識——いずれも、巨大で複雑、相互に連結されていて、価値ある情報に満ちています。

大規模言語モデルは、一度にそのゾウ全体を効果的に取り込めません。その理由は:

  • コンテキストウィンドウの制限
  • 検索精度の制約
  • レイテンシ(遅延)に関する考慮
  • トークンコストの最適化
  • コンテキストの希釈と検索ノイズ

だからこそ、ゾウは小さなピースに分ける必要があります。

しかし、ここでほとんどのRAGシステムは失敗します。

もしゾウをランダムに切り分けると、意味を壊してしまいます
文が文脈を失います。アイデアは断片化されます。関係性は消えます検索の品質は崩壊します。

良いチャンク化は、テキストを小さくすることが目的ではありません。
意味を保ちながら、検索を効率化することが目的です。

だからこそ、チャンク化は「ゾウをレゴのピースに変換すること」だと理解するとよいのです。

レゴのピースとは:

  • モジュール式 — 各ピースはそれ単体でも成立する
  • 構造化されている — 関連するピース同士がきれいに接続できる
  • 一貫している — 信頼できる検索のために十分標準化されている
  • 意味がある — 各ピースが意味的価値を保持している
  • 組み立て可能(composable) — タスクに必要なピースだけを組み合わせられる

良いチャンク化も同じです。

適切に設計されたチャンクは、小さすぎることで効率的な検索や生成を損ねない範囲で、構造セマンティクス関係性、および周辺のコンテキストを保持するべきです。

RAGシステムにおけるチャンク化の本当の目的は、単にドキュメントを分割することではありません。

チャンク化は、単にドキュメントを小さくすることではありません。

実際の目的は次のとおりです:

  • 保持する — セマンティックな意味
  • 改善する検索の精度
  • 幻覚を減らす
  • コンテキストウィンドウを最適化する
  • 根拠(grounding)の品質を向上させる
  • レイテンシとコストのバランス

実務では:

より良いチャンクは、より良い検索、より良いプロンプト、より良い回答につながります。

目標は次を取得することです:

  • 正しいピースを
  • 正しいコンテキストとともに
  • 正しいセクションから
  • 正しいタイミングで。

これは、効果的な検索拡張生成(Retrieval Augmented Generation, RAG)の土台です。

RAGパイプライン:エンドツーエンド

RAGシステムは、複雑さに関係なく同じ4つの段階の流れに従います。 各段階を理解すると、チャンク化やアーキテクチャの判断が恣意的ではなく、はっきりしたものになります。

段階1:ドキュメント

あなたの生のソース素材:PDF、Wordファイル、Webページ、文字起こし、データベースのエクスポート。LLMにそのまま渡すには大きすぎます。インデックス化や検索の前に、チャンクに分割する必要があります。

段階2:チャンク化と埋め込み

ドキュメントをユニットに切り分け、各ユニットをその意味を数値表現に変換するベクトル埋め込みに変換する。これらの埋め込みはベクトルデータベースに保存され、検索可能なインデックスを形成します。ここでのチャンク化戦略が、その後のすべてを決めます。

段階3:検索

ユーザーが質問すると、クエリも同様に埋め込み化されます。ベクトルデータベースは、クエリの意味に最も近い埋め込みを持つチャンクを返します。これが取得されたレゴのピースです。

段階4:拡張と生成

取得したチャンクに、周辺の親コンテキストを加えてプロンプトを組み立て、LLMに送信します。モデルは、受け取った素材から正確で根拠のある回答を生成します。

コアとなる洞察: 回答の品質は、検索品質に制約され、検索品質はチャンク品質に制約されます。より良いチャンク → より良い検索 → より良い回答。下流のあらゆるアーキテクチャ判断は、この土台の上に構築されています。

1. 固定サイズのチャンク化

最もシンプルで、最も広く使われている戦略です。ドキュメントを、意味、文の境界、ドキュメント構造を考慮せず、トークン数、文字数、または単語数によって等しいサイズのブロックに分割します。

LangChainのメソッド
CharacterTextSplitter: 単一のセパレータで分割します(デフォルトは )。その後、文字数によってchunk_sizeを強制します。

TokenTextSplitter: トークン数で分割します(例:OpenAIモデル向けのtiktokenなどのトークナイザを使用)。文字ベースの分割よりも、LLMのコンテキスト予算に対してより正確です。

返却形式: {"translated": "翻訳されたHTML"}
from langchain.text_splitter import CharacterTextSplitter, TokenTextSplitter

# 文字ベース
splitter = CharacterTextSplitter(
    chunk_size=1000,    # 1チャンクあたりの最大文字数
    chunk_overlap=200,  # チャンク境界で繰り返される文字数
    separator="

"
)

# トークンベース
splitter = TokenTextSplitter(
    chunk_size=512,  # 1チャンクあたりの最大トークン数
    chunk_overlap=50 # チャンク境界で繰り返されるトークン数
)

オーバーラップの目安: 通常は 10〜20% のオーバーラップが一般的です。chunk_size=1000 の場合、chunk_overlap は 100〜200 に設定してください。オーバーラップは、関連する回答が2つのチャンクに分割されるリスクを低減します。その代わりに軽微な冗長性が生じます。

強み: 実装が簡単、速い、予測可能で、スケールしやすいです。
弱み: 文章の途中で頻繁に分割され、その結果、複雑なドキュメントにおける意味の連続性や検索品質が低下します。
向いている用途: ログ、テレメトリ、JSON、CSV、その他の一様な構造化コンテンツ。

2. 再帰的チャンク分割

盲目的に分割するのではなく、再帰的チャンク分割はドキュメントの自然な構造を尊重します。これは、区切り文字の優先順位リスト( 、次に 、次に . / ! / ?, 最後にスペース)に従って処理し、チャンクがサイズ上限をまだ超えている場合に限って、より細かい区切り文字へ進めます。
これは、LangChain においてほとんどのドキュメントタイプの推奨デフォルト戦略です。

LangChain メソッド
RecursiveCharacterTextSplitter: 主な実装です。リスト内の各区切り文字を順に試し、次の区切りへフォールバックします。

RecursiveCharacterTextSplitter.from_language(): 特定のプログラミング言語(Python、JS、Markdown、HTML など)向けにあらかじめ区切り文字リストを設定したものです。

from langchain.text_splitter import RecursiveCharacterTextSplitter, Language

# 一般的な文章
splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=150,
    separators=["

", "
", ".", "!", "?", " ", ""]
)

# 言語を考慮(例: Python のソースコード)
splitter = RecursiveCharacterTextSplitter.from_language(
    language=Language.PYTHON,
    chunk_size=1000,
    chunk_overlap=100
)

オーバーラップの目安: ほとんどの文章では 10〜15% のオーバーラップがうまく機能します。コードの場合は、チャンク間で関数シグネチャが重複してしまうのを避けるため、オーバーラップを低く保ってください(50〜100 トークン)。

強み: 固定サイズでのチャンク分割よりも意味の保持が良い; 汎用的な戦略として優秀; 検索の整合性を高めます。

弱み: 意味を考慮するのではなく構造を考慮するため、ドキュメントのフォーマット品質に性能が依存します。

向いている用途: ドキュメント、PDF、記事、ナレッジベース、Webページ。

3. セマンティックチャンク分割

チャンクをどれくらいの大きさにすべきかを問う代わりに、セマンティックチャンク分割は「どの文が一緒に属するべきか」を問いかけます。
文はベクトル埋め込みに変換され、隣接する文同士の類似度が測定されます。類似度が閾値を下回る場所でチャンク境界が引かれます(これはトピックの切り替わりを示します)。

LangChain メソッド

SemanticChunker(langchain_experimental から)— ブレークポイント検出の戦略として、パーセンタイル、standard_deviation、四分位範囲(interquartile)の3つをサポートします。

from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings

splitter = SemanticChunker(
    embeddings=OpenAIEmbeddings(),
    breakpoint_threshold_type="percentile",  # または "standard_deviation", "interquartile"
    breakpoint_threshold_amount=95           # 類似度の低下の上位 5% が境界になる
)

オーバーラップの目安: セマンティックチャンク分割は固定の chunk_overlap を使いません。境界は意味に基づいて引かれるため、重複(オーバーラップ)はアプローチを損ないます。境界での連続性が必要な場合は、前のチャンクの最後の文を手動で末尾に追記することを検討してください。

強み: 検索の関連性が高い; 意味の連続性が強い; 文脈の精度を重視するシステムに適しています。

弱み: 計算コストが高いです。チャンク分割の時点で埋め込みモデルが必要です。類似度の閾値はデータセットごとに調整が必要です。

向いている用途: エンタープライズのナレッジシステム、研究プラットフォーム、ポリシードキュメント、文脈上の精度を必要とするAIアシスタント。

4. 階層的チャンク分割

2つのレベルのチャンクを作成します。文脈用の大きな親チャンクと、精度用の小さな子チャンクです。

検索(リトリーバル)はまず関連する箇所を見つけるために子レベルを対象にし、その後、親レベルへ拡張して周辺の文脈を返します。これはRAGにおける本質的なトレードオフに直接対応します。小さなチャンクは精度を高め、大きなチャンクは文脈を保持します。

LangChainのメソッド

ParentDocumentRetriever: 親チャンクをドキュメントストアに、子チャンクをベクターストアに保存し、取得時にそれらをリンクします。

from langchain.retrievers import ParentDocumentRetriever
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.storage import InMemoryStore
from langchain_community.vectorstores import Chroma

parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)  # 大きな文脈チャンク
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)    # 精密な取得(リトリーバル)用チャンク
retriever = ParentDocumentRetriever(
    vectorstore=Chroma(embedding_function=embeddings),
    docstore=InMemoryStore(),
    child_splitter=child_splitter,
    parent_splitter=parent_splitter
)

オーバーラップの指針: オーバーラップは子スプリッタにのみ適用してください(通常は10〜15%)。親チャンクは文脈のために丸ごと取得されるため、そこでオーバーラップを入れると価値というよりノイズが増えます。

強み: 文脈を犠牲にせず、取得精度を高く維持できます。長いドキュメントに効果的です。

弱み: インデックス作成と取得がより複雑になります。追加のストレージとオーケストレーションが必要です。

向いている用途: 法務文書、技術マニュアル、書籍、企業向けドキュメント、コンプライアンスシステム。

5. 構造とメタデータを意識したチャンク分割

ドキュメント自身の構造であるタイトルヘッダーセクションテーブル、およびページレイアウトを、ドキュメントを単なるプレーンテキストとして扱うのではなく、自然なチャンク境界として使用します。
特に、レイアウトに意味があり、任意の分割ではそれが壊れてしまうような企業向けPDFや構造化されたレポートでは重要です。

LangChainのメソッド

MarkdownHeaderTextSplitter: Markdownの見出しレベルで分割し、ヘッダーテキストを各チャンクのメタデータとして付与します。

HTMLHeaderTextSplitter: HTMLドキュメントでも同様のパターンで、'<h1>-<h4>' タグで分割します。

from langchain.text_splitter import MarkdownHeaderTextSplitter, HTMLHeaderTextSplitter

# Markdown
md_splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=[
        ("#",   "h1"),
        ("##",  "h2"),
        ("###", "h3"),
    ]
)
chunks = md_splitter.split_text(markdown_text)
# 各チャンクにはメタデータが付与されます: {"h1": "Section Title", "h2": "Subsection"}
# HTML
html_splitter = HTMLHeaderTextSplitter(
    headers_to_split_on=[("h1", "h1"), ("h2", "h2")]
)

オーバーラップの指針: これらのスプリッタは、サイズに基づく境界ではなく、構造上の境界でチャンクを生成します。下流工程のチャンクがまだ大きすぎる場合は、2段目として出力をRecursiveCharacterTextSplitterに流し込み、控えめなオーバーラップ(100〜150文字)を適用してください。

強み: レイアウトのセマンティクスを保持します。テーブルをそのまま保ちます。構造化された企業向けドキュメントの取得品質が向上します。

弱み: 高機能なドキュメントパーサが必要です。パーサの品質がパフォーマンスを直接左右します。

向いている用途: 財務レポート、コンプライアンス文書、技術PDF、医療ドキュメント、企業の記録。

6. ハイブリッドチャンク分割

同一のコーパス内でコンテンツタイプに応じて異なるチャンク分割戦略を適用します。ログには固定サイズ、ドキュメントには再帰的、研究論文にはセマンティック、MarkdownまたはHTMLには構造を意識した分割です。
LangChainには専用のハイブリッドスプリッタはありません。ハイブリッドパイプラインは、上記のビルディングブロックを手作業で組み合わせて作ります。

返却形式: {"translated": "翻訳されたHTML"}
from langchain.text_splitter import (
    TokenTextSplitter,
    RecursiveCharacterTextSplitter,
    MarkdownHeaderTextSplitter,
)
from langchain_experimental.text_splitter import SemanticChunker

def hybrid_chunk(doc):
    content_type = doc.metadata.get("type")

    if content_type == "log":
        return TokenTextSplitter(
            chunk_size=512, chunk_overlap=0
        ).split_documents([doc])

    elif content_type == "markdown":
        return MarkdownHeaderTextSplitter(
            headers_to_split_on=[("#", "h1"), ("##", "h2")]
        ).split_text(doc.page_content)

    elif content_type == "research":
        return SemanticChunker(
            embeddings=embeddings,
            breakpoint_threshold_type="percentile"
        ).split_documents([doc])

    else:
        return RecursiveCharacterTextSplitter(
            chunk_size=1000, chunk_overlap=150
        ).split_documents([doc])

重複のガイダンス:コンテンツタイプに応じて、戦略ごとに重複量を設定します。ログや構造化データ:0 または最小の重複。文章やドキュメント:10〜15%。コード:5〜10%。

強み: 柔軟で適応的。混在コンテンツのコーパス全体でより良いパフォーマンスを発揮します。

弱み: エンジニアリングの複雑さが高くなり、一貫して評価・調整するのが難しくなります。

最適な用途: エンタープライズAIプラットフォーム、 大規模な混在コンテンツコーパス、ナレッジマネジメントシステム、複数ソースの RAG パイプライン。

7. アジェンティック・チャンク(Agentic Chunking)

LLMが、どの情報が一緒に属するべきか、チャンクをどのように形成すべきか、そして取得(リトリーバル)をユーザーの意図にどのように適応させるべきかを動的に判断する、新しいアプローチです。これにより、チャンク分割を静的な前処理から、推論時のクエリを意識した推論へと変換します。
LangChainは、専用のスプリッター・クラスではなく、そのエージェントおよびチェーンの抽象化によってこれをサポートします。

from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
import json

llm = ChatOpenAI(model="gpt-4o", temperature=0)

prompt = PromptTemplate.from_template("""
あなたはドキュメントアナリストです。以下のテキストを、首尾一貫したトピックのセクションに分割してください。
オブジェクトのJSONリストのみを返してください。各オブジェクトには "title""content" キーを含めます。

Text:
{text}
""")

chain = LLMChain(llm=llm, prompt=prompt)

def agentic_chunk(text):
    result = chain.run(text=text)
    return json.loads(result)

重複のガイダンス: 伝統的な意味では適用できません。LLMが意味に基づいて境界を判断するためです。セクション間の連続性を保つには、プロンプト文脈に直前のセクションの簡単な要約を含めてください。

強み: 非常に適応的。強力な意味保持。クエリを意識した取得。

返却形式: {"translated": "翻訳されたHTML"}

弱点: 計算コストとレイテンシが高い;オーケストレーションとガードレールが必要;まだ大規模な本番環境で十分に検証されていない。

向いている用途: AIコパイロット、多エージェントシステム、リサーチアシスタント、エンタープライズの推論ワークフロー。

8. エージェント型RAG

注意: エージェント型チャンク分割(#7)と混同しないでください。

エージェント型チャンク分割は、インデックス作成時にドキュメントをどう分割するかについてです。エージェント型RAGは、クエリ時にLLMが何を取得するか、そして見つけた内容が答えとして十分かどうかをどう判断するかについてです。

標準的なRAGパイプラインは静的です。クエリが入力され、固定された取得ステップが実行され、top-kのチャンクがLLMに渡され、答えが出力されます。エージェント型RAGは、その直線的な流れを崩します。LLMエージェントは、いつ取得するか、何を検索するか、結果が十分かどうか、そして答えを生成する前に、洗練された質問でもう一度再クエリする必要があるかどうかを判断します。

この考え方に基づく一般的なパターンには、取得したドキュメントの関連性をスコアリングし、質が悪い場合はWeb検索にフォールバックするCorrective RAG(CRAG)、およびLLMが自分の出力を振り返り、再度取得が必要かどうかを判断するSelf-RAGがあります。

LangChainメソッド
create_retriever_tool は、任意のリトリーバを、エージェントが必要に応じて呼び出せるツールとして包みます。

AgentExecutor は、LangChainの古典的なエージェントループです。エージェントは、どのツールをいつ呼び出すかを決定します。

LangGraph — 本番のエージェント型RAGに推奨されるアプローチです。モデルが取得を、ノードの状態を持ったグラフ(retrieve → grade → rewrite → retrieve again)として扱い、明示的な条件付きエッジを持ちます。

from langchain.tools.retriever import create_retriever_tool
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
from typing import TypedDict, List
from langchain_core.messages import BaseMessage

llm = ChatOpenAI(model="gpt-4o", temperature=0)

# リトリーバをツールとして包む
retriever_tool = create_retriever_tool(
    retriever=vector_store.as_retriever(search_kwargs={"k": 5}),
    name="search_documents",
    description="関連する情報をナレッジベースから検索します。"
)

# --- LangGraph: Corrective RAGパターン ---
class AgentState(TypedDict):
    question: str
    documents: List[str]
    generation: str
    rewrite_count: int

def retrieve(state: AgentState):
    docs = vector_store.similarity_search(state["question"], k=5)
    return {"documents": docs}

def grade_documents(state: AgentState):
    # LLMが各ドキュメントを関連性について評価し、質の低いものを除外する
    prompt = f"このドキュメントは質問 '{state['question']}'に関連していますか?はいまたはいいえで答えてください。

{{doc}}"
    relevant = [
        doc for doc in state["documents"]
        if "yes" in llm.invoke(prompt.format(doc=doc.page_content)).content.lower()
    ]
    return {"documents": relevant}

返却形式: {"translated": "翻訳されたHTML"}def rewrite_query(state: AgentState):
    # ドキュメントの品質が低かった場合は、再取得する前に質問を言い換える
    rewritten = llm.invoke(
        f"取得を改善するために、この質問を書き換えてください: {state[' question']}"
    ).content
    return {"question": rewritten, "rewrite_count": state["rewrite_count"] + 1}

def generate(state: AgentState):
    context = "

".join(d.page_content for d in state["documents"])
    answer = llm.invoke(
        f"このコンテキストを使って回答してください:
{context}

質問: {state[' question']}"
    ).content
    return {"generation": answer}

def should_rewrite(state: AgentState):
    if len(state["documents"]) == 0 and state["rewrite_count"] < 2:
        return "rewrite"
    return "generate"

# グラフを構築する
workflow = StateGraph(AgentState)
workflow.add_node("retrieve", retrieve)
workflow.add_node("grade", grade_documents)
workflow.add_node("rewrite", rewrite_query)
workflow.add_node("generate", generate)

workflow.set_entry_point("retrieve")
workflow.add_edge("retrieve", "grade")
workflow.add_conditional_edges("grade", should_rewrite, {"rewrite": "rewrite", "generate": "generate"})
workflow.add_edge("rewrite", "retrieve")
workflow.add_edge("generate", END)app = workflow.compile()
result = app.invoke({"question": "GraphRAGのリスクは何ですか?", "rewrite_count": 0})

重なりのガイダンス:重なり(オーバーラップ)は、エージェント自身ではなく、下層のリトリーバのチャンク化戦略に設定されます。エージェント層はチャンク化の上で動作します。ベクターストアに投入するチャンク化戦略に合う重なりを使用してください(通常、再帰的または固定サイズのチャンクでは10〜15%です)。

強み: 単発の検索では失敗しがちな、複数ステップの問い合わせや曖昧な問い合わせを扱えます。初期の検索結果が不十分な場合に自己修正できます。1回のクエリサイクルで複数の検索ソース(ベクタDB、Web検索、SQL)を組み合わせられます。

弱み: 複数回のLLM呼び出しにより、クエリあたりのレイテンシが高くなります。リニアなパイプラインよりデバッグが難しくなります。無限の検索ループを避けるために、グラフ設計に慎重さが必要です。

向いている用途: 複雑なQ&Aシステム、問い合わせがオープンエンドになりがちなエンタープライズ向けコパイロット、リサーチアシスタント、そして検索品質のばらつきが大きい可能性のあるあらゆるパイプライン。

9. GraphRAG

GraphRAGは、もともとMicrosoft Researchによって開発されました。ドキュメントを平坦なテキストの列として扱うことを超えています。テキストを線形な文章(パッセージ)にチャンク分割する代わりに、ドキュメントからエンティティと関係を抽出し、それらをナレッジグラフとして保存します。検索はその後グラフを辿り、複数のソースやドキュメントの複数セクションにまたがって情報をつなげる必要がある質問に答えます。これは、ベクタ検索だけではうまく処理できない種類の問いです。

主な検索モードは2つあります: ローカル検索 は、近傍のグラフノードを辿ることで、特定のエンティティ単位の質問に答えます。グローバル検索 は、インデックス作成時に生成されたコミュニティ要約を用いて、コーパス全体にわたるテーマを統合します。

LangChainのメソッド

LangChainはグラフデータベース(Neo4j、Amazon Neptune、ArangoDB)と統合し、グラフベースのRAGパイプラインを構築するためのツールを提供します。
LLMGraphTransformerはLLMを使ってテキストからエンティティと関係を抽出し、それをグラフドキュメントへ変換します。

Neo4jGraph + GraphCypherQAChain はグラフをNeo4jに保存し、生成されたCypherクエリを通じて自然言語でクエリします。
Neo4jVector — Neo4jバックエンド上でグラフ探索(トラバーサル)とベクタ類似検索を組み合わせるハイブリッドアプローチ。

from langchain_experimental.graph_transformers import LLMGraphTransformer
from langchain_community.graphs import Neo4jGraph
from langchain.chains import GraphCypherQAChain
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o", temperature=0)

# 手順 1: チャンクからエンティティと関係を抽出
transformer = LLMGraphTransformer(llm=llm)
graph_docs = transformer.convert_to_graph_documents(documents)

# 手順 2: Neo4jに格納
graph = Neo4jGraph(
    url="bolt://localhost:7687",
    username="neo4j",
    password="password"
)
graph.add_graph_documents(graph_docs)

# 手順 3: 自然言語でグラフをクエリ
chain = GraphCypherQAChain.from_llm(
    llm=llm,
    graph=graph,
    verbose=True,
    return_intermediate_steps=True
)
response = chain.invoke({"query": "Which authors collaborated with researchers at MIT?"})
For hybrid vector + graph retrieval:
pythonfrom langchain_community.vectorstores import Neo4jVector
from langchain_openai import OpenAIEmbeddings

返却形式: {"translated": "翻訳されたHTML"}# グラフと並行してチャンクをベクトルとして保存する
vector_store = Neo4jVector.from_documents(
    documents,
    embedding=OpenAIEmbeddings(),
    url="bolt://localhost:7687",
    username="neo4j",
    password="password",
    index_name="document_chunks",
    node_label="Chunk",
    embedding_node_property="embedding"
)

retriever = vector_store.as_retriever(search_kwargs={"k": 5})

オーバーラップの指針: GraphRAG は連続性のためにチャンクのオーバーラップに依存しません。エンティティ間の関係が、そのギャップを構造的に埋めます。グラフ抽出の前に文書をプレチャンクする場合は、LLM がそれらを抽出する前に、エンティティの言及が少なくとも1つのチャンクに入るように、RecursiveCharacterTextSplitter を適度なオーバーラップ(100〜150文字)で使用してください。

強み: マルチホップ推論(例:「X に関係するすべてのプロジェクトで、かつ Y にも関連するものを見つける」)に優れています。ベクトル検索では見えない、文書間の関係を提示できます。グローバル検索により、コーパス全体にわたるテーマの統合が可能になります。

弱み: インデックス作成コストと複雑性が大幅に高いこと。グラフの品質は LLM による抽出精度に依存します。複雑なスキーマでは Cypher クエリ生成が脆くなり得ます。ベクトル検索のほうが高速で安価な、単純な事実確認の用途には適していません。

向いているもの: 知識グラフ、リサーチ用のコーパス、コンプライアンスおよび規制システム、密な相互参照を持つエンタープライズ Wiki、そして、複数の文書にまたがる事実をつなげて質問に答える必要があるあらゆる領域。



The Core Trade-Off

よくある誤解は、「より小さいチャンクほど常に検索が良くなる」というものです。実際には、あまりにも小さいチャンクは文脈を失い、意味が分断され、幻覚(ハルシネーション)が増える可能性があります。

チャンク分割は、4つの相反する要因のバランス取りです:

普遍的に最適な戦略はありません。適切な選択は、データの特性、クエリのパターン、検索アーキテクチャ、そしてビジネス要件によって決まります。


Quick Reference


Final Thoughts

最も強力な本番環境向け RAG システムは、稀に単一のチャンク分割戦略だけに依存します。堅牢なアーキテクチャは通常、次のように組み合わせます:

  • 再帰的チャンク分割(recursive chunking):一般的なプローズ向け
  • セマンティック・チャンク分割(semantic chunking):精度が重要なコンテンツ向け
  • 階層的リトリーバル(hierarchical retrieval):長い文書や密な文書向け
  • 構造を意識したパース(structure-aware parsing):エンタープライズ向け PDF 向け
  • ハイブリッド・オーケストレーション(hybrid orchestration):コンテンツの種類が異なる場合

エンタープライズ AI が成熟するにつれて、リトリーバル・アーキテクチャはモデル選定と同じくらい重要になってきています。そして、知的なリトリーバルは知的なチャンク分割から始まります。