8月7日、Sana Hassanが「Building a Multimodal RAG Pipeline with NVIDIA NeMo Retriever, Hosted NIMs, LanceDB, Reranking, and Grounded Generation」と題した記事を公開した。この記事では、NVIDIA NeMo RetrieverとホストされたNIMエンドポイントを組み合わせ、PDFからテキスト・表・グラフを抽出してLanceDBに格納し、マルチモーダルRAGパイプラインを構築する手順について詳しく紹介されている。
なぜこのパイプラインが実用的か
テキストだけを検索対象にした従来のRAG構成では、PDFに含まれる表やグラフ、インフォグラフィックの内容を正確に取得できないケースが多い。本チュートリアルはその課題に正面から向き合い、テキスト・表・グラフ・インフォグラフィックを統一的に処理するパイプラインを、Google Colab上で完結する形で実装している。
モデル推論はNVIDIA NIMs(NVIDIA AI Foundationが提供するホスト型推論エンドポイント)に委ねるため、ローカルGPUは不要だ。必要なのはNVIDIA APIキー(nvapi-で始まるもの)のみである。
使用するコンポーネントの背景
NVIDIA NeMo Retriever
NVIDIA NeMo Retrieverは、エンタープライズ向けRAGパイプライン構築を目的としたNVIDIAのマイクロサービス群だ。埋め込み生成・リランキング・抽出といった各処理を個別のNIMエンドポイントとして提供しており、ローカルにGPUを持たない環境でもAPIキー経由で利用できる。
LanceDB
LanceDBは、Pythonから直接利用できるサーバーレスのベクトルデータベースだ。PostgreSQLやRedisのように別プロセスを立ち上げる必要がなく、ローカルディレクトリへの書き込みだけで動作する。Chroma・Qdrant・Weaviateといった他のベクトルDBと比較したときの特徴は、Apacheのカラム型フォーマットであるLance形式を採用している点にある。これによりディスク効率と読み取り速度を両立しており、単一Notebook内での完結構成に適している。本チュートリアルではIVF_HNSW_SQインデックスを使用し、検索速度と精度のバランスを取っている。
NeMo Retriever + LanceDBの組み合わせ
NeMo RetrieverはベクトルDB自体を内包しない。埋め込み生成・リランキングの処理はNIMエンドポイントが担い、ベクトルの永続化と検索はLanceDBが担うという役割分担になっている。この構成により、推論インフラを持たない開発者でも、PDFからグラウンデッド生成までの一連のパイプラインを動かせる。
パイプラインの全体構成(6ステージ)
チュートリアルは以下の6ステージで構成されている。
Stage 1:オフラインテキスト抽出(APIキー不要)
まず、APIキーなし・GPU不要で動作することを確認するため、PDFiumを使ったCPUベースのテキスト抽出を実行する。
offline = (
create_ingestor(run_mode="inprocess", allow_no_gpu=True)
.files(DOCS)
.extract(
extract_text=True,
extract_tables=False, extract_charts=False,
extract_images=False, extract_infographics=False,
use_page_elements=False,
method="pdfium",
)
)
df_offline = offline.ingest()
この段階では表・グラフの抽出は行わず、テキストのみを取得する。環境確認と動作検証を兼ねたステップだ。
Stage 2:マルチモーダル取り込み(本命)
APIキーがある場合に実行される本格的な取り込み処理がここだ。使用するNIMエンドポイントは以下の5種類:
| 役割 | エンドポイント |
|---|---|
| ページレイアウト検出 | nemotron-page-elements-v3 |
| OCR | nemotron-ocr-v1 |
| 表構造抽出 | nemotron-table-structure-v1 |
| グラフ・インフォグラフィック解析 | nemotron-graphic-elements-v1 |
| 埋め込み生成 | nvidia/llama-nemotron-embed-1b-v2 |
抽出後は512トークン単位のチャンク分割(64トークンのオーバーラップ付き)と、コンテンツハッシュ・バウンディングボックスのIoUを使った重複除去を適用する。生成されたベクトルはLanceDBのIVF_HNSW_SQインデックスに格納される。
ing = (
create_ingestor(run_mode="inprocess", allow_no_gpu=True, error_policy="collect")
.files(DOCS)
.extract(
extract_text=True, extract_tables=True,
extract_charts=True, extract_infographics=True,
method="pdfium", dpi=200,
table_output_format="markdown",
split_config={"text": {"max_tokens": 512, "overlap_tokens": 64}},
...
)
.dedup(content_hash=True, bbox_iou=True, iou_threshold=0.45)
.embed(model_name="nvidia/llama-nemotron-embed-1b-v2", ...)
.vdb_upload(vdb_op="lancedb", ...)
)
Stage 3〜4:検索とVLリランキング
Stage 3では、クエリを埋め込みベクトルに変換し、LanceDBのインデックスから上位チャンクを取得する密検索(dense retrieval)を実装する。単一クエリとバッチクエリの両方に対応している。
Stage 4では、nvidia/llama-nemotron-rerank-vl-1b-v2を使ったビジョン・ランゲージ(VL)リランキングを加える。候補を広めに取得(refine_factor=4)してから意味的関連度で並べ替える構成だ。
reranking = Retriever(
rerank=True,
rerank_kwargs={
"model_name": "nvidia/llama-nemotron-rerank-vl-1b-v2",
"refine_factor": 4,
"batch_size": 16,
},
...
)
Stage 5:メタデータフィルタリング
LanceDBのWHERE句を使い、特定のキーワードを含むチャンクだけを検索対象に絞ることができる。
hits = retriever.query(
"gadget costs",
top_k=5,
vdb_kwargs={"where": "text LIKE '%Cost%'"},
)
Stage 6:グラウンデッド生成とRecall@k評価
最終ステージでは、取得したチャンクをコンテキストとしてnvidia/llama-3.3-nemotron-super-49b-v1.5に渡し、回答を生成する。プロンプトは「提供されたコンテキストのみを使って回答し、出典を[1][2]の形式で明記せよ」という構成になっており、ハルシネーションを抑制する設計だ。
prompt = textwrap.dedent(f"""\
Answer the question using ONLY the numbered context below.
Cite the sources you used as [1], [2], etc. If the context is
insufficient, say so plainly.
Context:
{chr(10).join(ctx)}
Question: {question}
""")
最後に、3件のゴールドラベルクエリに対するRecall@5を計算し、検索品質を定量評価する。
実行環境と成果物
- 実行環境:Python 3.12(Google Colab標準ランタイムと一致)、ローカルGPU不要
- 生成される成果物:
./lancedb(ベクトルテーブル)、./extracted.md(Markdown化されたドキュメント全文) - 全コード:GitHubで公開されている
PDFからの非構造化データ処理・ベクトルDB構築・リランキング・引用付き生成という一連のフローを、単一のNotebookで完結させている点が本チュートリアルの実用的な価値だ。
詳細はBuilding a Multimodal RAG Pipeline with NVIDIA NeMo Retriever, Hosted NIMs, LanceDB, Reranking, and Grounded Generationを参照していただきたい。