GPT-4やClaudeのような大規模言語モデル(LLM)は非常に強力ですが、根本的な制限があります。それは、知識がトレーニング時点で凍結されていることです。社内ドキュメント、データベース、リアルタイム情報にアクセスすることができません。検索拡張生成(RAG) は、LLMの生成能力と外部ソースから情報を取得する能力を組み合わせることで、まさにこの問題を解決します。
問題:LLMの限界
RAGについて話す前に、なぜそれが必要なのかを理解することが重要です。
- 静的な知識:LLMはトレーニング中に見たものだけを知っています。カットオフ日以降に起きた出来事について質問しても、答えることができません。
- ハルシネーション:LLMが答えを知らない場合、もっともらしいが完全に誤った情報を生成する傾向があります。
- プライベートデータへのアクセス不可:汎用LLMは、企業の社内ドキュメント、チケット、コードベースにアクセスできません。
RAGは、クエリ時に外部ソースから取得した関連コンテキストをモデルに提供することで、これら3つの問題すべてに対処します。
RAGとは?
検索拡張生成は、外部のナレッジベースから取得した情報でLLMに送信するプロンプトを強化するアーキテクチャです。モデルのパラメトリックな知識のみに頼るのではなく、RAGはまず関連情報を検索し、それをプロンプトに注入することで、正確で根拠のある回答を生成できるようにします。
RAGの詳細な仕組み
RAGアーキテクチャは2つの主要フェーズで構成されます:インデキシング(オフライン)と検索 + 生成(オンライン)。
フェーズ1:インデキシング(ドキュメントの取り込み)
インデキシングフェーズは、セマンティック検索のためにドキュメントを準備します。4つのステップで構成されます。
1. ドキュメントの読み込み
ドキュメントはPDFファイル、Webページ、データベース、Markdownファイル、APIなど、あらゆるソースから取得できます。ドキュメントローダーはこれらのドキュメントを読み取り、構造化テキストに変換します。
2. テキスト分割(チャンキング)
LLMのコンテキストウィンドウは限られており、ドキュメントは非常に長くなる可能性があります。テキストスプリッターはドキュメントをチャンクと呼ばれる小さな断片に分割します。チャンキングの品質は非常に重要です。小さすぎるチャンクはコンテキストを失い、大きすぎるチャンクは関連性を薄めます。
最も一般的な戦略は:
- 再帰的文字分割:
\n\n、\n、.などのセパレータを使用してテキストを再帰的に分割し、ドキュメント構造を尊重します。 - セマンティック分割:エンベディングを使用してテキスト内の自然な区切り点を見つけます。
- チャンクオーバーラップ:連続するチャンク間にオーバーラップを含め、境界でのコンテキストを保持します。
3. エンベディング
各チャンクはエンベディングモデル(OpenAIのtext-embedding-3-smallなど)を通じて数値ベクトル(エンベディング)に変換されます。これらのベクトルはテキストのセマンティックな意味を捉えます。類似した意味を持つ文は、多次元空間で近いベクトルを持ちます。
4. ベクトルストア
ベクトルはChromaDB、Pinecone、Weaviate、FAISSなどのベクトルストア(ベクトルデータベース)に保存されます。このデータベースは類似性検索に最適化されています。クエリが与えられると、最も類似したベクトル(つまり最も関連性の高いテキストチャンク)を見つけます。
フェーズ2:検索 + 生成
ユーザーが質問すると:
- 質問が同じエンベディングモデルを使用してエンベディングに変換されます。
- ベクトルストアが類似性検索(通常はコサイン類似度またはユークリッド距離)で最も類似したチャンクを見つけます。
- 取得されたチャンクがコンテキストとしてプロンプトに挿入されます。
- LLMが提供されたコンテキストに基づいて回答を生成します。
LangChainでRAGパイプラインを構築する
LangChainは、LLMベースのアプリケーションを構築するための最も人気のあるPython(およびJavaScript)フレームワークです。RAGパイプラインのすべてのコンポーネントに対する高レベルの抽象化を提供します。
インストール
pip install langchain langchain-openai langchain-community chromadb
ステップ1:ドキュメントの読み込み
LangChainは、さまざまなデータソース用の数十のドキュメントローダーを提供しています。
from langchain_community.document_loaders import ( PyPDFLoader, WebBaseLoader, DirectoryLoader, TextLoader, ) # PDFを読み込む pdf_loader = PyPDFLoader("docs/manual.pdf") pdf_docs = pdf_loader.load() # Webページを読み込む web_loader = WebBaseLoader("https://docs.example.com/guide") web_docs = web_loader.load() # ディレクトリからすべての.mdファイルを読み込む dir_loader = DirectoryLoader("./knowledge_base", glob="**/*.md", loader_cls=TextLoader) md_docs = dir_loader.load() all_docs = pdf_docs + web_docs + md_docs
ステップ2:ドキュメントをチャンクに分割
from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200, separators=["\n\n", "\n", ". ", " ", ""], ) chunks = text_splitter.split_documents(all_docs) print(f"元のドキュメント: {len(all_docs)}, チャンク: {len(chunks)}")
chunk_overlapパラメータは重要です。連続するチャンク間にオーバーラップを作成し、境界でコンテキストが失われないようにします。
ステップ3:エンベディングとベクトルストアの作成
from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma embedding_model = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = Chroma.from_documents( documents=chunks, embedding=embedding_model, persist_directory="./chroma_db", )
ステップ4:レトリーバーの作成
レトリーバーは、クエリが与えられるとベクトルストアから最も関連性の高いチャンクを取得するコンポーネントです。
retriever = vectorstore.as_retriever( search_type="similarity", search_kwargs={"k": 4}, ) relevant_docs = retriever.invoke("認証はどのように機能しますか?") for doc in relevant_docs: print(doc.page_content[:200]) print("---")
ステップ5:RAGチェーンの構築
LLMとプロンプトテンプレートですべてを組み合わせます。
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser llm = ChatOpenAI(model="gpt-4o", temperature=0) prompt = ChatPromptTemplate.from_template(""" 提供されたコンテキストのみに基づいて質問に回答してください。 コンテキストに十分な情報がない場合は、分からないと言ってください。 コンテキスト: {context} 質問:{question} 回答: """) def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs) rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser() ) response = rag_chain.invoke("システムの認証はどのように機能しますか?") print(response)
高度なRAGテクニック
基本的なパイプラインは十分に機能しますが、回答の品質を大幅に向上させるいくつかのテクニックがあります。
マルチクエリ検索
ユーザーのクエリが曖昧だったり、ドキュメントで使用されている言語と一致しない場合があります。マルチクエリレトリーバーは、複数の視点を捉えるために元の質問のバリエーションを自動的に生成します。
from langchain.retrievers import MultiQueryRetriever multi_retriever = MultiQueryRetriever.from_llm( retriever=vectorstore.as_retriever(), llm=llm, ) docs = multi_retriever.invoke("セキュリティのベストプラクティスは何ですか?")
コンテキスト圧縮
チャンクのすべてのコンテンツがクエリに関連しているわけではありません。コンテキスト圧縮レトリーバーは、LLMを使用して各取得チャンクから関連部分のみを抽出します。
from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import LLMChainExtractor compressor = LLMChainExtractor.from_llm(llm) compression_retriever = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=retriever, )
ハイブリッド検索
純粋なセマンティック検索が常に最適とは限りません。ハイブリッド検索はセマンティック検索(エンベディング)と字句検索(BM25、キーワードマッチング)を組み合わせて、より良い結果を得ます。
from langchain.retrievers import EnsembleRetriever from langchain_community.retrievers import BM25Retriever bm25_retriever = BM25Retriever.from_documents(chunks) bm25_retriever.k = 4 semantic_retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) hybrid_retriever = EnsembleRetriever( retrievers=[bm25_retriever, semantic_retriever], weights=[0.4, 0.6], )
会話型RAG(メモリ付き)
会話のコンテキストを記憶するRAGチャットボットを構築するには、会話履歴を考慮してユーザーの質問を再定式化するメモリを追加する必要があります。
from langchain.chains import create_history_aware_retriever from langchain_core.prompts import MessagesPlaceholder contextualize_prompt = ChatPromptTemplate.from_messages([ ("system", "チャット履歴とユーザーの最新の質問を考慮して、" "履歴なしでも理解できるように質問を再定式化してください。"), MessagesPlaceholder("chat_history"), ("human", "{input}"), ]) history_aware_retriever = create_history_aware_retriever( llm, retriever, contextualize_prompt )
ベストプラクティス
- 適切なチャンクサイズを選択する:さまざまなサイズ(500〜1500トークン)で実験してください。精確な回答には小さいチャンク、広いコンテキストには大きいチャンクを使用します。
- ドキュメントのメタデータを使用する:ソース、日付、カテゴリをチャンクのメタデータとして追加します。これにより、検索時に結果をフィルタリングできます。
- 品質を評価する:RAGASなどのフレームワークを使用して、faithfulness、relevancy、context precisionなどのメトリクスを測定します。
- ドキュメントの更新を管理する:ベクトルストアをデータソースと同期させるための再取り込みパイプラインを実装します。
- リランカーを追加する:初期検索後、リランキングモデル(Cohere Rerankなど)を使用して、実際の関連性に基づいて結果を並べ替えます。
まとめ
RAGは、特定の最新知識へのアクセスが必要なAIアプリケーションを構築するための標準アーキテクチャになりました。LangChainは、パイプラインのすべてのコンポーネントに対する抽象化を提供することで、実装を大幅に簡素化します。
次のステップ:
- ローカルで実験する:ChromaDBと少数のドキュメントから始めて、パイプラインに慣れましょう。
- LangSmithを探索する:LangSmithを使用して、本番環境でチェーンを監視およびデバッグしましょう。
- さまざまなエンベディングモデルを試す:
text-embedding-3-small、text-embedding-3-large、Sentence Transformersのオープンソースモデルを比較しましょう。 - ドキュメントを参照する:LangChainドキュメントは優れた、常に更新されるリソースです。