RAGのChunkingとは?文書分割・Hybrid Search・Rerankerを解説

文書をchunkへ分割しHybrid SearchとRerankerで検索する全体像

文書をchunkへ分割しHybrid SearchとRerankerで検索する全体像

RAG(Retrieval-Augmented Generation、検索拡張生成)の検索精度は、Embedding modelやvector databaseだけで決まりません。元文書をどの単位へ分けるか、keyword検索と意味検索の候補をどう統合するか、上位候補をどのmodelで並べ直すかが互いに影響します。

「chunk sizeは何tokenが正解か」という問いに、すべての文書で使える固定値はありません。質問が求める根拠の長さ、見出しや表などの文書構造、Embedding modelの入力長、後段LLMへ渡せるcontext量を固定し、実queryに近い評価setで比較して決めます。

この記事では、Chunking(文書を検索単位へ分割する処理)、Hybrid Search(複数の検索方式を組み合わせる検索)、Reranker(取得済み候補を詳しく再評価するmodel)を一つのpipelineとして解説します。

3行要約

  • chunkは小さければ良いわけでも、大きければ良いわけでもありません。検索に必要な焦点と、回答に必要なcontextの両方で評価します。
  • Hybrid SearchではBM25(語の頻度と希少性を使うranking手法)とDense Retrievalのscoreを無条件に足さず、正規化したscore fusionか、順位を使うRRFなどで統合します。
  • Rerankerは候補の順番を改善できますが、candidate集合へ入らなかった正解文書を復元できません。candidate recallを先に確認します。

RAG全体のindexing・検索・生成・引用の流れは、RAGとは?LLMに外部知識を使わせる仕組みで解説しています。Dual EncoderやANN(Approximate Nearest Neighbor、近似最近傍探索)の内部は、Embedding検索とは?Dense Retrievalの仕組みをDPR論文から解説を参照してください。

Chunkingが変える3つの単位

Chunkingを理解するには、次の3つを分けます。

単位 役割
source document 更新・権限・引用の正本 就業規則PDF全体
retrieval unit indexへ登録し、検索順位を付ける単位 「在宅勤務の申請」節
generation context LLMへ根拠として渡す単位 申請条件と例外を含む親section

3つを同じにする必要はありません。検索では具体的な質問に近い小さなunitが有利でも、回答には前提や例外を含む広いcontextが必要な場合があります。

たとえば「試用期間中に在宅勤務を申請できるか」という質問を考えます。検索用chunkに「在宅勤務は申請できます」だけが入り、直前の「試用期間中を除く」が別chunkへ分かれると、表面的には高い関連度でも誤解を招く根拠になります。

Dense X Retrieval: What Retrieval Granularity Should We Use?は、document、passage、sentenceなどのretrieval granularity(検索粒度)がretrievalと下流taskの性能へ大きく影響すると報告しました。論文は自己完結した単一factoidを表すpropositionも比較し、対象実験ではfine-grainedなunitが有効でした。

ただし、「最小単位へ分ければ常に良い」という結論ではありません。LongRAGは、短いunitによるcontext喪失と膨大な検索空間を問題として、long-context LLMと長いretrieval unitを組み合わせました。異なる研究が示すのは単一の正解値ではなく、retrieverとreaderを含むsystem全体で粒度を評価する必要性です。

小さすぎるchunkと大きすぎるchunk

chunk sizeを変えると、少なくとも4つの性質が変わります。

観点 小さいchunk 大きいchunk
検索の焦点 queryと一致する話題へ絞りやすい 複数話題が1 vectorへ混ざりやすい
周辺context 主語、条件、例外を失いやすい 前後関係を保ちやすい
index件数 増える 減る
LLM context 不要部分を減らしやすい 1件あたりのtoken消費が増える
重複取得 overlapや近接chunkで増えやすい 同じsourceの重複は減りやすい
更新範囲 局所的に差し替えやすい 小さな変更でも大きなunitを再encodeする

小さすぎるchunk、意味単位のchunk、大きすぎるchunkの比較

小さすぎるchunkでは、「それ」「同制度」「前項」の参照先が消えたり、表のheaderとcellが離れたりします。短いtextは検索queryへ近く見えても、回答に必要な意味を単独で持たない場合があります。

大きすぎるchunkでは、休暇、給与、在宅勤務、情報securityが一つのvectorへ圧縮されます。queryに関係しない文章もLLMへ入り、context tokenを消費します。context windowへ入るからといって、すべての情報を同様に使えるとは限りません。Lost in the Middleは、対象modelとtaskで関連情報の位置を変えると性能が変化し、長いcontextの中間で低下する傾向を報告しました。

設計目標は「最小chunk」でも「最大chunk」でもなく、次の2条件を同時に満たす単位です。

  1. 検索時に、queryとgold evidence(正解根拠)の関連度を表現できる。
  2. 生成時に、条件・例外・出典を失わず回答できる。

この2条件が衝突するなら、検索用child chunkと生成用parent contextを分けます。

固定長Chunkingとoverlapを数値で理解する

最も単純な方法は、token列を一定長で区切るfixed-size chunkingです。chunk lengthを \(L\)、次のchunk開始位置までのstride(移動幅)を \(S\) とすると、overlap \(O\) は次です。

\[O=L-S\]

文書長を \(T\) とし、末尾の短いchunkも残す場合、\(T>L\) でのおおよそのchunk数 \(N\) は次のように表せます。

\[N=1+\left\lceil\frac{T-L}{S}\right\rceil\]

たとえば \(T=1600\) token、\(L=400\) token、\(O=100\) tokenなら、\(S=300\) tokenでchunk数は5です。各chunkを400 tokenとして保存すると、indexへ渡す延べ長は最大2000 tokenになります。元文書1600 tokenとの差は、境界を守るために重複させた部分です。

Parameter
文書長 \(T\) 1600 token
chunk length \(L\) 400 token
overlap \(O\) 100 token
stride \(S=L-O\) 300 token
chunk数 \(N\) 5
延べindex対象 最大2000 token

文書境界で条件が切れる場合とoverlapで条件が残る場合

overlapは、answer spanが境界をまたぐ失敗を減らす保険になります。一方で、増やすほど次のcostも増えます。

  • Embedding計算量とindex容量
  • ほぼ同じchunkがtop-kを占める重複
  • LLMへ同じ文章を複数回渡すtoken cost
  • 評価時に同一sourceの近接chunkを複数正解として数える難しさ

overlap率だけで決めず、「境界をまたぐgold evidenceのHit@kがどれだけ改善し、重複率とcostがどれだけ増えたか」を測ります。

文字数とtoken数を混同しない

日本語の1文字が常に1 tokenになるわけではありません。Embedding modelごとにtokenizerと最大入力長が異なります。文字数で分割しても、encode直前に実token数を測り、切り捨てが発生していないか確認します。

また、固定長分割は再現しやすいbaselineですが、見出し、段落、list、表、code blockを途中で切ります。baselineとして測った後、文書構造を使う方式と比較します。

Chunking方式を同じ軸で比較する

固定長、見出し、Semantic、Proposition、Late Chunkingの違い

固定長・recursive splitting

fixed-sizeは指定token数で機械的に切ります。recursive splittingは、見出し、段落、改行、文、文字のようなseparatorを優先順に試し、上限へ収めます。

利点は実装と再現が容易なことです。弱点は、意味のまとまりがseparatorと一致する保証がないことです。PDF抽出で改行が崩れていれば、段落区切り自体が信用できません。

見出し・section単位

MarkdownやHTMLのheading、PDFの章番号などを使い、sectionを意味単位として保ちます。heading_path=("休暇", "年次有給休暇", "申請期限")のようなmetadataを付けると、retrievalとcitationの両方で文書内の位置を説明できます。

長いsectionは内部で追加分割します。ただし、子chunkへheading textを複製するか、metadataだけで持つかは、Embedding modelと検索方式を固定して評価します。

Semantic chunking

隣接文のEmbedding距離やtopic変化を使い、意味が変わる位置で分割します。文字数だけでは捉えにくい話題境界を使える一方、境界判定model、threshold、計算costが増えます。Embedding modelを更新すると境界まで変わり得るため、chunker versionを残します。

Proposition・fact単位

文章を自己完結したfactへ書き換えてindex化する方法です。Dense X Retrievalはpropositionをretrieval unitとして提案しました。queryと個別factを対応させやすい反面、proposition生成の誤り、否定・条件の脱落、元文書へのtraceが新しいriskになります。

原文を置き換える正本として使わず、検索用派生dataとしてsource spanと対応付けます。

Parent-child retrieval

小さいchild chunkを検索し、採用時に親sectionをLLMへ渡します。

検索:  「申請期限は利用日の3営業日前」  ← child chunk
                         │ parent_id
生成:  「在宅勤務申請」section全体      ← parent context

検索の焦点と回答contextを分離できる一方、親が大きすぎればnoiseが戻ります。同じparentに属する複数childが上位へ来た場合はdeduplicate(重複除去)し、parent単位のscore集約規則を決めます。

検索用child chunkから生成用parent contextへ戻す仕組み

Late Chunking

通常の方式は、文書をchunkへ切ってから各chunkを独立にencodeします。Late Chunkingは、long-context embedding modelへ長いtextを先に入力してtokenをcontextualizeし、その後にchunk境界ごとにmean poolingします。

通常: 文書 → chunk A / B / C → 個別encode → vector A / B / C
Late: 文書全体 → token表現をまとめてencode → A / B / Cごとにpooling

chunk Bのvectorへ周辺contextを反映できることが利点です。ただし、modelの最大入力長を超える文書、長文encodeのmemory・latency、境界自体の選択は残ります。「semantic chunking」と「late chunking」は別の軸です。前者はどこで切るか、後者は切る前後のどちらでcontextualizeするかを扱います。

長いretrieval unit

LongRAGのように、関連documentをまとめた長いunitを検索し、long-context readerへ渡す方向もあります。短く切る方式と比べ、unit数とcontext喪失を減らせる可能性がありますが、単一vectorへの意味圧縮と1候補あたりのcostは増えます。

方式 検索の焦点 周辺context 主なcost・risk
固定長 長さ次第 境界で失いやすい 構造を切る
見出し・section 文書構造に沿う 保ちやすい 長さが不均一
semantic topic境界に沿える threshold次第 判定modelとversion管理
proposition 個別factへ強く絞る 原文contextを失いやすい 生成誤りとtrace
parent-child childで焦点、parentで説明 調整しやすい dedupとscore集約
late chunking chunk単位を維持 encode時に周辺を反映 長文modelの入力長・cost
long unit 広いcontextを保持 高い 意味圧縮とtoken cost

方式名だけで選ばず、同じcorpus、同じquery set、同じretrieverで比較します。

Chunkを再生成・削除できるmetadataを持つ

chunk textとvectorだけを保存すると、元文書の更新、権限変更、citationへ対応できません。最低限、次を保持します。

Field 用途
chunk_id 検索結果・評価・citationを対応付ける
source_id 元文書へ戻る
source_version どの版から作ったか特定する
parent_id 検索用childから生成用parentへ戻る
heading_path 文書内の意味的位置を示す
start_offset / end_offset 原文spanを再現する
chunker_version 分割規則を比較・再現する
embedding_version vector再生成範囲を決める
tenant_id / ACL(Access Control List、閲覧権限一覧) 検索前に権限filterする

chunk_idを本文hashだけにすると、同じ定型文が複数sectionにある場合に衝突し得ます。source_idsource_version、span、chunker versionを組み合わせ、安定して再生成できる規則を決めます。

source削除時は、child chunk、parent context、sparse index、vector index、retrieval cacheを同じlineage(由来関係)から削除します。

Hybrid Searchで異なる検索の強みを合わせる

Dense Retrievalは言い換えや自然文queryを拾いやすい一方、製品型番、error code、人名、日付の完全一致を落とすことがあります。BM25などのsparse retrieval(語の一致を使う検索)は、exact tokenを追いやすい反面、語彙が異なる言い換えに弱い場合があります。

BEIRは18 datasetで複数のretrieval architectureを比較し、BM25が堅牢なbaselineであること、rerankingやlate-interactionが平均的に高いzero-shot性能を示す一方で計算costが高いことを報告しました。自社corpusでもdenseだけに置き換えず、query category別に比較します。

Query Sparseが拾いやすい理由 Denseが拾いやすい理由
E1042の対処 error codeの完全一致 周辺説明が似ていれば補助できる
「PCが熱い」 PCと文書のノートパソコンが不一致 言い換えを近づけられる
2026年4月の規程 年月・固有語をfilterできる 日付の正確な一致は保証しない
「退職後も秘密保持?」 語が一致すれば強い 質問文と条項の意味を対応できる

BM25の完全一致検索とDense Retrievalの意味検索を統合するHybrid Search

Hybrid Searchでは、sparseとdenseを独立に実行し、候補集合を統合します。

                         ┌─ BM25 top 50 ─┐
query ─ normalize/filter ┤                ├─ fusion ─ top 40 ─ rerank
                         └─ Dense top 50 ┘

注意点は、取得後に権限filterするのではなく、両retrieverへ同じtenant・ACL・version filterを適用することです。

Score fusionとRank fusionを区別する

BM25 scoreとcosine similarityは尺度が違います。BM25はquery・documentの語頻度や長さで変わり、cosine similarityは正規化vectorの角度に基づきます。次の単純加算には意味がありません。

\[s(d)=s_{\mathrm{BM25}}(d)+s_{\mathrm{dense}}(d)\]

score fusionを使うなら、各scoreをquery内でmin-maxやz-scoreなどに正規化し、重みを評価dataで調整します。ただし、外れ値、candidate集合、query categoryが変わると正規化の挙動も変わります。

RRF(Reciprocal Rank Fusion、逆順位融合)はraw scoreではなく順位を使います。RRF原論文の考え方は、document \(d\) に対し、各ranker \(r\) の順位から次のscoreを加算するものです。

\[\operatorname{RRF}(d)=\sum_{r\in\mathcal{R}} \frac{1}{k+\operatorname{rank}_r(d)}\]

\(k\)は上位順位と下位順位の差を滑らかにする定数です。原論文は実験で60を使い、ElasticsearchのRRF資料rank_constantのdefaultを60としています。これは便利な開始点であって、自社corpusでの最適値を保証しません。

RRFの具体計算

\(k=60\)、BM25とDenseの上位3件を次のようにします。

Document BM25 rank Dense rank RRF score
A 1 圏外 \(1/61=0.01639\)
B 3 2 \(1/63+1/62=0.03200\)
C 圏外 1 \(1/61=0.01639\)

BM25順位とDense順位をRRFで融合しBが上位になる計算例

Bはどちらのretrieverでも1位ではありませんが、両方から支持されたため融合後に上位へ来ます。raw scoreのscaleを揃えずに候補を統合できるのがRRFの利点です。

一方、順位に変換すると「1位が2位より圧倒的に高い」というscore差は捨てられます。rank windowの外にある文書も融合できません。RRF、正規化score fusion、学習済みfusion modelを同じ評価setで比較します。

Rerankerは候補を詳しく並べ直す

前回扱ったDual Encoderは、queryとpassageを別々にencodeするため、corpus vectorを事前計算できます。大規模候補生成に向く一方、query tokenとpassage tokenの詳細な相互作用は単一vectorへ圧縮されます。

Cross Encoderはqueryと候補passageを一緒に入力し、token間の関係を使ってrelevance scoreを出します。Passage Re-ranking with BERTはBERTをquery-based passage rerankingへ適用し、MS MARCO passage retrievalの当時の条件でprevious state of the artに対しMRR@10を相対27%改善したと報告しました。この改善幅は特定datasetと時点の結果であり、一般的な保証ではありません。

観点 Dual Encoder retriever Cross Encoder reranker
入力 queryとpassageを別々にencode queryとpassageを同時に入力
corpus側の事前計算 できる pairごとに必要
対象件数 大規模corpusから候補生成 数十〜数百候補の再評価
token間の詳細比較 単一vectorへ圧縮 cross attentionで扱える
主な失敗 正解をcandidateへ入れられない 候補内の順位を誤る

Dual Encoderで候補生成しCross Encoderで詳細にrerankする二段構成

rerankerは検索器の代わりではありません。candidate top-kに正解がなければ、並べ直しても正解は現れません。Sentence-BERTが示すように、pairwise BERTは全組み合わせへ適用すると計算量が大きいため、first-stage retrievalとの二段構成にします。

candidate_k・rerank_k・context_kを分ける

一つのtop_kをpipeline全体で使わず、役割ごとに分けます。

Parameter 意味 小さすぎる場合 大きすぎる場合
candidate_k sparse/denseが取得する候補数 正解が候補へ入らない search・fusion cost増加
rerank_k Cross Encoderで再評価する件数 順位改善の対象が狭い model latency・cost増加
context_k LLMへ渡すchunk/parent数 根拠不足 noise・重複・token cost増加

通常は次の関係になります。

\[\text{context\_k}\leq\text{rerank\_k}\leq\text{candidate\_k}\]

たとえば各retrieverから50件ずつ取り、fusion後40件をrerankし、deduplicateとtoken budgetを通して5件をcontextへ採用します。この数値自体を推奨するのではなく、各段階で何件残ったかをtraceする例です。

同じparentのchildが複数上位にある場合、context_k=5でも実質1つのsectionしかカバーしないことがあります。source diversity、parent dedup、version、token budgetを見て採用します。

RRFを最小実装してdata contractを確認する

次は2つ以上のrank listを融合する独自のPython例です。productionでは、同じdocument ID、同じfilter、同じindex snapshotから作られたlistかを先に検証します。

from __future__ import annotations

import logging
from collections import defaultdict
from collections.abc import Sequence
from dataclasses import dataclass

LOGGER = logging.getLogger(__name__)


@dataclass(frozen=True)
class FusedHit:
    """RRFで統合した検索候補を表す。

    Args:
        document_id: Retriever間で共通の文書ID。
        score: 各rank listから加算したRRF score。
        contributing_rankers: この文書を返したranker数。

    Returns:
        Fusion後の候補とtrace情報を保持するimmutable object。

    Raises:
        TypeError: Fieldの型が契約と一致しない場合。

    Example:
        ``FusedHit("doc-b", 0.032, 2)``
    """

    document_id: str
    score: float
    contributing_rankers: int


def reciprocal_rank_fusion(
    ranked_lists: Sequence[Sequence[str]],
    *,
    rank_constant: int = 60,
    limit: int | None = None,
) -> list[FusedHit]:
    """複数のdocument ID順位列をRRFで統合する。

    Args:
        ranked_lists: 各retrieverが返したdocument IDの順位列。
        rank_constant: 下位rankの影響を調整する正の定数。
        limit: 返す候補数。Noneなら全候補を返す。

    Returns:
        RRF score降順、同scoreではdocument ID順の候補。

    Raises:
        ValueError: Rank listが不足、定数が非正、limitが非正、
            または同一list内に重複IDがある場合。

    Example:
        >>> lists = [["doc-a", "doc-c", "doc-b"], ["doc-c", "doc-b"]]
        >>> [hit.document_id for hit in reciprocal_rank_fusion(lists, limit=2)]
        ['doc-c', 'doc-b']
    """
    if len(ranked_lists) < 2:
        raise ValueError("RRFには2つ以上のrank listが必要です")
    if rank_constant <= 0:
        raise ValueError("rank_constantは正の整数で指定してください")
    if limit is not None and limit <= 0:
        raise ValueError("limitは正の整数またはNoneで指定してください")

    scores: defaultdict[str, float] = defaultdict(float)
    sources: defaultdict[str, int] = defaultdict(int)

    for ranked_list in ranked_lists:
        if len(set(ranked_list)) != len(ranked_list):
            raise ValueError("同一rank list内のdocument IDは一意にしてください")
        for rank, document_id in enumerate(ranked_list, start=1):
            scores[document_id] += 1.0 / (rank_constant + rank)
            sources[document_id] += 1

    hits = [
        FusedHit(document_id, score, sources[document_id])
        for document_id, score in scores.items()
    ]
    hits.sort(key=lambda hit: (-hit.score, hit.document_id))

    LOGGER.info(
        "RRF complete: rankers=%d candidates=%d limit=%s",
        len(ranked_lists),
        len(hits),
        limit,
    )
    return hits if limit is None else hits[:limit]

このfunctionはrank fusionだけを担当します。ACL filter、document ID正規化、parent dedup、rerank、context token計算を混ぜません。各stageを小さくすると、どこで候補が消えたかを追跡できます。

評価はpipelineの段階ごとに行う

最終回答の正誤だけを見ると、Chunking、retrieval、rerank、generationのどこを直すべきか分かりません。

Stage 主なmetric 確認する問い
Chunking gold span coverage、自己完結率、重複率 正解根拠が切断・欠落していないか
Candidate retrieval Recall@k / Hit@k 正解chunkが候補へ入ったか
Fusion union recall、方式別寄与率 sparse/denseの追加候補が役立ったか
Rerank MRR、nDCG@k 正解候補を上位へ移せたか
Context selection evidence coverage、重複token率、context token 必要根拠をbudget内で残したか
Generation answer correctness、citation support、abstention 取得済み根拠を正しく使ったか

RAG検索pipelineを段階別metricで診断する図

Chunking評価setを作る

実queryごとに、答えを支えるsource spanと許容するparent sectionをlabel付けします。完全一致する1 chunkだけをgoldにすると、同じ根拠を説明する別chunkを誤って不正解にするため、relevance基準を先に決めます。

次のquery categoryも持たせます。

  • 1文で答えられるfact
  • 条件と例外が離れている質問
  • 表のheaderとcellが必要な質問
  • 複数sectionを必要とする質問
  • 型番・error code・日付
  • 文書に答えがない質問

一度に一つのstageだけ変える

次のようなablation(構成要素を一つずつ変える比較)を行います。

  1. fixed-size、overlapなしをbaselineにする。
  2. chunk lengthだけ変え、retrieval recallとcostを測る。
  3. overlapだけ加え、境界queryと重複率を測る。
  4. structure-aware分割へ変え、同じretrieverで比較する。
  5. BM25を追加し、fusion前後のunion recallを測る。
  6. RRFまたはscore fusionを追加する。
  7. rerankerを追加し、候補内の順位改善とlatencyを測る。
  8. context selectionを追加し、answerとcitationを測る。

Embedding model、chunker、fusion、reranker、promptを同時に変えると、改善原因が分かりません。各runへ次を記録します。

query_id
source_version / chunker_version / embedding_version
sparse_index_version / dense_index_version
candidate IDs and ranks per retriever
fusion ranks / reranker scores
selected context IDs / excluded reasons
answer / citation IDs

症状から修正するstageを特定する

症状 先に確認するstage 代表的な原因 次の実験
正解文がどのchunkにも残らない Chunking parser欠落、境界分断 source span coverage
正解chunkはあるが候補に出ない Retriever query mismatch、metric、filter sparse/dense別Recall@k
sparseかdense片方だけで拾える Fusion window不足、ID不一致 union recallとRRF
候補にあるが順位が低い Reranker relevance model、domain shift rerank前後のMRR/nDCG
上位だがLLMへ渡らない Context selection dedup、token budget、parent展開 除外理由のtrace
根拠を渡したが回答が誤る Generation prompt、position、model能力 oracle contextで評価
古い規程が出る Indexing version filter、削除漏れ lineageとactive version
他tenant文書が出る Security ACL metadata・filter漏れ retrieval前の権限test

Oracle contextでgenerationを切り分ける

人手で選んだ正解contextをLLMへ渡しても回答できなければ、retrievalを改善しても解決しません。反対にoracle contextでは答えられ、retrieved contextでは答えられないなら、Chunkingからcontext selectionまでに原因があります。

この切り分けにより、「RAGの精度」という一つの数字を、修正可能なstageへ分解できます。

導入時の最小チェックリスト

  1. source documentのparser結果を目視し、表・見出し・改行の欠落を確認する。
  2. 実queryとgold source spanを持つ評価setを作る。
  3. fixed-size・overlapなしを再現可能なbaselineにする。
  4. chunk lengthとoverlapを別々に変える。
  5. chunk_idからsource、version、parent、spanを辿れるようにする。
  6. BM25とFlat exact denseを同じfilter条件で評価する。
  7. union recallを確認してからfusion方式を選ぶ。
  8. candidate recallを確認してからrerankerを追加する。
  9. candidate_krerank_kcontext_kを別々に記録する。
  10. token budget、重複率、latency、index sizeも品質metricと一緒に測る。
  11. oracle contextでgeneration側の上限を測る。
  12. versionを固定した新indexを評価してから切り替える。

よくある質問

chunk sizeは何tokenから試せばよいですか?

modelの最大入力長、文書内の意味単位、gold evidenceの長さから複数候補を作ります。特定frameworkのdefault値を正解とせず、たとえば短・中・長の3条件を同じquery setで比較します。比較時はretrieval recallだけでなく、重複token、index件数、rerank latency、最終回答も記録します。

overlapは多いほど安全ですか?

境界分断を減らせますが、index容量、Embedding計算、重複取得、context tokenを増やします。境界をまたぐqueryのHit@k改善と、近接chunkの重複率を同時に見ます。見出しや文単位で自然に分割できるなら、大きなoverlapが不要な場合もあります。

Hybrid Searchなら必ずDense Retrievalより良くなりますか?

保証されません。sparseとdenseが同じ誤候補を返す場合や、fusion windowが狭い場合、ID・filterが不一致の場合は改善しません。まず各retrieverのRecall@kと候補unionを測り、追加候補に価値があるか確認します。

RRFのrank constantは60で固定してよいですか?

60はRRF原論文の実験や一部実装のdefaultとして知られていますが、万能値ではありません。candidate windowと一緒に評価します。値を大きくすると一般に下位rankの寄与差が緩やかになります。

Rerankerを入れればchunkingは雑でもよいですか?

いいえ。正解根拠がchunkから欠落したり、candidate集合に入らなかったりした場合、rerankerは復元できません。Chunkingのspan coverage、candidate recall、rerank後順位を順に確認します。

Parent-child retrievalではどちらをEmbeddingしますか?

典型的には小さいchildをEmbeddingして検索し、parent_idから広いsectionへ戻します。ただし、childだけではqueryとの対応が曖昧ならheadingやparent summaryを入力へ含める方法もあります。どのtextをencodeしたかをversion付きで記録し、比較します。

まとめ

RAGのChunkingは、長文を指定token数で切るだけの前処理ではありません。何を検索順位の単位にし、何を回答根拠としてLLMへ渡すかを決めるdata modelです。

小さいchunkは焦点を作りやすい一方でcontextを失い、大きいchunkは前後関係を保つ一方で意味圧縮とtoken costが増えます。固定長、structure-aware、proposition、parent-child、late chunking、long unitを同じ評価setで比較し、万能値を置かないことが重要です。

Hybrid Searchはsparseとdenseの異なる候補を集め、RRFはscore scaleを直接揃えず順位で融合できます。Cross Encoder rerankerは候補内の詳細な関連度を評価しますが、未取得の正解は救えません。

Chunking、candidate retrieval、fusion、rerank、context selection、generationを別stageとしてtraceすれば、「RAGの精度が悪い」という症状を、修正できる設計判断へ変えられます。

関連記事

参考資料

コメント

タイトルとURLをコピーしました