社内文書(ExcelやWord、PowerPoint)をRAG(検索拡張生成)に投入する際、以下のような課題に直面したことはないでしょうか。
- Excelの横長テーブルや結合セルが崩れ、行や列の意味関係が壊れてしまう
- シート内のシステム構成図やフロー図が無視され、図中のキーワードで検索できない
- 機械的な文字数でチャンク分割した結果、表の途中や図の解説が分断されてしまう
本記事では、オープンソースパーサー「Docling」とGoogleのマルチモーダルLLM「Gemini」を連携させます。複雑な表構造と、画像内の論理構造(Mermaid記法)を忠実に維持したRAG用Markdownを生成する実践手順を解説します。
手元のPython環境ですぐに動かせるスクリプトとプロンプト例も掲載しています。
前提環境とセットアップ
本手法の検証環境は以下の通りです。
- Python: 3.10 以上
-
主要ライブラリ:
-
docling: Office文書のXML直接パースおよび画像切り出し -
google-genai: GeminiマルチモーダルAPIによる画像解析 -
python-dotenv: 環境変数管理
-
ライブラリのインストール
ターミナルで以下のコマンドを実行して必要なパッケージをインストールします。
pip install docling google-genai python-dotenv
APIキーの設定
プロジェクト直下に .env ファイルを作成し、Google AI Studioで取得したGemini APIキーを設定します。
GEMINI_API_KEY="your_api_key_here"
処理の全体パイプライン
全体の処理フローは以下の4ステップで構成されます。
- DoclingによるOffice直接パース: PDFを経由せず、Excel/Wordの内部XMLから表やテキストを直接Markdown化し、埋め込み画像をローカルへ自動抽出する。
- Geminiによるマルチモーダル解析: 抽出された画像をGeminiへ送信し、図の論理構造を表すMermaidコードと解説テキストを生成する。
- Markdownへの自動インジェクション: 元Markdown内の画像リンク直後に、生成されたMermaid・解説文を引用ブロックとして自動挿入する。
- 構文保護型チャンキング: H1/H2見出し境界で分割し、図表と解説を同一チャンク内に保護する。
[Excel/Wordファイル]
│
▼ (Docling直接パース)
┌────────────────────────┬────────────────────────┐
│ 構造化Markdown (表保持) │ 抽出画像 (image_*.png) │
└────────────────────────┴────────────────────────┘
│
▼ (GeminiマルチモーダルAPI)
[Mermaidコード + 詳細解説]
│
▼ (自動インジェクション)
[完全構造化Markdown (表 + テキスト + Mermaid)]
│
▼ (構文保護チャンカー)
[高精度RAG用チャンク (ベクトル検索対応)]
実装手順とコード
Step 1: Doclingによる直接パースと画像切り出し
Officeファイルを事前にPDFへ変換すると、A4用紙枠によって横長テーブルの右端が強制分断されるトラブルに見舞われます。

図: PDF変換時、A4枠からあふれた表の右端が別ページや下部へ強制分断される様子
Doclingでは内部XML構造を直接パースします。用紙サイズに制限されず表構造を保ちながら、埋め込み画像を個別ファイルとして自動保存できます。

図: XML直接パースにより、横長テーブルや結合セルが1つのMarkdown表として維持される様子
from pathlib import Path
from docling.document_converter import DocumentConverter, PdfFormatOption
from docling.datamodel.base_models import InputFormat
from docling.datamodel.pipeline_options import PdfPipelineOptions
from docling_core.types.doc import ImageRefMode
def parse_office_document(file_path: Path, output_dir: Path) -> Path:
"""
Officeファイルを直接パースし、画像を抽出してMarkdownを出力します。
"""
artifacts_dir = output_dir / f"{file_path.stem}_artifacts"
artifacts_dir.mkdir(parents=True, exist_ok=True)
# 画像をローカルフォルダへ実ファイルとして切り出す設定
pipeline_options = PdfPipelineOptions()
pipeline_options.images_scale = 2.0
pipeline_options.generate_picture_images = True
converter = DocumentConverter()
result = converter.convert(file_path)
# Markdownとして保存 (画像は相対パスで参照)
md_content = result.document.export_to_markdown(
image_mode=ImageRefMode.REFERENCED,
artifacts_path=artifacts_dir
)
md_path = output_dir / f"{file_path.stem}.md"
with open(md_path, "w", encoding="utf-8") as f:
f.write(md_content)
return md_path
この処理により、横長テーブルもセルの結合状態を保ったまま1つのMarkdown表として出力されます。
Step 2: GeminiマルチモーダルAPIによるMermaid化
切り出した画像(システム構成図やフロー図)をGeminiに渡し、論理構造をMermaid記法で出力させます。

図: Excel内部から自動抽出されたシステム接続構成の元画像
RAGのインデックスに会話文(「承知しました」等)が混入するのを防ぎ、かつMermaidの構文エラーを防止するため、プロンプトには厳格なガードレールを設けます。
import os
from google import genai
from google.genai import types
# RAG専用のプロンプト定義
IMAGE_PARSER_PROMPT = """この画像を詳細に解析し、その内容をテキストで再現したMarkdown形式の説明を生成してください。
【重要】挨拶、メタな説明、前置き、結びなどの会話調テキストは一切出力しないでください。手順書の一部としてそのまま読めるように、解析結果のみを出力してください。
以下の出力形式を遵守してください:
- システム構成図・フローチャート・シーケンス図などの場合:
図の論理構造を忠実に再現した「Mermaid記法(```mermaid ... ```)」を出力し、直後に構成要素の補足説明を記述してください。
【重要】Mermaidのノードラベルに特殊文字、丸括弧 ( )、スラッシュ /、改行タグ <br> を含める場合は、必ずノードラベル全体をダブルクォートで囲んでください(例: A["Client PC<br/>(Web Browser)"] )。
- 表(テーブル)画像の場合:
解説を挟まず、Markdownテーブル記法(| 列1 | 列2 | ...)のみを出力してください。
- テキスト文書画像の場合:
画像内の文字を忠実に文字起こししたテキストのみを出力してください。
"""
def analyze_image_with_gemini(image_path: Path) -> str:
"""
GeminiマルチモーダルAPIを利用して画像からMermaidと解説文を生成します。
"""
client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY"))
with open(image_path, "rb") as f:
image_bytes = f.read()
response = client.models.generate_content(
model="gemini-2.5-flash", # または gemini-1.5-flash
contents=[
types.Part.from_bytes(data=image_bytes, mime_type="image/png"),
IMAGE_PARSER_PROMPT
]
)
return response.text.strip()
Step 3: MarkdownへのMermaid・解説自動インジェクション
生成されたMermaidコードと解説文を、元Markdownの画像リンク()の直後に引用ブロック(> **[画像の説明]**:)として差し込みます。
import re
def merge_image_descriptions(md_path: Path, descriptions: dict[str, str]) -> None:
"""
生成された画像解説をMarkdownの画像リンク直後へ引用ブロックとして挿入します。
"""
if not descriptions:
return
with open(md_path, "r", encoding="utf-8") as f:
md_content = f.read()
for file_name, desc in descriptions.items():
# Markdownの画像リンクパターンにマッチ
pattern = rf'(!\[.*?\]\([^)]*?{re.escape(file_name)}\))'
# 行頭に "> " を付与して引用ブロック化
formatted_desc = "\n".join([f"> {line}" for line in desc.split("\n")])
replacement = r'\1' + f"\n\n> **[画像の説明]**:\n>\n{formatted_desc}\n"
md_content = re.sub(pattern, replacement, md_content)
with open(md_path, "w", encoding="utf-8") as f:
f.write(md_content)
この置換処理により、画像リンクの直下に図のテキスト情報がシームレスに結合されます。
Step 4: 構文保護型チャンキングの実装
最後に、生成されたMarkdownをRAGのベクトル検索に適合したチャンクへ分割します。
単純な文字数区切りを避け、見出し境界で区切りつつ、図表と解説を同一チャンク内に保持します。
def chunk_markdown_safely(md_content: str, source_file: str) -> list[dict]:
"""
見出し構造と図表ブロックを保護しながらMarkdownをチャンク分割します。
"""
chunks = []
current_lines = []
chunk_idx = 1
lines = md_content.split("\n")
for line in lines:
# H1, H2見出しが現れたら既存チャンクを保存して新規分割
if re.match(r'^#{1,2}\s+', line) and current_lines:
chunk_text = "\n".join(current_lines).strip()
if chunk_text:
chunks.append({
"chunk_index": chunk_idx,
"text": chunk_text,
"meta": {"source_file": source_file}
})
chunk_idx += 1
current_lines = []
current_lines.append(line)
if current_lines:
chunk_text = "\n".join(current_lines).strip()
if chunk_text:
chunks.append({
"chunk_index": chunk_idx,
"text": chunk_text,
"meta": {"source_file": source_file}
})
return chunks
変換結果の確認
実際に運用手順書のExcel(表・フロー図混在)を処理した結果は以下のようになります。
1. 表構造の保持
横長の運用パラメータ表が欠損なく単一のMarkdownテーブルとして出力されます。
| 管理グループ | 設定項目 | 適用設定値 | 適用状態説明 |
|---|---|---|---|
| インフラ管理 | アクティブサーバー台数 | 3台 | 冗長化構成による常時運用 |
| インフラ管理 | 監視サイクル | 60秒間隔 | pingおよびHTTPによるヘルスチェック |
2. 図のMermaid化と自動インジェクション
抽出された構成図画像リンクの直後に、Geminiが解析したMermaidダイアグラムと解説テキストが挿入されます。

図: 画像リンクの直後に、Mermaidダイアグラムと詳細な構成要素解説が自動展開された様子

> **[画像の説明]**:
>
> ### システム接続構成図
>
> ```mermaid
> graph LR
> A["クライアントPC<br>(ブラウザ利用)"] --> B["負荷分散装置<br>(LB)"]
> B --> C["Webサーバー<br>(アプリ稼働環境)"]
> ```
>
> #### 構成要素の説明
> - **クライアントPC**: ユーザーがブラウザ経由でアクセスする端末。
> - **負荷分散装置(LB)**: トラフィックを各Webサーバーへ振り分ける。
> - **Webサーバー**: アプリケーションが稼働する実サーバー環境。
この処理により、ユーザーが「負荷分散装置の役割」などを質問した際、ベクトル検索やキーワード検索で、確実にこのチャンクが最上位にヒットします。
注意点とTips
-
OfficeファイルのPDF化は避ける:
ExcelやPowerPointのRAG化では、安易なPDF変換を避けてください。A4用紙枠による表の強制分割や、読み込み順序の狂いが発生するためです。Office文書は直接XMLをパースするのが確実です。 -
Mermaidのダブルクォート制約:
LLMにMermaidを出力させる際、丸括弧やスラッシュ、<br>で構文エラーが起きやすくなります。プロンプトで必ず「ノードラベル全体をダブルクォートで囲む」制約を指定してください。 -
処理速度:
軽量モデル(gemini-2.5-flashやgemini-1.5-flash)を活用できます。Excelパースに約2〜4秒、画像解析に約1.5〜2.5秒と、1件あたり計4〜7秒程度で全自動完了します。
まとめ
Doclingでの直接パースと、Geminiによる図の論理構造のMermaid化を組み合わせます。これにより、「表が崩れる」「図が検索できない」というRAG前処理の2大課題を一挙に解決できます。
社内ドキュメントのRAG精度改善に取り組んでいる方は、ぜひ試してみてください。
関連記事・お問い合わせ
-
開発背景・アーキテクチャの解説:
本ツールの設計思想や、PDF変換の失敗談などの実装裏話は Zenn手記記事 にまとめています。 -
業務導入・受託開発のご相談:
AI Nexusでは、業務効率化ツールの受託開発やAI導入コンサルティングを行っています。詳細は AI Nexus 公式サイト をご覧ください。