学習目標
- RAGアーキテクチャとその重要性を理解する
- 検索用にドキュメントを読み込み、処理する
- 埋め込みを作成し、ベクトルデータベースに保存する
- 質問応答用の検索チェーンを構築する
- 本番RAGシステムのベストプラクティスを適用する
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システムを構築してください:
- WebBaseLoaderを使用してドキュメントページを読み込む
- RecursiveCharacterTextSplitterでチャンクに分割する
- Chromaベクトルデータベースに保存する
- ソース引用付きで質問に回答するRAGチェーンを作成する
「このライブラリをインストールするにはどうすればよいですか?」や「主な機能は何ですか?」などの質問でテストしてください。
まとめ
- RAGは検索されたドキュメントに基づいてLLMのレスポンスを根付かせる
- Document Loadersはファイル、PDF、Webページなどからデータを取り込む
- Text Splittersはドキュメントを検索可能なチャンクに分割する
- Embeddingsはテキストを類似性検索用のベクトルに変換する
- Vector Storesは効率的なセマンティック検索を可能にする
- LCELを使用して検索チェーンを構成する
- ハイブリッド検索はベクトルとキーワードマッチングを組み合わせてより良い結果を得る