メインコンテンツへスキップ
Code & Craft
AI活用 約9分で読めます

RAG(検索拡張生成)実装完全ガイド|LangChain で作る社内Q&Aボット

RAG(Retrieval-Augmented Generation)の仕組みと実装方法を解説。ベクトル検索、Embedding、LangChain・LlamaIndex 使用例まで完全網羅。

RAG(Retrieval-Augmented Generation / 検索拡張生成) は、LLM に外部データを参照させる技術です。社内ドキュメントをベースにした Q&A ボット、カスタマーサポート自動化、ナレッジ検索など、多くの実用的な用途があります。この記事では、RAG の仕組みから実装まで解説します。

なぜ RAG が必要か

LLM は膨大な知識を持っていますが、以下の弱点があります。

  1. 知識カットオフ: 学習時点までの情報しかない
  2. ハルシネーション: 知らないことも「それっぽく」答えてしまう
  3. プライベートデータ非対応: 社内情報を知らない
  4. 情報の信頼性: 出典が不明

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オープンソース、セマンティック検索
QdrantRust 製、高速
Chromaシンプル、開発用に最適
pgvectorPostgreSQL 拡張、既存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 に特化したライブラリです。

項目LangChainLlamaIndex
汎用性高いRAG 特化
ドキュメント豊富簡潔
学習コスト
エージェント強い限定的

RAG だけなら LlamaIndex の方がシンプルです。多様な AI ワークフローを扱うなら LangChain が有利です。

コスト管理

Embedding API のコスト

OpenAI text-embedding-3-large の場合:

  • 100万トークン = $0.13

本番運用でのコスト最適化

  1. キャッシュ: 同じ質問は再検索しない
  2. バッチ処理: 複数ドキュメントをまとめて埋め込み
  3. モデル選択: 小さいモデルから試す
  4. チャンクサイズ: 小さくしすぎない
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 アプリケーションを構築するための重要な技術です。

導入のステップ:

  1. 小さく始める: Chroma + LangChain で PoC
  2. 評価する: 実際のユーザーからフィードバック収集
  3. 最適化: Chunking、Retriever、プロンプトを調整
  4. スケール: Pinecone 等のマネージドサービスへ移行

社内ドキュメント、API 仕様書、過去のサポート履歴など、構造化されていないテキストデータがある組織なら、RAG で大きな価値を生み出せます。まずは手元のドキュメントで試してみましょう。

参考リンク

#RAG #LangChain #AI #ベクトル検索 #LLM
シェア: