RAG(検索拡張生成)実装完全ガイド|LangChain で作る社内Q&Aボット
RAG(Retrieval-Augmented Generation)の仕組みと実装方法を解説。ベクトル検索、Embedding、LangChain・LlamaIndex 使用例まで完全網羅。
RAG(Retrieval-Augmented Generation / 検索拡張生成) は、LLM に外部データを参照させる技術です。社内ドキュメントをベースにした Q&A ボット、カスタマーサポート自動化、ナレッジ検索など、多くの実用的な用途があります。この記事では、RAG の仕組みから実装まで解説します。
なぜ RAG が必要か
LLM は膨大な知識を持っていますが、以下の弱点があります。
- 知識カットオフ: 学習時点までの情報しかない
- ハルシネーション: 知らないことも「それっぽく」答えてしまう
- プライベートデータ非対応: 社内情報を知らない
- 情報の信頼性: 出典が不明
RAG はこれらを以下の仕組みで解決します。
質問
↓
関連ドキュメントを検索
↓
ドキュメントを LLM に渡す
↓
ドキュメントを参照して回答生成
RAG の仕組み
基本フロー
┌─────────────┐
│ Knowledge │
│ Base │
└──────┬──────┘
│
↓ (事前処理)
┌─────────────┐
│ Embedding │
│ (ベクトル化) │
└──────┬──────┘
│
↓
┌─────────────┐
│ Vector DB │
└──────┬──────┘
↑
│ (検索)
┌─────────────┐
│ ユーザー │
│ 質問 │
└──────┬──────┘
│
↓
┌─────────────┐
│ 関連情報 + │
│ 質問 │
└──────┬──────┘
│
↓
┌─────────────┐
│ LLM │
└──────┬──────┘
│
↓
┌─────────────┐
│ 回答 │
└─────────────┘
Embedding とは
テキストを 数値ベクトルに変換する技術です。意味が近いテキストは、ベクトル空間上で近い位置になります。
"犬" → [0.12, 0.89, 0.34, ...]
"猫" → [0.15, 0.85, 0.31, ...] ← 近い
"車" → [0.78, 0.12, 0.45, ...] ← 遠い
OpenAI の text-embedding-3-large やオープンソースの sentence-transformers がよく使われます。
ベクトル DB
検索対象のベクトルを保存するデータベースです。
主要なベクトル DB:
| DB | 特徴 |
|---|---|
| Pinecone | マネージドサービス、使いやすい |
| Weaviate | オープンソース、セマンティック検索 |
| Qdrant | Rust 製、高速 |
| Chroma | シンプル、開発用に最適 |
| pgvector | PostgreSQL 拡張、既存DB活用 |
| Milvus | エンタープライズ向け |
LangChain での実装
LangChain は RAG 実装の定番ライブラリです。
セットアップ
pip install langchain langchain-openai langchain-community chromadb
基本的な RAG の実装
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
# 1. ドキュメントをロード
loader = TextLoader("docs/handbook.txt")
documents = loader.load()
# 2. ドキュメントを分割
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
chunks = text_splitter.split_documents(documents)
# 3. Embedding してベクトル DB に保存
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db"
)
# 4. Retrieval QA チェーンを作成
llm = ChatOpenAI(model="gpt-4o", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
return_source_documents=True
)
# 5. 質問
result = qa_chain.invoke({"query": "有給休暇の取得方法は?"})
print(result["result"])
print("ソース:", [doc.metadata for doc in result["source_documents"]])
Chunking の最適化
ドキュメントの分割方法は RAG の精度に大きく影響します。
Chunk Size の選び方
| Chunk Size | 特徴 |
|---|---|
| 200〜500 | 正確な情報検索、文脈不足の可能性 |
| 500〜1000 | バランスが良い(推奨) |
| 1000〜2000 | 文脈豊富、関連性が下がる可能性 |
Overlap の役割
RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200 # 前後のチャンクと 200 文字重複
)
重複があることで、文の途中で切れた情報も復元できます。
セマンティック Chunking
langchain_experimental.text_splitter.SemanticChunker を使うと、意味的なまとまりで分割できます。
from langchain_experimental.text_splitter import SemanticChunker
text_splitter = SemanticChunker(
OpenAIEmbeddings(),
breakpoint_threshold_type="percentile"
)
chunks = text_splitter.create_documents([text])
Retriever の最適化
1. Top-K の調整
retriever = vectorstore.as_retriever(
search_kwargs={"k": 5} # 上位 5 件を取得
)
k を増やすと情報量が増えますが、関連性の低い情報も混ざるリスクがあります。
2. MMR(Maximal Marginal Relevance)
多様性を持たせつつ関連性の高い結果を取得:
retriever = vectorstore.as_retriever(
search_type="mmr",
search_kwargs={"k": 5, "lambda_mult": 0.5}
)
3. ハイブリッド検索
ベクトル検索と BM25(キーワード検索)を組み合わせます。
from langchain.retrievers import BM25Retriever, EnsembleRetriever
bm25_retriever = BM25Retriever.from_documents(chunks)
vector_retriever = vectorstore.as_retriever()
ensemble = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.5, 0.5]
)
プロンプトテンプレートのカスタマイズ
デフォルトのプロンプトを日本語に最適化します。
from langchain.prompts import PromptTemplate
template = """
以下のコンテキスト情報を使って質問に答えてください。
情報にない内容は「提供された情報には記載されていません」と答えてください。
推測や一般知識で答えないでください。
コンテキスト:
{context}
質問: {question}
回答:
"""
prompt = PromptTemplate(
template=template,
input_variables=["context", "question"]
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
chain_type_kwargs={"prompt": prompt}
)
複雑な RAG: 質問の書き換え
ユーザーの質問を LLM で書き換えて検索精度を上げる手法です。
from langchain.chains import MultiQueryRetriever
multi_query_retriever = MultiQueryRetriever.from_llm(
retriever=vectorstore.as_retriever(),
llm=llm
)
# 「有給の取得方法」→ 複数の検索クエリを生成
# - 「有給休暇の申請手順」
# - 「有給休暇の取得ルール」
# - 「有休を取る方法」
docs = multi_query_retriever.invoke("有給の取得方法")
Self-RAG: 自己評価する RAG
検索結果の質を LLM 自身が評価し、必要なら再検索する高度な手法。
from langchain_community.retrievers import SelfQueryRetriever
# 検索結果を評価し、不十分なら再検索
# 詳細な実装は LangChain ドキュメント参照
ベクトル DB 別の選び方
Chroma(開発・小規模)
vectorstore = Chroma.from_documents(chunks, embeddings)
- 追加インストール不要
- ローカルで動作
- 〜10万件程度までおすすめ
pgvector(既存 PostgreSQL 活用)
from langchain_community.vectorstores import PGVector
vectorstore = PGVector.from_documents(
chunks,
embeddings,
connection_string="postgresql://user:pass@localhost/db"
)
- 既存のDBを流用
- トランザクション対応
- 〜100万件程度までおすすめ
Pinecone(マネージド・大規模)
from langchain_pinecone import PineconeVectorStore
vectorstore = PineconeVectorStore.from_documents(
chunks,
embeddings,
index_name="my-index"
)
- フルマネージド
- 数億件までスケール
- エンタープライズ向け
LlamaIndex との比較
LlamaIndex も RAG に特化したライブラリです。
| 項目 | LangChain | LlamaIndex |
|---|---|---|
| 汎用性 | 高い | RAG 特化 |
| ドキュメント | 豊富 | 簡潔 |
| 学習コスト | 中 | 低 |
| エージェント | 強い | 限定的 |
RAG だけなら LlamaIndex の方がシンプルです。多様な AI ワークフローを扱うなら LangChain が有利です。
コスト管理
Embedding API のコスト
OpenAI text-embedding-3-large の場合:
- 100万トークン = $0.13
本番運用でのコスト最適化
- キャッシュ: 同じ質問は再検索しない
- バッチ処理: 複数ドキュメントをまとめて埋め込み
- モデル選択: 小さいモデルから試す
- チャンクサイズ: 小さくしすぎない
import redis
import hashlib
def cached_embedding(text, embeddings):
key = hashlib.md5(text.encode()).hexdigest()
cached = redis_client.get(key)
if cached:
return json.loads(cached)
vec = embeddings.embed_query(text)
redis_client.set(key, json.dumps(vec), ex=86400)
return vec
評価と改善
RAG の品質を評価するフレームワークを紹介します。
RAGAS ライブラリ
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_recall,
context_precision
)
result = evaluate(
dataset=eval_dataset,
metrics=[
faithfulness, # 回答の忠実度
answer_relevancy, # 質問との関連性
context_recall, # コンテキストの再現率
context_precision # コンテキストの精度
]
)
実装チェックリスト
基本実装:
- ドキュメントの準備
- Chunking(適切なサイズとoverlap)
- Embedding モデルの選定
- ベクトル DB の選定
- Retriever の実装
- LLM チェーンの作成
品質向上:
- プロンプトのカスタマイズ
- ハイブリッド検索
- 評価データセットの作成
- メトリクスによる評価
- 継続的改善
運用:
- ログ記録
- エラーハンドリング
- コスト監視
- A/B テスト
- フィードバックループ
まとめ
RAG は LLM の弱点を補い、実用的な AI アプリケーションを構築するための重要な技術です。
導入のステップ:
- 小さく始める: Chroma + LangChain で PoC
- 評価する: 実際のユーザーからフィードバック収集
- 最適化: Chunking、Retriever、プロンプトを調整
- スケール: Pinecone 等のマネージドサービスへ移行
社内ドキュメント、API 仕様書、過去のサポート履歴など、構造化されていないテキストデータがある組織なら、RAG で大きな価値を生み出せます。まずは手元のドキュメントで試してみましょう。