1つのドキュメントに複数の「表現」を持たせる技術
連載「階層型RAG完全ガイド」について:
本シリーズは全5回を通じて、特定のフレームワーク(LangChain, LlamaIndex等)に依存せず、本番環境で真に機能する**検索システム設計(Retrieval System Design)**の基礎から応用までを網羅的に解説します。
1. 本質:なぜドキュメントに複数の「表現」が必要なのか?
従来のRAGでは、「元の文章(Raw Text)をそのままベクトル化する」という 1対1 のマッピングが当然のように行われてきました。
しかし、実際の業務データには**「ベクトル検索ではうまく捉えられないが、LLMにとっては極めて理解しやすい」**という構造が数多く存在します。
- 複雑な表データ(Tables): ベクトル埋め込みは行・列の二次元関係や数値を捉えるのが苦手ですが、Markdown形式の表であればLLMは高い精度で推論できます。
-
ソースコード(Source Code):
def verify_session_token(token: str) -> bool:のような構文は、自然言語の質問(「二段階認証が完了しているか確認する処理はどこ?」)とベクトル空間上で乖離します。 - 情報密度の高い長文: 多数の話題が混在していると、ベクトルが全方位に引っ張られて類似度スコアが低下します(ベクトルの希釈化)。
例:身分証明と人間の多面的プロフィール
人間が状況に応じて異なる情報で識別される場面を考えてみてください。
- 空港の出入国ゲート: 審査官は分厚い履歴書ではなく、「パスポートの顔写真」や「指紋」(高密度で一意な特徴ベクトル)だけで瞬時に本人確認を行います。
- 採用の書類選考: 採用担当者は、過去の全メール履歴ではなく**「職務経歴書の要約」や「スキルタグ一覧」**(Summary / Keywords)で検索します。
- 病院の診察室: 医師は正確な治療を行うために、要約だけでなく**「詳細なカルテと検査データ」**(Raw Data)をすべて読み込みます。
入国ゲートを通過するためだけに、毎回50ページのカルテを音読させるのは非効率です。
Multi-Representation Retrieval(多重表現検索) はこれと同じ原理です。検索のために最適化された「複数の身分証明カード(要約、想定質問、タグ)」を用意して高速・高精度にヒットさせ、回答時には完全な「生データ(Raw Document)」をLLMに渡して推論させます。
2. 7つの代表的な表現パターン
| 表現形式 | 生成方法 | 検索時の主目的 | 最適なユースケース |
|---|---|---|---|
| 1. Raw Text | 原文をそのまま維持 | 一般的な意味的類似性マッチング | 標準的な解説文・ニュース |
| 2. Summary | 小型LLMによる2〜3文の要約 | 主題の凝縮、ベクトルの希釈化防止 | 長大な節、章、論文 |
| 3. Hypothetical Questions | LLMによる3〜5問の想定質問 | クエリ対クエリ(Query-to-Query)一致 | 社内FAQ、規程集、マニュアル |
| 4. Keywords & Entities | 固有表現・専門用語の抽出 | スパース検索・完全一致フィルタ | 医療記録、法務条文、部品品番 |
| 5. Table Summary | 表の構造と傾向を文章化 | ベクトル検索での表の意味理解 | 財務諸表、製品価格・在庫表 |
| 6. Code Docstring | 関数の目的・引数・戻り値の解説 | 開発者が自然言語でコードを検索 | SDK仕様書、コードベース |
| 7. Image Caption | Vision LLMによる画像解析 | 図表やアーキテクチャ図の検索 | システム構成図、インフォグラフィック |
3. Summary-to-Raw Retrieval:ベクトルの希釈化(Dilution)を解決する
1,500文字を超える長いセクションをそのままベクトル化すると、財務・人事・技術の話題が混ざり合い、特定の狭い質問に対するコサイン類似度が著しく低下します。
処理パイプライン
-
インジェスチョン段階(Indexing):
- 小型で高速なLLM(GPT-4o-mini, Claude 3.5 Haiku, Llama-3.1-8B等)を用いて、原文から100文字程度の凝縮された要約を生成します。
- 例:「本セクションでは、年2回の昇給評価基準、6月の定期査定要件、および役職手当の上限規定について述べている。」
- この要約文のみをベクトル化してVector DBに登録(メタデータに
raw_doc_idを付与)。 - 1,500文字の原文全体はDocument Storeにそのまま保存。
-
クエリ段階(Retrieval):
- ユーザーの質問は要約ベクトルと高スコアでマッチ。
-
raw_doc_idを参照して原文全文を取得し、LLMに渡して正確な回答を生成。
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
SUMMARY_PROMPT = """あなたは検索インデックス作成の専門家です。
以下のテキストを読み、検索用の要約文(80〜120文字程度)を作成してください。
中心となるトピック、重要な固有名詞、具体的な条件・数値を必ず含めてください。
対象テキスト:
{raw_content}
検索用要約:"""
def generate_summary_representation(raw_chunk: str, llm) -> str:
chain = PromptTemplate.from_template(SUMMARY_PROMPT) | llm | StrOutputParser()
return chain.invoke({"raw_content": raw_chunk})
4. Hypothetical Question Retrieval(HyDE & 逆引き質問インデックス)
なぜ回答ではなく「質問」をインデックスするのか?
自然言語処理において、「ユーザーの質問(Query)」 と 「ドキュメントの平叙文(Fact)」 の間には、意味的・構文的な大きな隔たり(Semantic Gap)が存在します。
ユーザーの質問 : 「育休手当を申請するにはどんな書類が必要ですか?」(疑問文・行動要求)
規程の本文記述 : 「雇用保険法に基づく育児休業給付金の支給申請手続きに関する取扱要領...」(行政的・平叙文)
この2つのベクトルの距離は意外と遠くなります。しかし、あらかじめドキュメントから**「この文章で答えられる質問リスト」**を生成してベクトル化しておけば、検索は Query-to-Query(質問同士の比較) となり、類似度スコアが劇的に向上します。
想定質問生成プロンプト
QUESTIONS_PROMPT = """以下のテキストを読み、ユーザーが尋ねる可能性の高い具体的な質問を3〜5個作成してください。
箇条書き(-)で出力してください。
テキスト:
{raw_content}
想定質問リスト:"""
5. Table Retrieval:表データを救済する3ステップ戦略
ベクトル検索単体では、以下のような表の行・列の関係を正しく解釈できません。
| 商品コード | 東京本社 在庫 | 大阪支社 在庫 | 福岡支店 在庫 | 販売価格 |
|---|---|---|---|---|
| IP-16-128 | 45 | 0 | 120 | 149,800円 |
| IP-16-256 | 12 | 8 | 34 | 169,800円 |
平坦なテキスト("IP-16-128 45 0 120 149,800円")として埋め込むと、「大阪支店に128GBの在庫はあるか?」という質問に正しく答えられません。
3ステップ戦略の実装
[PDF / HTML内の生の表]
│
▼ ステップ1:構造を崩さずMarkdown表として抽出
[Raw Table Markdown] ──> Document Store にそのまま保存
│
▼ ステップ2:LLMに表を「読ませて」解説文を生成
「本表は、iPhone 16(128GBおよび256GBモデル)の東京本社、大阪支社、福岡支店における
在庫数と販売価格を示している。特筆すべき点として、128GBモデルは大阪支社で現在在庫切れ(0台)である。」
│
▼ ステップ3:上記解説文をベクトル化して格納
[Vector Store] ──(メタデータ: {"is_table": True, "columns": [...]})
検索時はLLMが生成した自然言語の解説文がヒットし、回答生成時には元のMarkdown表がそのままプロンプトに挿入されるため、LLMは正確無誤に数値を読み取ることができます。
6. ソースコードと画像のマルチ表現
1. ソースコードの表現
-
課題: 開発者は
def verify_session_token(token: str) -> bool:という完全な関数名ではなく、「二段階認証のセッション検証処理」といった目的ベースの自然言語で検索します。 - 解決策: ASTパーサーで関数を抽出し、LLMで関数の目的・引数・戻り値をまとめたDocstringを生成 $\rightarrow$ Docstringをベクトル化し、取得時にはコード全文を返却。
2. 図表・アーキテクチャ図の表現
-
課題: システム構成図の画像ファイル(
architecture.png)にはテキスト検索が効かない。 -
解決策: Vision LLM(GPT-4o, Claude 3.5 Sonnet等)に画像を入力し、
-
Short Caption: 表示用のキャプション -
Detailed Data Extraction: 図内のコンポーネントと通信フローの完全な文章解説
を生成し、後者をベクトル化してインデックスに登録。
-
7. Multi-Vector Store アーキテクチャ(1対Nマッピング)
データベース内部では、1つの元ドキュメント(Document Payload)に対して複数の検索用ベクトルノード(Vector Nodes) が紐づく構造をとります。
from pydantic import BaseModel
from typing import List, Optional
class DocumentPayload(BaseModel):
id: str # Document Store側のプライマリキー
raw_content: str # Markdown本文、表、またはソースコード
doc_type: str # 'text' | 'table' | 'code' | 'image'
class VectorNode(BaseModel):
vector_id: str
doc_id: str # DocumentPayload.id を参照する外部キー
representation_type: str # 'summary' | 'question' | 'keyword' | 'caption'
search_text: str # ベクトル化対象となったテキスト
embedding: List[float]
クエリ時の統合処理:
要約ベクトル経由でヒットした場合でも、想定質問経由でヒットした場合でも、システムは参照先の doc_id に集約し、重複を排除して1つの完全な生コンテキストとしてLLMへ渡します。
8. 適用判断マトリクスとトレードオフ
| データ種別 | 推奨する表現形式 | 導入効果 | トレードオフ・留意点 |
|---|---|---|---|
| 財務諸表、料金・在庫表 | Table Textual Summary | 表の読み飛ばしや誤読を防止 | 事前処理時にLLM呼び出しコストが発生 |
| 社内FAQ、手続きマニュアル | 想定質問(3〜5問) | 検索ヒット率が30〜40%向上 | Vector DBの登録ベクトル数が増加 |
| 論文、大部な技術標準 | Summary-to-Raw | ベクトル希釈化の完全な防止 | 事前の要約パイプラインが必要 |
| ソースコードリポジトリ | AST + Function Docstring | 自然言語での関数逆引きが可能 | 言語ごとのASTパーサーが必要 |
9. Part 3のまとめと次回予告
本章のキーポイント
- 複雑なデータ構造(表、コード、画像)を単一のベクトルで表現しようとしない。
- 要約、想定質問、キャプション を「検索のトリガー」として機能させ、LLMには「未加工の生データ」を供給する。
- インジェスチョン時のLLMコストを抑えるため、小型モデルと非同期ワーカーキュー(Celery, SQS等)を活用する。
👉 次回 階層型RAG完全ガイド:Part 4 - Multi-Level Retrieval & RAPTOR では、数百ページに及ぶ大部なドキュメント全体を俯瞰し、「企業全体の戦略的変遷は何か?」といった包括的な質問に答えるための「再帰的クラスタリング&マルチレベル要約(RAPTOR)」を詳しく解説します!
参考文献
- Gao, L., et al. (2022). Precise Zero-Shot Dense Retrieval without Relevance Labels (HyDE) - arXiv:2212.10496
- LangChain Official Documentation. MultiVector Retriever Architecture
- LlamaIndex Documentation. Document Summary Index Guide
- Unstructured.io Documentation. Extracting and Parsing Complex PDF Tables
- Tree-sitter. An Incremental Parsing System for Programming Guides