高度なRAG:マルチベクトル検索で「壊れたテーブル」問題を解決する

AI tutorial - IT technology blog
AI tutorial - IT technology blog

標準的なRAGの致命的な欠陥

ほとんどのRAGパイプラインは、テキストを500文字のチャンクに分割し、最善を尽くすという単純な前提から始まります。これは基本的なブログ記事なら機能しますが、ネストされたテーブルを含む10-K(年次報告書)や回路図だらけの技術マニュアルを読み込ませた途端、システムは破綻します。

標準的なチャンキングでは、テーブルが真ん中で分割され、行とヘッダーが切り離されてしまうことがよくあります。リトリーバーがその孤立した行を抽出しても、LLMにはコンテキストのないランダムな数字の羅列にしか見えません。単に失敗するだけでなく、自信満々にハルシネーション(もっともらしい嘘)を引き起こします。

私は数ヶ月間、本番環境のRAGシステムが基本的なPDFクエリで失敗する原因の調査に費やしました。犯人はLLMの推論能力ではなく、検索戦略にありました。マルチベクトルアプローチ、特に「親ドキュメント(Parent Document)」戦略に切り替えることで、テーブルデータの検索精度を不安定な55%から92%以上に向上させることができました。それ以来、データの正確性が不可欠な複数のエンタープライズプロジェクトでこのアーキテクチャを採用しています。

検索戦略の実際の比較

コードを書く前に、「素朴な(Naive)」アプローチが失敗し、マルチベクトルが成功する理由を理解する必要があります。それは、検索性とコンテキストのバランスをどう取るかという問題に集約されます。

素朴なアプローチ(標準的なRAG)

一般的なセットアップでは、ドキュメントを同じサイズの小さなチャンクに分割し、埋め込み(エンベディング)を作成して保存します。検索時には、システムが上位K個のチャンクを見つけ出します。小さなチャンクは特定のキーワードを特定するのには適していますが、周囲のコンテキストが不足します。逆に、大きなチャンクには「ノイズ」が多く含まれ、埋め込みベクトルが希釈されて検索精度が低下します。精度を取るかコンテキストを取るかというトレードオフに陥るのです。

マルチベクトル戦略(親子関係)

マルチベクトルリトリーバー(Multi-vector Retriever)は、「検索対象のデータ」と「LLMに送るデータ」を分離します。検索プロセスには、小さな「子」チャンクや簡潔な要約をインデックス化します。

これらは、別のストアにある大きな「親」ドキュメントにリンクされています。子チャンクがクエリに一致すると、リトリーバーは親ドキュメント全体(例えば2,000語の章全体や15行のテーブル全体)を取得し、LLMに渡します。これにより、小チャンク検索の外科的な精度と、フルドキュメントの豊かなコンテキストを両立させることができます。

トレードオフ:それだけの価値はあるか?

この手法は強力ですが、万能薬ではありません。パフォーマンスの向上とインフラコストを天秤にかける必要があります。

メリット

  • ドキュメントの整合性: テーブルがバラバラになりません。LLMはヘッダー、単位、脚注をセットで確認できます。
  • 意味的な明快さ: 画像やテーブルの要約をインデックス化することで、非テキストデータも検索可能になります。
  • 高いS/N比: 1,000文字の生テキストブロックを検索するよりも、100文字の要約を検索する方が精度が高くなることがよくあります。

コスト

  • ストレージ需要: データを埋め込みと生テキスト/画像の両方で保存するため、実質的に2回保存することになります。
  • 取り込みの遅延: 高精度なツールを使用して100ページのPDFを解析するには、数秒ではなく数分かかる場合があります。
  • API費用: レポート内の50個のテーブルの要約を生成するには、アップロード段階で50回の追加のLLMコールが必要になります。

モダンな技術スタック

これを構築するには、単なる文字列の読み取りではなく、ドキュメントの構造を実際に「認識」できるツールが必要です。

  • Unstructured.io: PDFをテーブルやナラティブテキストなどの個別の要素に分割するためのゴールドスタンダードです。
  • LangChain: 要約と生データのリンクを管理する MultiVectorRetriever クラスを提供します。
  • ChromaDB または Pinecone: ベクトル保存用の高性能ストアです。
  • Redis: サブミリ秒の検索を実現するために、親ドキュメントを保持する DocStore として優れた選択肢です。
  • GPT-4o または Claude 3.5 Sonnet: 複雑なテーブルを検索可能な説明文に要約するのに長けています。

ステップ・バイ・ステップの実装

テキストと複雑なテーブルの両方を含む財務レポートを処理するパイプラインを構築してみましょう。

1. 構造的要素の抽出

unstructured を使用してテーブルの境界線を特定します。これにより、最初から「テーブルの分断」問題を防ぐことができます。

from unstructured.partition.pdf import partition_pdf

# 高精度なテーブル検出で要素を抽出
raw_pdf_elements = partition_pdf(
    filename="q3_report.pdf",
    extract_images_in_pdf=False,
    infer_table_structure=True, # テーブル処理の秘訣
    chunking_strategy="by_title",
    max_characters=4000,
)

# 特殊な処理のためにテーブルを分離
tables = [el for el in raw_pdf_elements if el.category == "Table"]
texts = [el for el in raw_pdf_elements if el.category == "CompositeElement"]

2. 検索可能な要約の作成

生のHTMLやMarkdownのテーブルは、ベクトル検索には密度が高すぎることがよくあります。要約を作成することで「検索可能な架け橋」を作ります。

from langchain_openai import ChatOpenAI

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

table_summaries = []
for table in tables:
    prompt = f"セマンティック検索用にこのテーブルを要約してください。主要な指標と行/列のヘッダーを含めてください: {table.text}"
    summary = model.invoke(prompt)
    table_summaries.append(summary.content)

3. マルチベクトルリトリーバーの構築

ここで、ベクトルストア内の検索可能な要約と、ドキュメントストア内の元の生データをリンクさせます。

import uuid
from langchain.retrievers.multi_vector import MultiVectorRetriever
from langchain.storage import InMemoryStore
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

# 要約用のベクトルストア
vectorstore = Chroma(collection_name="summaries", embedding_function=OpenAIEmbeddings())
store = InMemoryStore() 
id_key = "doc_id"

retriever = MultiVectorRetriever(
    vectorstore=vectorstore,
    docstore=store,
    id_key=id_key,
)

def add_data(summaries, raw_contents):
    doc_ids = [str(uuid.uuid4()) for _ in summaries]
    # ベクトル検索に要約を追加
    summary_docs = [Document(page_content=s, metadata={id_key: doc_ids[i]}) for i, s in enumerate(summaries)]
    retriever.vectorstore.add_documents(summary_docs)
    # 親ストアに生データを追加
    retriever.docstore.mset(list(zip(doc_ids, raw_contents)))

add_data(table_summaries, [t.text for t in tables])
add_data([t.text[:1000] for t in texts], [t.text for t in texts])

4. 結果

ユーザーが「第3四半期の収益成長率は?」と尋ねると、システムは収益テーブルの要約を見つけます。次に、LLMのために生のテーブル全体を取得します。モデルは、答えを正確に計算するために必要なすべての数字を把握できるようになります。

画像やチャートへの拡張

これと全く同じパターンを視覚データにも適用できます。GPT-4oのようなマルチモーダルモデルを使用して、折れ線グラフの200語程度の説明文を作成します。その説明文をインデックス化し、元の画像にリンクさせます。ユーザーがトレンドについて尋ねると、説明文が検索のトリガーとなり、LLMは分析対象の実際の画像を受け取ります。これは、テキストのみのモデルでは認識できないデータを扱うための堅牢な方法です。

終わりに

本格的なエンタープライズアプリケーションにおいて、基本的なRAGからの脱却は不可欠です。標準的なチャンキングはプロトタイプには適していますが、現実世界のドキュメントレイアウトに直面した途端に失敗します。マルチベクトルアーキテクチャは、より多くのオーケストレーションと高い取り込みコストを必要としますが、その見返りはユーザーが真に信頼できるシステムです。PDFに単なる段落以上のものが含まれているなら、今すぐ構築すべきはこのアーキテクチャです。

Share: