🇯🇵 JP | 🌐 EN

第4章:RAG(検索拡張生成)

知識に基づいたAIシステムを構築

📖 読了時間: 20-25分 📊 難易度: 中級 💻 コード例: 6 📝 演習: 1

学習目標

4.1 RAGとは?

RAG(Retrieval-Augmented Generation、検索拡張生成)は、回答を生成する前に知識ベースから関連情報を検索することで、LLMの応答を強化するテクニックです。

RAG(検索拡張生成)

2段階のプロセス:(1)ユーザーのクエリに基づいて知識ベースから関連ドキュメントを検索、(2)クエリと検索されたドキュメントの両方をコンテキストとして使用してレスポンスを生成。これにより、LLMのレスポンスを事実に基づいた最新の情報に根付かせることができます。

graph LR A[ユーザークエリ] --> B[Retriever] B --> C[ベクトルストア] C --> D[関連ドキュメント] D --> E[LLM + コンテキスト] A --> E E --> F[根拠のあるレスポンス] style B fill:#667eea,color:#fff style C fill:#11998e,color:#fff style E fill:#f093fb,color:#fff

なぜRAGか?

課題 RAGによる解決
LLMには知識のカットオフがある 最新情報を検索
LLMは事実を捏造(ハルシネーション)する可能性がある 実際のドキュメントに基づいてレスポンスを生成
LLMはドメイン知識が不足している カスタム知識ベースを追加
LLMはプライベートデータにアクセスできない 内部ドキュメントを安全にクエリ

4.2 ドキュメントの読み込み

最初のステップは、さまざまなソースからドキュメントを読み込むことです:

必要な依存関係

ドキュメントローダー用の追加パッケージをインストールしてください:

pip install beautifulsoup4 pypdf  # WebBaseLoaderとPyPDFLoader用
pip install chromadb              # Chromaベクトルストア用
コード例1:ドキュメントの読み込み
from langchain_community.document_loaders import (
    TextLoader,
    PyPDFLoader,
    WebBaseLoader,
    DirectoryLoader
)

# テキストファイルを読み込む
text_loader = TextLoader("./data/document.txt")
text_docs = text_loader.load()

# PDFを読み込む
pdf_loader = PyPDFLoader("./data/paper.pdf")
pdf_docs = pdf_loader.load()

# Webページを読み込む
web_loader = WebBaseLoader("https://example.com/article")
web_docs = web_loader.load()

# ディレクトリ内のすべてのファイルを読み込む
dir_loader = DirectoryLoader("./data/", glob="**/*.txt")
all_docs = dir_loader.load()

# 各ドキュメントにはコンテンツとメタデータがある
for doc in text_docs:
    print(f"コンテンツ: {doc.page_content[:100]}...")
    print(f"メタデータ: {doc.metadata}")

4.3 テキスト分割

効率的な検索のために、ドキュメントをチャンクに分割する必要があります:

コード例2:テキスト分割戦略
from langchain_text_splitters import (
    RecursiveCharacterTextSplitter,
    CharacterTextSplitter
)

# 推奨:RecursiveCharacterTextSplitter
# 自然な境界(段落、文、単語)で分割しようとする
splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,      # チャンクあたりの最大文字数
    chunk_overlap=200,    # コンテキストのためのチャンク間オーバーラップ
    separators=["\n\n", "\n", " ", ""]  # 分割優先順位
)

# ドキュメントを分割
chunks = splitter.split_documents(text_docs)

print(f"元のドキュメント数: {len(text_docs)}")
print(f"分割後: {len(chunks)} チャンク")

# 各チャンクはメタデータを保持
for i, chunk in enumerate(chunks[:3]):
    print(f"\nチャンク {i}: {len(chunk.page_content)} 文字")
    print(chunk.page_content[:100] + "...")

チャンキングのベストプラクティス(2025年)

  • セマンティックチャンキング:関連するコンテンツを一緒に保持(段落、セクション)
  • コンテキストヘッダー:各チャンクにセクション見出しを含める
  • チャンクサイズ:ユースケースに応じて500-1500トークン
  • オーバーラップ:10-20%のオーバーラップでチャンク境界のコンテキストを保持

4.4 埋め込みとベクトルストア

埋め込みはテキストを類似性検索用の数値ベクトルに変換します:

コード例3:埋め込みとベクトルストアの作成
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

# 埋め込みモデルを作成
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# 埋め込みをテスト
test_embedding = embeddings.embed_query("機械学習とは何ですか?")
print(f"埋め込み次元: {len(test_embedding)}")  # 1536

# ドキュメントからベクトルストアを作成
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"  # ディスクに永続化
)

# ベクトルストアをクエリ
results = vectorstore.similarity_search(
    "ニューラルネットワークの学習はどのように機能しますか?",
    k=3  # 上位3件を返す
)

for doc in results:
    print(f"スコア: {doc.metadata.get('score', 'N/A')}")
    print(f"コンテンツ: {doc.page_content[:200]}...\n")

一般的なベクトルストア

ベクトルストア タイプ 最適な用途
Chroma ローカル/クラウド 開発、小〜中規模
FAISS ローカル 高性能ローカル検索
Pinecone クラウド 本番環境、マネージドサービス
Weaviate クラウド/セルフホスト ハイブリッド検索、フィルタリング
Milvus クラウド/セルフホスト 大規模本番環境

4.5 RAGチェーンの構築

コード例4:完全なRAGパイプライン
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# コンポーネントをセットアップ
embeddings = OpenAIEmbeddings()
vectorstore = Chroma(persist_directory="./chroma_db", embedding_function=embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
llm = ChatOpenAI(model="gpt-4")

# RAGプロンプトを作成
rag_prompt = ChatPromptTemplate.from_messages([
    ("system", """あなたは親切なアシスタントです。以下のコンテキストのみに基づいて質問に回答してください。
コンテキストに関連情報が含まれていない場合は、そのように伝えてください。

コンテキスト:
{context}"""),
    ("human", "{question}")
])

# ドキュメントをフォーマットするヘルパー関数
def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

# LCELでRAGチェーンを構築
rag_chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | rag_prompt
    | llm
    | StrOutputParser()
)

# チェーンを使用
response = rag_chain.invoke("機械学習の主な種類は何ですか?")
print(response)

4.6 高度なRAGパターン

コード例5:ソース引用付きRAG
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from pydantic import BaseModel, Field
from typing import List

# 引用付きの構造化出力を定義
class AnswerWithSources(BaseModel):
    answer: str = Field(description="質問への回答")
    sources: List[str] = Field(description="使用したソースドキュメントのリスト")

# 構造化出力付きチェーンを作成
llm_with_sources = ChatOpenAI(model="gpt-4").with_structured_output(AnswerWithSources)

rag_prompt_with_sources = ChatPromptTemplate.from_messages([
    ("system", """コンテキストに基づいて回答してください。使用したソースを含めてください。

コンテキスト:
{context}"""),
    ("human", "{question}")
])

def format_docs_with_sources(docs):
    formatted = []
    for i, doc in enumerate(docs):
        source = doc.metadata.get("source", f"doc_{i}")
        formatted.append(f"[ソース: {source}]\n{doc.page_content}")
    return "\n\n".join(formatted)

rag_chain_with_sources = (
    {"context": retriever | format_docs_with_sources, "question": RunnablePassthrough()}
    | rag_prompt_with_sources
    | llm_with_sources
)

result = rag_chain_with_sources.invoke("ディープラーニングとは何ですか?")
print(f"回答: {result.answer}")
print(f"ソース: {result.sources}")
コード例6:ハイブリッド検索(ベクトル + キーワード)
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever

# キーワードベースのRetrieverを作成
bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 3

# ベクトルRetrieverを作成
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})

# アンサンブルで組み合わせ(ハイブリッド検索)
ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.4, 0.6]  # 40% キーワード、60% セマンティック
)

# RAGチェーンで使用
results = ensemble_retriever.invoke("ニューラルネットワーク最適化技術")
for doc in results:
    print(doc.page_content[:100] + "...")

演習

演習:ドキュメントQ&Aシステムを構築

ドキュメントサイトに関する質問に回答するRAGシステムを構築してください:

  1. WebBaseLoaderを使用してドキュメントページを読み込む
  2. RecursiveCharacterTextSplitterでチャンクに分割する
  3. Chromaベクトルデータベースに保存する
  4. ソース引用付きで質問に回答するRAGチェーンを作成する

「このライブラリをインストールするにはどうすればよいですか?」や「主な機能は何ですか?」などの質問でテストしてください。

まとめ

免責事項

本コンテンツは教育目的で提供されています。LangChainはLangChain Inc.によって開発・保守されています。