ほとんどのRAGプロトタイプは、その内部構造はどれも似通っています。誰かがPDFをパイプラインに投入し、テキストをきれいに512トークンのチャンクに分割し、それらをベクトルデータベースに放り込み、作業完了とする。ちょっとしたデモであれば、これで十分に印象的に見えるかもしれません。しかし、本番環境では、それは崩壊します。

固定サイズのチャンクは、何を切り取っているかなど気にしません。免責条項の真っ只中で法的契約を分割してしまうこともあれば、5つの無関係なAPIエンドポイントを同じコンテキストウィンドウに詰め込み、モデルをノイズで埋め尽くしてしまうこともあります。また、必要以上に多くの断片を検索させることになり、レイテンシを増大させ、トークンを浪費させます。その結果、回答は不完全になり、ハルシネーションが発生し、ユーザーの不満を招くことになります。

私たちは検索レイヤーを根本から見直し、再構築しました。その結果、レイテンシを40%削減しながら、リコール率95%を達成するシステムを実現しました。私たちが具体的にどのように行ったのか、その手法を説明します。

なぜ固定チャンクは本番環境で通用しないのか

512トークンというデフォルト値は、設計上の選択ではありません。それは初期のエンベディングモデルのコンテキストウィンドウや、ライブラリの整ったデフォルト設定による副産物に過ぎません。実装は容易ですが、それに頼ることは致命的です。

ドキュメントは均一ではありません。法的な条項は、明確な区切りがないまま700トークンに及ぶこともあります。それを512トークンで分割すると、2つの孤立した断片が生まれます。弁護士やコンプライアンス担当者が責任制限額について尋ねたとき、システムは義務の半分しか返しません。言語モデルは欠落した半分をハルシネーションで補完するか、さらに悪いことに、制限額が存在しないと否定してしまいます。

APIドキュメントは、それとは逆の弊害に見舞われます。500トークンのチャンクは、認証ヘッダー、エラーコード、レート制限、Webhookスキーマといった、モジュール全体を飲み込んでしまう可能性があります。開発者が AUTH_4027 の扱い方を尋ねたとき、検索器は無関係な関数の寄せ集めを提示してしまいます。モデルはそれらを平均化して、中身のない汎用的な情報の塊にするしかなくなります。

不適切なチャンキングは、レイテンシも増大させます。断片が不十分であれば、トピックをカバーするために、より大きな top-k が必要になります。チャンクが増えればプロンプトが長くなります。プロンプトが長くなれば、生成速度は低下し、コストは増大します。ユーザー体験は、些細な問題の積み重ねによって、じわじわと蝕まれていくのです。

ドキュメントに合わせてチャンクを最適化する

私たちはトークンを数えるのをやめ、内容を読み解くことにしました。適切なチャンキング戦略は、ソースの構造に依存します。

法的ドキュメントには、条項を意識した境界を持つ再帰的文字チャンキング(recursive character chunking)が必要です。スプリッターは階層を尊重します。まずセクションヘッダーを探し、次に番号付きの段落、そして自然な文章の区切りを探します。副条項を分断したり、義務的なフレーズをチャンク間で分割したりすることはありません。免責に関する一節を検索したとき、条項全体、制限額、および例外事項がひとまとめに取得できます。

APIドキュメントには、構造を意識したチャンキング(structure-aware chunking)が求められます。私たちはトークン予算ではなく、関数定義に基づいてパースします。各チャンクには、完全な関数シグネチャ、そのパラメータの説明、および直近のエラーハンドリングに関する注記が含まれます。開発者が特定のメソッドを検索した場合、任意の分割によって断片化したものではなく、契約の全体像を受け取ることができます。

サポートチケットはノイズが多く、非線形です。スレッドはバグ報告から始まり、回避策を紹介し、内部のエスカレーションノートで終わることもあります。セマンティック・チャンキング(semantic chunking)は、文同士のエンベディングの類似性を測定することで、トピックの転換を検知します。自然なテーマの境界でのみ分割を許可するため、ログイン失敗に関する会話が、請求サイクルに関するフォローアップと混ざることはありません。

Wikiが最も困難でした。Wikiは広範囲にわたり、相互リンクされ、緩やかに構成されています。そこで私たちは、軽量なLLMがページを読み、テーマの一貫性に基づいて分割箇所を決定するエージェンティック・チャンキング(agentic chunking)を採用しました。取り込み時のコストはわずかに上がりますが、生成されるチャンクは自己完結しており、すぐに検索に利用できる状態になります。デプロイのベストプラクティスに関するページは、任意のテキストブロックではなく、事前チェック、ロールバック手順、モニタリング設定といった論理的なユニットに分割されます。

ハイブリッド検索:キーワードとベクトルの融合

密ベクトル検索(Dense vector search)は意味を理解します。しかし、正確な文字列の検索には向いていません。ユーザーが AUTH_4027 のような正確なエラーコードや、「Stark Industries」のような顧客名を検索した場合、ベクトルエンベディングはターゲットを見失う可能性があります。なぜなら、それらは文字レベルの正確さではなく、概念的な近接性を最適化しているからです。

BM25による純粋なキーワード検索には、その逆の欠陥があります。AUTH_4027 は完璧に見つけ出しますが、「認証失敗」と「ログイン拒否」の間の概念的なつながりを見逃してしまいます。

We run both in parallel. BM25 and vector search operate independently over the same corpus. Their result lists are merged using Reciprocal Rank Fusion, which reorders candidates by balancing their positional ranks. You do not need calibrated weights. You simply get the precision of exact match and the intuition of semantic search in a single ranked list.

Then we add a cross-encoder reranker. This is a separate model that scores each passage against the original query, producing a relevance signal far finer than either retriever alone. It adds about 50 milliseconds of latency. It increases recall by 15 percent. If you care about answer quality, that trade is non-negotiable.

Query Expansion: Fix the Search Before It Starts

Bad queries are the dirty secret of every retrieval system. Users do not write like your embedding space. They type "it broke." They paste truncated stack traces. They use internal jargon your index has never seen.

We transform the query before it ever touches the index. First, we expand a single query into three to five diverse search terms. If the original is "payment failed," we also search for "transaction error," "billing declined," and "charge unsuccessful