MarkdownをRAG(検索拡張生成)で扱う知識ベースとして活用する方法

AIテキスト

RAG(Retrieval-Augmented Generation)は、AIが外部データから関連情報を検索し、それを基に回答を生成する仕組みです。

その根幹となるのが、文章を適切な構造で分割し、意味を損なわずに埋め込みベクトル化する前処理です。

Markdownは見出しやリストなど情報構造があるため、RAG用に変換する際の工夫次第で検索精度や回答品質が大きく変わります。

本記事では、MarkdownをRAG向けに扱いやすくする前処理とチャンク設計の基本を整理します。

Markdown形式とは

Markdownは、見出しや段落といった人間が自然に理解できる構造を持つテキスト形式です。書くことを妨げずに構造化できるため、人間の思考と相性が良いのが特徴です。

HTML / JSON / PDF構造抽出でも同様のことは可能ですが、Markdownはシンプルで可読性が高く、編集も容易な点で優れています。

この性質は、AIとの共同作業、特にRAGにおいて有効です。人間がMarkdownで書いた文章は、その構造をそのままチャンク分割の境界として利用でき、検索や生成に適した形へ自然に変換できます。Markdownは、人とAIをつなぐ実用的な中間表現として便利です。

RAGにおけるチャンク分割の重要性

RAGシステムでは、ドキュメント全体をそのままベクトル化して検索に使うのではなく、意味的にまとまりのある小さな単位(チャンク)に分割してから埋め込みを作成します。

この理由は、言語モデルや埋め込みモデルが扱えるコンテキストウィンドウの制限や、短く区切られた情報の方が類似度検索で精度を出しやすいという点にあります。

例えば、チャンクが長すぎると意味がぼやけ、逆に短すぎると文脈が欠けてしまうことがあります。

実際、様々なRAGベストプラクティスでも、チャンク設計がRAG性能の大部分を決定する要素として挙げられています。
Mastering RAG: Advanced Chunking Strategies for Vector Databases | by Subash B S | Medium

Markdownの構造を活かして

Markdownは、見出し(#〜)、段落、リスト、コードブロックなど、テキスト内に自然な境界が存在します。

この構造をそのままチャンク単位の境界として利用することが推奨されます。

単純にトークン数で等分する方法よりも、意味的にまとまりのある単位で切る方が検索時の関連性が高くなる傾向があります。

たとえば、見出しごとに分割し、その中でも大きすぎる場合は段落単位で再分割する方法がよく知られています。
Document Chunking for RAG: 9 Strategies Tested (70% Accuracy Boost 2025) | LLM Practical Experience Hub

Markdown → 前処理の流れ

まずMarkdownを解析し、テキスト構造を抽出します。

具体的には、見出し階層を保持しながら分割ポイントとして扱い、段落や箇条書きも独立したセグメントとして扱うことがポイントです。次に、構造化されたテキストセクションを適切なサイズのチャンクにまとめます。

ベクトルDB(例:Chroma DB)で扱いやすい形に変換します。

原文(行番号付き)

01: # 見出し1
02: ## 見出し2
03: ### 見出し3 
04: 本文
05: 本文続き

変換後

[見出し1:見出し2:見出し3:L04]本文
[見出し1:見出し2:見出し3:L05]本文続き

大きさは一般的に200〜800トークン程度が基準とされ、意味的境界を壊さない範囲で区切ると検索精度が向上します。

なお、テキストの意味を損なわないように、やや重なり(overlap)を持たせてチャンクを作る手法もありますが、重なりすぎると冗長になるため注意が必要です。
Mastering RAG: Advanced Chunking Strategies for Vector Databases | by Subash B S | Medium

Pythonコードの簡易例です。本記事では見出し構造に限定した最小実装を示しています。

import re
from typing import List, Tuple, Optional

HEADING_RE = re.compile(r"^(?P<hashes>#{1,6})\s+(?P<title>.+?)\s*$")

# Markdown見出し+本文行をRAG向けに変換
def markdown_to_rag_lines(md: str, *, pad: int = 2) -> List[str]:
    lines = md.splitlines()
    headings: List[Optional[str]] = [None] * 7
    out: List[str] = []
    in_fence = False
    fence_token: Optional[str] = None

    for i, raw in enumerate(lines, start=1):
        line = raw.rstrip("\n")

        m = HEADING_RE.match(line)
        if m:
            level = len(m.group("hashes"))
            title = m.group("title").strip()
            headings[level] = title
            # clear deeper levels
            for lv in range(level + 1, 7):
                headings[lv] = None
            continue

        text = line.strip()
        if not text:
            continue  # skip empty lines

        # ヘッダ出力
        path_parts = [headings[lv] for lv in range(1, 7) if headings[lv]]
        path = ":".join(path_parts) if path_parts else ""

        out.append(f"[{path}:L{str(i).zfill(pad)}]{text}")

    return out

if __name__ == "__main__":
    sample = """# 見出し1
## 見出し2
### 見出し3
本文
本文続き
"""
    for s in markdown_to_rag_lines(sample):
        print(s)

# Expected:
# [見出し1:見出し2:見出し3:L04]本文
# [見出し1:見出し2:見出し3:L05]本文続き

実際のMarkdownドキュメントでは、コードブロックや引用中に含まれる見出し記号(#)を誤認しないように、コードフェンスの中は見出し解析をスキップするなどの工夫も必要です。

また、見出しだけでなく箇条書きや表、引用なども意味的な単位として扱うことがベターです。

チャンクの設計とメタデータ

この記事の例のように、チャンクごとに見出し情報や元Markdownファイルの位置(例:ファイル名+見出しパス+行番号)をメタデータとして保持することが重要です。

このメタデータは、RAG検索結果から元のMarkdownドキュメントを調べるための鍵となります。

検索後に該当箇所を識別することで、前後の行などの情報を付加して精度を高めることができます。
Best Chunking Strategies for RAG in 2025

検索精度とチャンクの関係

適切なチャンクサイズと意味的境界に基づく分割は、検索精度と回答の関連性を大きく高めることが示されています

大きなチャンクはコンテキストを広く含みますが、関連情報の特定が難しくなる一方で、小さなチャンクは文脈が切れてしまい正確な情報抽出が難しくなります。

そのため、見出しや段落単位の自然境界を優先しつつ、必要に応じて再分割する戦略が多くの実装で推奨されています。
About SEO, GEO, and AI Search in 2025

まとめ

MarkdownをRAGシステムで効果的に使うには、ドキュメントの構造を理解し、意味単位を保ったチャンク分割を設計することが鍵です。

見出し階層を活かし、適切なチャンクサイズと重なりを設定することで、検索精度と生成回答の品質が向上します。

最新のチャンク戦略では、構造ベースの分割→埋め込み→メタデータ付与という流れが標準となりつつあり、これを踏まえた前処理設計が重要です。

次回は、実践的なドキュメントをMarkdown形式に整備し、RAG向けに前処理、Chroma DBに格納する実装例を解説します。

タイトルとURLをコピーしました