RAG-readyデータの作り方:企業事例とコード実装ガイド
「RAGの精度は、データ準備で8割決まる」 — どんなに優れたLLMやベクトルDBを使っても、入力データの品質が低ければ精度は出ません。
本記事では、実際の企業事例とコードを交えながら、RAG用データ作成の全工程を解説します。
1. RAGデータパイプラインの全体像
[生データ]
│
├── PDF / DOCX / PPTX
├── Webページ / Wiki
├── データベース / API
└── Slack / メール
│
▼
[① 抽出・パース] ← Docling / Unstructured.io / LlamaParse
│
▼
[② クリーニング] ← ノイズ除去・重複排除・PII除去
│
▼
[③ チャンキング] ← Recursive / Semantic / Structure-aware
│
▼
[④ メタデータ付与] ← LLM Enrichment / ルールベース
│
▼
[⑤ Embedding] ← OpenAI / Cohere / Ruri v3
│
▼
[⑥ インデキシング] ← Pinecone / Weaviate / pgvector
│
▼
[⑦ 品質評価] ← RAGAS / DeepEval
2. ドキュメントの抽出・パース
Docling(IBM)で PDF → Markdown
from docling.document_converter import DocumentConverter
converter = DocumentConverter()
result = converter.convert("company_manual.pdf")
markdown = result.document.export_to_markdown()
Unstructured.io で多形式対応
from unstructured.partition.auto import partition
elements = partition(filename="report.pdf", strategy="hi_res")
for element in elements[:5]:
print(f"[{type(element).__name__}] {str(element)[:100]}")
# → [Title] 2024年度 事業報告
# → [NarrativeText] 当社は2024年度、前年比20%の成長を...
# → [Table] | 指標 | 数値 | 前年比 |...
ツール比較
| ツール | 表の精度 | 速度 | OSS | 特徴 |
|---|---|---|---|---|
| Docling (IBM) | 97.9% | 中 | ✅ | 構造保持に優れる |
| Unstructured.io | 90%+ | 速い | ✅ | OCR・多形式対応 |
| LlamaParse | 95%+ | 最速 | ❌ | クラウドAPI |
3. クリーニング
import re
def clean_for_rag(text: str) -> str:
"""RAG用テキストクリーニング"""
# ヘッダー・フッター除去(ページ番号等)
text = re.sub(r"^\s*\d+\s*$", "", text, flags=re.MULTILINE)
# 過剰な空白を正規化
text = re.sub(r"\n{3,}", "\n\n", text)
text = re.sub(r" {2,}", " ", text)
# 制御文字除去
text = re.sub(r"[\x00-\x08\x0b-\x0c\x0e-\x1f]", "", text)
# 空行のみのセクション除去
text = re.sub(r"\n\s*\n\s*\n", "\n\n", text)
return text.strip()
# 使用例
raw = "\n\n\n 2024年度報告 \n\n\n\n当社は...\n 42 \n"
cleaned = clean_for_rag(raw)
print(cleaned)
# → "2024年度報告\n\n当社は..."
PII(個人情報)の除去
import re
PII_PATTERNS = {
"email": r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",
"phone_jp": r"0\d{1,4}-\d{1,4}-\d{3,4}",
"my_number": r"\d{4}\s?\d{4}\s?\d{4}",
}
def scrub_pii(text: str) -> str:
for label, pattern in PII_PATTERNS.items():
text = re.sub(pattern, f"[{label.upper()}_REDACTED]", text)
return text
print(scrub_pii("連絡先: tanaka@example.com, 03-1234-5678"))
# → "連絡先: [EMAIL_REDACTED], [PHONE_JP_REDACTED]"
4. チャンキング戦略
2026年の結論: Recursive Character Splitting が最強
FloTorchベンチマーク(2026年)の結果、512トークンのRecursive Character Splittingが回答精度・検索F1スコアの両方で最高性能を達成。セマンティックチャンキングはベクトル数が3〜5倍に膨張します。
| 戦略 | 精度 | ベクトル数 | コスト | 推奨度 |
|---|---|---|---|---|
| Recursive Character | ★★★★★ | 1x | 低 | 本番推奨 |
| Semantic | ★★★★☆ | 3-5x | 高 | 高精度要件のみ |
| Fixed-size | ★★★☆☆ | 1x | 最低 | プロトタイプ |
| Structure-aware | ★★★★☆ | 1x | 中 | 構造化文書向け |
実装コード
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 本番推奨設定
splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=50, # 10%オーバーラップ
separators=[
"\n## ", # Markdownの見出し(H2)
"\n### ", # H3
"\n#### ", # H4
"\n\n", # 段落
"\n", # 改行
"。", # 日本語の文末
"、", # 読点
" ", # スペース
"", # 文字単位(最終手段)
],
length_function=len,
)
text = """## 1. 事業概要
当社は2024年度、AI事業を中心に大きく成長しました。
売上高は前年比20%増の180億円を記録。
## 2. 財務ハイライト
営業利益は30億円(前年比36%増)。
従業員数は1,200人に拡大。"""
chunks = splitter.split_text(text)
for i, chunk in enumerate(chunks):
print(f"--- Chunk {i+1} ({len(chunk)}文字) ---")
print(chunk[:100])
具体的なチャンク分割例
入力ドキュメント(500文字):
# 株式会社ABC AIレポート 2024
## 1. AI導入の背景
人手不足が深刻化する中、当社はAI Agent導入を決定した。
特にカスタマーサポート部門では、問い合わせ件数が前年比
150%に増加し、対応品質の維持が困難になっていた。
## 2. 導入したソリューション
LangChainベースのRAGシステムを構築。社内マニュアル
2,000ページをベクトル化し、オペレーターの回答支援を実現。
## 3. 成果
- 初回解決率: 45% → 78%
- 平均対応時間: 15分 → 6分
- 顧客満足度: 3.2 → 4.5(5点満点)
出力チャンク:
| # | チャンク内容 | 文字数 |
|---|---|---|
| 1 | # 株式会社ABC AIレポート 2024\n\n## 1. AI導入の背景\n人手不足が深刻化する中...対応品質の維持が困難になっていた。 | 156 |
| 2 | ## 2. 導入したソリューション\nLangChainベースのRAGシステムを構築...オペレーターの回答支援を実現。 | 98 |
| 3 | ## 3. 成果\n- 初回解決率: 45% → 78%\n- 平均対応時間: 15分 → 6分\n- 顧客満足度: 3.2 → 4.5(5点満点) | 95 |
→ セクション見出しで自然に分割され、各チャンクが自己完結的な意味を持つ
5. メタデータ付与
LLMでメタデータを自動生成
2025年12月の研究論文で、LLM生成メタデータにより精度が最大12%向上することが示されました(0.733 → 0.825)。
from openai import OpenAI
import json
client = OpenAI()
def enrich_metadata(chunk: str, source: str) -> dict:
"""LLMでチャンクにメタデータを付与"""
response = client.chat.completions.create(
model="gpt-4o-mini",
response_format={"type": "json_object"},
messages=[{
"role": "user",
"content": f"""以下のテキストチャンクを分析し、JSONでメタデータを出力してください:
- summary: 1文の要約(50文字以内)
- keywords: キーワード配列(3-5個)
- category: カテゴリ(技術/ビジネス/法務/人事/財務から選択)
- entities: 固有名詞の配列
テキスト:
{chunk}"""
}],
)
metadata = json.loads(response.choices[0].message.content)
metadata["source"] = source
metadata["chunk_length"] = len(chunk)
return metadata
# 使用例
chunk = "LangChainベースのRAGシステムを構築。社内マニュアル2,000ページをベクトル化し、オペレーターの回答支援を実現。"
meta = enrich_metadata(chunk, "ai_report_2024.pdf")
print(json.dumps(meta, ensure_ascii=False, indent=2))
出力例:
{
"summary": "LangChainでRAGシステムを構築し社内マニュアルをベクトル化",
"keywords": ["LangChain", "RAG", "ベクトル化", "社内マニュアル", "回答支援"],
"category": "技術",
"entities": ["LangChain"],
"source": "ai_report_2024.pdf",
"chunk_length": 62
}
6. Embeddingモデルの選定
2026年モデル比較
| モデル | 提供元 | MTEB | 次元数 | 日本語 | コスト |
|---|---|---|---|---|---|
| Cohere embed-v4 | Cohere | 65.2 | 可変 | ◎ | 中 |
| text-embedding-3-large | OpenAI | 64.6 | 3072 | ○ | 中 |
| text-embedding-3-small | OpenAI | 62.3 | 1536 | ○ | 低 |
| BGE-M3 | BAAI | 63.0 | 1024 | ◎ | 無料 |
| Ruri v3 | 名古屋大学 | — | 1024 | ★★★ | 無料 |
| multilingual-e5-large | MS | 63.0 | 1024 | ◎ | 無料 |
Embedding生成コード
from openai import OpenAI
client = OpenAI()
def embed_chunks(chunks: list[str], model="text-embedding-3-small") -> list[list[float]]:
"""OpenAI APIでチャンクをベクトル化"""
response = client.embeddings.create(
input=chunks,
model=model,
)
return [item.embedding for item in response.data]
# 使用例
chunks = [
"LangChainベースのRAGシステムを構築。",
"初回解決率が45%から78%に向上。",
"顧客満足度は3.2から4.5に改善。",
]
vectors = embed_chunks(chunks)
print(f"ベクトル数: {len(vectors)}, 次元数: {len(vectors[0])}")
# → ベクトル数: 3, 次元数: 1536
日本語特化: Ruri v3
from sentence_transformers import SentenceTransformer
# 名古屋大学開発の日本語特化モデル
model = SentenceTransformer("cl-nagoya/ruri-v3-310m")
# 日本語テキストを直接ベクトル化(MeCab不要)
texts = [
"経費精算の承認フローを自動化する方法",
"Slackで経費申請ボットを構築する手順",
"来期の予算計画について",
]
embeddings = model.encode(texts)
print(f"次元数: {embeddings.shape[1]}") # → 1024
7. ベクトルDBへのインデキシング
pgvector(PostgreSQL拡張)の場合
-- テーブル作成
CREATE TABLE rag_documents (
id BIGSERIAL PRIMARY KEY,
content TEXT NOT NULL,
metadata JSONB DEFAULT '{}',
embedding vector(1536),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- インデックス作成(HNSW: 高速近傍探索)
CREATE INDEX ON rag_documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 検索クエリ(類似度上位5件)
SELECT id, content, metadata,
1 - (embedding <=> $1::vector) AS similarity
FROM rag_documents
WHERE metadata->>'category' = '技術'
ORDER BY embedding <=> $1::vector
LIMIT 5;
Pineconeの場合
from pinecone import Pinecone
pc = Pinecone(api_key="YOUR_API_KEY")
index = pc.Index("rag-documents")
# アップサート(メタデータ付き)
index.upsert(vectors=[
{
"id": "chunk_001",
"values": embedding_vector, # [0.012, -0.034, ...]
"metadata": {
"source": "ai_report_2024.pdf",
"category": "技術",
"keywords": ["LangChain", "RAG"],
"date": "2024-12-01",
},
},
])
# メタデータフィルタリング付き検索
results = index.query(
vector=query_vector,
top_k=5,
filter={"category": {"$eq": "技術"}},
include_metadata=True,
)
8. 品質評価(RAGAS)
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_precision,
context_recall,
)
from datasets import Dataset
# テストデータ
eval_data = {
"question": [
"初回解決率はどのくらい改善しましたか?",
"どのフレームワークを使いましたか?",
],
"answer": [
"初回解決率は45%から78%に改善しました。",
"LangChainベースのRAGシステムを使用しました。",
],
"contexts": [
["初回解決率: 45% → 78%、平均対応時間: 15分 → 6分"],
["LangChainベースのRAGシステムを構築。社内マニュアル2,000ページをベクトル化。"],
],
"ground_truth": [
"初回解決率は45%から78%に向上した。",
"LangChainを使用した。",
],
}
dataset = Dataset.from_dict(eval_data)
results = evaluate(
dataset=dataset,
metrics=[faithfulness, answer_relevancy, context_precision, context_recall],
)
print(results)
# → {'faithfulness': 0.95, 'answer_relevancy': 0.92,
# → 'context_precision': 0.88, 'context_recall': 0.90}
評価指標の意味
| 指標 | 何を測るか | 目標値 |
|---|---|---|
| Faithfulness | 回答が検索結果に基づいているか | > 0.90 |
| Answer Relevancy | 回答が質問に答えているか | > 0.85 |
| Context Precision | 検索結果は質問に関連しているか | > 0.80 |
| Context Recall | 必要な情報がすべて検索されたか | > 0.80 |
9. 企業事例まとめ
| 企業 | アプローチ | 主な成果 |
|---|---|---|
| LINEヤフー | 社内ナレッジRAG + 自動品質評価 | 98%精度、年80万時間削減目標 |
| Anthropic | Contextual Retrieval(文脈付与) | 検索失敗率49%削減 |
| Salesforce | Enriched Index(多段階エンリッチ) | 99.99%稼働率 |
| IBM (Docling) | PDF→Markdown変換OSS | 表抽出97.9%精度 |
| Notion | CDC+データレイク基盤 | 権限付きリアルタイムRAG |
| 出光興産 | 多言語RAG(日→英→日) | 90%+応答精度 |
| Shopify | JIT命令 + GTXテスト | 50+ツール対応Agent |
| Elastic | Elasticsearch + RAG | LG CNSで検索精度95%向上 |
10. 実践チェックリスト
- ✅ データソースの棚卸し(PDF / Wiki / DB / API)
- ✅ パースツール選定(Docling / Unstructured / LlamaParse)
- ✅ クリーニングパイプライン構築(ノイズ除去・PII除去・正規化)
- ✅ チャンキング戦略決定(Recursive 512トークン推奨)
- ✅ メタデータ設計(source, category, date, keywords)
- ✅ Embeddingモデル選定(日本語: Ruri v3 / 多言語: BGE-M3)
- ✅ ベクトルDB選定(Pinecone / pgvector / Weaviate)
- ✅ 権限・アクセス制御の設計
- ✅ RAGASで品質ベースライン測定
- ✅ CI/CDでデータ更新パイプライン自動化