ドキュメントRAGにおける隠れた苦悩
チュートリアルを見れば、検索拡張生成(RAG)システムの構築は一見簡単そうに思えます。いくつかのPDFを用意し、チャンクに分割し、ベクトルデータベースに保存して、あとはLLMに任せるだけです。しかし、このワークフローは、10-K(年次報告書)や50ページに及ぶ保険契約書のような現実世界のドキュメントに直面した途端、壁にぶつかります。最近、あるシステムが企業の四半期収益を5億ドルと回答(ハルシネーション)するのを目にしました。実際には5,000万ドルだったのですが、表の読み取りを誤ったことが原因でした。
問題は通常、LLM自体にあるのではなく、「データ」にあります。モデルにバラバラな文字の羅列を与えれば、バラバラな回答しか返ってきません。標準的な PDFローダーは視覚的な構造を削ぎ落としてしまうことが多く、整理された表を混沌とした「単語のスープ」に変えてしまいます。プロダクションレベルのシステムを構築するには、まずパース(解析)戦略を修正する必要があります。
なぜ標準的なPDFパーサーはパイプラインを台無しにするのか
多くの開発者はPyPDF2や基本的なLangChainのローダーから使い始めます。これらは軽量で無料ですが、単純なエッセイ以上の複雑なものには対応できません。主に以下のような点で失敗します:
- 表の破壊: 行と列を単一のフラットな文字列に変換してしまいます。「2023年度の収益」というヘッダーと、それに対応する数値の関連性が瞬時に失われます。
- 段組みの混乱: 2段組みのレイアウトにおいて、多くのパーサーはページ全体を左から右へと読み取ります。これにより、異なるトピックの文章が混ざり合い、意味をなさない段落になってしまいます。
- 視覚情報の無視: 重要なデータがチャートやフローチャートに含まれていることがよくあります。標準的なパーサーはこれらを完全に無視するため、AIの知識ベースに大きな欠落が生じます。
- OCRの限界: スキャンされたドキュメントは、基盤となるOCRエンジンが低コントラストのテキストや傾いたページを処理できない場合、文字化けしてしまうことがよくあります。
いくつかのRAGアプリケーションをデプロイして学んだのは、最も重要なステップはベクトルDBの選定ではなく、埋め込み(embedding)を行う前にドキュメントが「マシンリーダブル(機械判読可能)」であることを保証することだということです。
抽出戦略の比較
どの手法が複雑なレイアウトを最も適切に処理できるか、いくつかの方法をテストしました。内訳は以下の通りです。
1. 従来のPythonライブラリ (PyPDF2, PDFMiner)
これらはローカルで動作し、高速です。単純なテキスト中心のドキュメントには適していますが、表に関しては事実上役に立ちません。ドキュメントに複雑なグリッドがある場合は、これらを避けてください。埋め込みにノイズを混入させるだけです。
2. ローカルOCRエンジン (Tesseract, PaddleOCR)
これらはスキャンされた画像には適していますが、多くの計算リソースを必要とします。また、OCRが壊してしまった表を再構築するためだけに、何百行ものカスタム正規表現を書く羽目になります。ほとんどのチームにとって、膨大な時間の浪費となります。
3. レイアウト認識型パーサー (LlamaParse)
これは現在、LLMネイティブなパースにおけるゴールドスタンダード(標準)です。LlamaParseは、ページの視覚的な意図を理解するクラウドベースのサービスです。単にテキストを抽出するのではなく、表をクリーンなMarkdownやHTMLに変換します。LLMはウェブデータでトレーニングされているため、Markdown形式の表を非常に高い精度で処理できます。
最強のスタック: LlamaParse + LlamaIndex
私が構築した中で最も信頼性の高いパイプラインは、重労働をLlamaParseに任せ、オーケストレーションにLlamaIndexを使用するものです。この組み合わせにより、PDFから最終的な回答に至るまで、文脈(コンテキスト)が損なわれることなく保持されます。
ステップ1: 環境設定
まず、LlamaCloudからAPIキーを取得します。次に、コアライブラリをインストールします。
pip install llama-index llama-parse llama-index-embeddings-openai
ステップ2: 抽出パイプラインの構築
このスクリプトは、私が財務諸表を処理する際によく使うものです。LlamaParseにMarkdownを出力させるのが、表の整合性を保つ秘訣です。
import os
from llama_parse import LlamaParse
from llama_index.core import Settings, VectorStoreIndex
from llama_index.embeddings.openai import OpenAIEmbedding
# キーを設定
os.environ["LLAMA_CLOUD_API_KEY"] = "your_llama_parse_key"
os.environ["OPENAI_API_KEY"] = "your_openai_key"
# パーサーを初期化
parser = LlamaParse(
result_type="markdown", # Markdownにより表の可読性を維持
num_workers=4, # 並列ワーカーで処理を高速化
verbose=True,
language="ja"
)
# ドキュメントをパース
documents = parser.load_data("./quarterly_report.pdf")
# 埋め込みモデルを設定
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
# インデックスとクエリエンジンを作成
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("第3四半期と第4四半期の純利益を比較してください。")
print(response)
ステップ3: Excelおよびマルチシートデータの処理
LlamaParseはPDFに限定されません。データが複数のタブに分散している場合でも、Excelファイルを驚くほど適切に処理します。CSVに変換して文脈を失う代わりに、.xlsxファイルを直接読み込ませることができます。パーサーは各シートを個別のセクションとして扱うため、RAGシステムが巨大なワークブック内の特定のデータポイントを特定しやすくなります。
現場で得た教訓
これらのパイプラインを実際の現場で構築することで、ドキュメントには書かれていないいくつかの教訓を得ました。
Markdownは必須
常に result_type="markdown" を設定してください。GPT-4oやClaude 3.5のようなモデルは、| column | という構文を解釈することに非常に長けています。プレーンテキストでは、モデルは列の終わりと始まりを推測しなければならず、それがエラーの原因となります。
マルチモーダルパースを有効にする
レポートがチャートでいっぱいの場合は、マルチモーダル機能をオンにしましょう。LlamaParseはビジョンモデルを使用して、グラフのテキスト説明を作成できます。これにより、RAGシステムは「20%の成長トレンドを示す棒グラフ」を「視認」し、その事実を回答に含めることができるようになります。
スマートチャンキングは単純な分割に勝る
単に500文字ごとにテキストを分割しないでください。Markdownを使用しているため、ヘッダー(H1、H2)に基づいてチャンク化できます。これにより、関連情報をまとめて保持できます。10行の表を途中で分割すべきではありません。1つのチャンクに収めることで、LLMが完全なコンテキストを把握できるようになります。
コストの管理
LlamaParseはプレミアムサービスです。1万件のドキュメントを処理する場合、ハイブリッドアプローチが最適です。基本的なテキストファイルには無料のローカルパーサーを使用し、視覚的なインテリジェンスを必要とする複雑で表の多いPDFのためにLlamaParseを節約しましょう。
最後に
RAGシステムの質は、取得できるデータの質に依存します。基本的なPDFスクレイパーからLlamaParseのようなレイアウト認識型ツールに切り替えることで、AIのハルシネーションの最も一般的な原因を解決できます。これにより、脆弱なプロトタイプを、どんなに複雑なドキュメントでも処理できるプロフェッショナルなツールへと進化させることができます。

