
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行要約
- Chunkingが変える3つの単位
- 小さすぎるchunkと大きすぎるchunk
- 固定長Chunkingとoverlapを数値で理解する
- Chunking方式を同じ軸で比較する
- Chunkを再生成・削除できるmetadataを持つ
- Hybrid Searchで異なる検索の強みを合わせる
- Score fusionとRank fusionを区別する
- Rerankerは候補を詳しく並べ直す
- candidate_k・rerank_k・context_kを分ける
- RRFを最小実装してdata contractを確認する
- 評価はpipelineの段階ごとに行う
- 症状から修正するstageを特定する
- 導入時の最小チェックリスト
- よくある質問
- まとめ
- 関連記事
- 参考資料
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では、「それ」「同制度」「前項」の参照先が消えたり、表のheaderとcellが離れたりします。短いtextは検索queryへ近く見えても、回答に必要な意味を単独で持たない場合があります。
大きすぎるchunkでは、休暇、給与、在宅勤務、情報securityが一つのvectorへ圧縮されます。queryに関係しない文章もLLMへ入り、context tokenを消費します。context windowへ入るからといって、すべての情報を同様に使えるとは限りません。Lost in the Middleは、対象modelとtaskで関連情報の位置を変えると性能が変化し、長いcontextの中間で低下する傾向を報告しました。
設計目標は「最小chunk」でも「最大chunk」でもなく、次の2条件を同時に満たす単位です。
- 検索時に、queryとgold evidence(正解根拠)の関連度を表現できる。
- 生成時に、条件・例外・出典を失わず回答できる。
この2条件が衝突するなら、検索用child chunkと生成用parent contextを分けます。
固定長Chunkingとoverlapを数値で理解する
最も単純な方法は、token列を一定長で区切るfixed-size chunkingです。chunk lengthを \(L\)、次のchunk開始位置までのstride(移動幅)を \(S\) とすると、overlap \(O\) は次です。
文書長を \(T\) とし、末尾の短いchunkも残す場合、\(T>L\) でのおおよそのchunk数 \(N\) は次のように表せます。
たとえば \(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は、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方式を同じ軸で比較する

固定長・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集約規則を決めます。

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_id、source_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できる | 日付の正確な一致は保証しない |
| 「退職後も秘密保持?」 | 語が一致すれば強い | 質問文と条項の意味を対応できる |

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の角度に基づきます。次の単純加算には意味がありません。
score fusionを使うなら、各scoreをquery内でmin-maxやz-scoreなどに正規化し、重みを評価dataで調整します。ただし、外れ値、candidate集合、query categoryが変わると正規化の挙動も変わります。
RRF(Reciprocal Rank Fusion、逆順位融合)はraw scoreではなく順位を使います。RRF原論文の考え方は、document \(d\) に対し、各ranker \(r\) の順位から次のscoreを加算するものです。
\(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\) |

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へ入れられない | 候補内の順位を誤る |

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増加 |
通常は次の関係になります。
たとえば各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 | 取得済み根拠を正しく使ったか |

Chunking評価setを作る
実queryごとに、答えを支えるsource spanと許容するparent sectionをlabel付けします。完全一致する1 chunkだけをgoldにすると、同じ根拠を説明する別chunkを誤って不正解にするため、relevance基準を先に決めます。
次のquery categoryも持たせます。
- 1文で答えられるfact
- 条件と例外が離れている質問
- 表のheaderとcellが必要な質問
- 複数sectionを必要とする質問
- 型番・error code・日付
- 文書に答えがない質問
一度に一つのstageだけ変える
次のようなablation(構成要素を一つずつ変える比較)を行います。
- fixed-size、overlapなしをbaselineにする。
- chunk lengthだけ変え、retrieval recallとcostを測る。
- overlapだけ加え、境界queryと重複率を測る。
- structure-aware分割へ変え、同じretrieverで比較する。
- BM25を追加し、fusion前後のunion recallを測る。
- RRFまたはscore fusionを追加する。
- rerankerを追加し、候補内の順位改善とlatencyを測る。
- 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へ分解できます。
導入時の最小チェックリスト
- source documentのparser結果を目視し、表・見出し・改行の欠落を確認する。
- 実queryとgold source spanを持つ評価setを作る。
- fixed-size・overlapなしを再現可能なbaselineにする。
- chunk lengthとoverlapを別々に変える。
chunk_idからsource、version、parent、spanを辿れるようにする。- BM25とFlat exact denseを同じfilter条件で評価する。
- union recallを確認してからfusion方式を選ぶ。
- candidate recallを確認してからrerankerを追加する。
candidate_k、rerank_k、context_kを別々に記録する。- token budget、重複率、latency、index sizeも品質metricと一緒に測る。
- oracle contextでgeneration側の上限を測る。
- 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の精度が悪い」という症状を、修正できる設計判断へ変えられます。
関連記事
参考資料
- Xueguang Ma et al.(2023), Dense X Retrieval: What Retrieval Granularity Should We Use?
- Michael Günther et al.(2024), Late Chunking: Contextual Chunk Embeddings Using Long-Context Embedding Models
- Ziyan Jiang et al.(2024), LongRAG: Enhancing Retrieval-Augmented Generation with Long-context LLMs
- Rodrigo Nogueira and Kyunghyun Cho(2019), Passage Re-ranking with BERT
- Nils Reimers and Iryna Gurevych(2019), Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks
- Gordon V. Cormack et al.(2009), Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods
- Elastic, Elasticsearch: Reciprocal rank fusion
- Nandan Thakur et al.(2021), BEIR: A Heterogenous Benchmark for Zero-shot Evaluation of Information Retrieval Models
- Nelson F. Liu et al.(2023), Lost in the Middle: How Language Models Use Long Contexts
- Vladimir Karpukhin et al.(2020), Dense Passage Retrieval for Open-Domain Question Answering



コメント