LangChain で RAG 構築:Retriever・VectorStore の設計ベストプラクティス

近年、大規模言語モデル(LLM)の活用が急速に進む中で、Retrieval-Augmented Generation(RAG)システムが注目を集めています。RAG は、外部の知識ベースから関連情報を取得し、それを基に回答を生成する技術で、LLM の知識不足や幻覚問題を解決する有効な手段です。
この記事では、LangChain を使用した RAG システムの構築において、特に Retriever と VectorStore の設計に焦点を当てたベストプラクティスをご紹介します。実際のコード例とともに、効率的で実用的な RAG システムを構築するための具体的な手法を学んでいただけます。
背景
RAG が必要になる理由
大規模言語モデルは非常に優秀ですが、以下のような課題があります。
mermaidflowchart TD
llm[大規模言語モデル] -->|課題1| knowledge[知識の限界]
llm -->|課題2| hallucination[幻覚・不正確な情報]
llm -->|課題3| update[情報の更新困難]
knowledge --> solution[RAGシステム]
hallucination --> solution
update --> solution
solution --> retrieval[外部知識の検索]
solution --> generation[正確な回答生成]
RAG システムが解決する主な問題は次の通りです:
- 知識の範囲制限: 学習データの範囲内でしか回答できない
- 情報の古さ: 学習データのカットオフ時点以降の情報が反映されない
- 幻覚問題: 存在しない情報を事実として回答してしまう可能性
これらの課題により、企業での実用的な AI システム構築では、最新かつ正確な情報を参照できる RAG アプローチが不可欠となっています。
従来の検索システムとの違い
従来のキーワードベース検索と RAG の違いを以下の表で整理します:
項目 | 従来の検索システム | RAG システム |
---|---|---|
検索方式 | キーワードマッチング | セマンティック検索 |
結果の形式 | 文書リスト | 自然言語での回答 |
意図理解 | 限定的 | 高度な文脈理解 |
カスタマイズ性 | 低い | 高い |
実装複雑度 | 低い | 中〜高い |
RAG システムでは、ベクトル化された文書から意味的に関連する情報を検索し、それを基に自然な回答を生成できます。これにより、ユーザーの質問意図をより深く理解した回答が可能になります。
LangChain を選ぶメリット
LangChain が RAG 構築で選ばれる理由は以下の通りです:
- 豊富な VectorStore 対応: Chroma、FAISS、Pinecone 等、主要なベクトルデータベースをサポート
- 柔軟な Retriever 設計: 様々な検索戦略を簡単に実装・切り替え可能
- エコシステムの充実: LLM、Embedding、Chain 等の統合が容易
- 活発なコミュニティ: 継続的なアップデートと豊富な情報
これらの特徴により、プロトタイプから本番運用まで一貫して LangChain を使用できるメリットがあります。
課題
VectorStore 選択の複雑さ
RAG システム構築において、適切な VectorStore の選択は重要な課題です。選択を困難にする主な要因を図で示します:
mermaidflowchart LR
choice[VectorStore選択] -->|考慮要素1| performance[パフォーマンス要件]
choice -->|考慮要素2| scale[スケール要件]
choice -->|考慮要素3| cost[コスト制約]
choice -->|考慮要素4| maintenance[運用・保守性]
performance --> local[ローカル型<br/>FAISS, Chroma]
performance --> cloud[クラウド型<br/>Pinecone, Weaviate]
scale --> distributed[分散対応]
scale --> single[単体運用]
各 VectorStore には以下のような特性があります:
- FAISS: 高速だが単体サーバー向け、運用が複雑
- Chroma: 開発が容易だが大規模運用に制限
- Pinecone: 高機能だが月額コストが発生
- Weaviate: 多機能だが学習コストが高い
この多様性により、要件に最適な選択を行うのが困難になっています。
Retriever 設計の難しさ
効果的な Retriever を設計する際に直面する主な課題は以下です:
- 検索戦略の選択: Dense、Sparse、Hybrid のどれを選ぶか
- チャンクサイズの最適化: 文書分割の粒度設定
- 類似度計算方式: コサイン類似度、ユークリッド距離等の選択
- フィルタリング条件: メタデータを使った条件絞り込み
これらの要素が相互に影響するため、最適な組み合わせを見つけるのは試行錯誤が必要です。
検索精度とパフォーマンスのバランス
RAG システムでは検索精度の向上とレスポンス速度の確保を両立する必要があります:
精度向上手法 | パフォーマンスへの影響 | 実装コスト |
---|---|---|
大きな Embedding モデル | レスポンス時間増加 | 低い |
複数 Retriever の組み合わせ | 計算量増加 | 中程度 |
Re-ranking 機能 | 大幅な時間増加 | 高い |
インデックス最適化 | メモリ使用量増加 | 中程度 |
この調整は、システムの用途や要求仕様によって最適解が変わるため、慎重な設計が求められます。
解決策
LangChain における VectorStore の種類と特徴
LangChain で利用可能な主要な VectorStore の特徴を整理します:
mermaidclassDiagram
class VectorStore {
+add_documents()
+similarity_search()
+as_retriever()
}
class FAISS {
+高速検索
+ローカル運用
+大規模対応
}
class Chroma {
+開発容易
+永続化対応
+メタデータ豊富
}
class Pinecone {
+クラウド型
+スケーラブル
+高機能
}
VectorStore <|-- FAISS
VectorStore <|-- Chroma
VectorStore <|-- Pinecone
各 VectorStore の具体的な選択指針:
開発・プロトタイプ段階
推奨: Chroma
- 簡単なセットアップ
- 永続化機能付き
- メタデータフィルタリング対応
中規模本番運用
推奨: FAISS + 独自永続化
- 高速な検索性能
- メモリ効率が良い
- カスタマイズ性が高い
大規模・エンタープライズ
推奨: Pinecone または Weaviate
- 分散処理対応
- 運用負荷が低い
- 高度な機能セット
Retriever の設計指針
効果的な Retriever を設計するための基本方針をご紹介します:
1. チャンクサイズの最適化
文書分割の粒度は検索精度に大きく影響します:
javascript// 推奨されるチャンクサイズ設定例
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000, // 基本は1000文字
chunkOverlap: 200, // 20%のオーバーラップ
separators: ['\n\n', '\n', '。', '.', ' ', ''],
});
上記設定の根拠:
- 1000 文字: ほとんどの Embedding モデルの最適範囲
- 200 文字オーバーラップ: 文脈の連続性を保持
- 階層的区切り: 自然な文章境界で分割
2. 検索戦略の選択
用途に応じた検索戦略の選び方:
検索タイプ | 適用場面 | 特徴 |
---|---|---|
Dense Retrieval | 意味的類似性重視 | セマンティック検索に優秀 |
Sparse Retrieval | キーワード重視 | 専門用語・固有名詞に強い |
Hybrid Retrieval | バランス重視 | 両方の長所を活用 |
3. Multi-Query Retriever の活用
検索精度向上のための高度な手法:
typescript// Multi-Query Retriever の基本実装
import { MultiQueryRetriever } from 'langchain/retrievers/multi_query';
const retriever = MultiQueryRetriever.fromLLM({
llm: chatModel,
retriever: vectorStore.asRetriever(),
queryCount: 3, // 3つの異なる質問を生成
});
この手法により、元の質問から複数の検索クエリを生成し、より幅広い関連文書を取得できます。
最適化のための設定パラメータ
パフォーマンスと精度を調整するための重要パラメータ:
VectorStore 共通パラメータ
typescriptconst retrieverConfig = {
k: 5, // 取得文書数(基本は3-5)
scoreThreshold: 0.7, // 類似度閾値(0.6-0.8推奨)
fetchK: 20, // 事前フィルタリング数
lambda: 0.5, // Diversity調整(MMR使用時)
};
検索品質向上パラメータ
javascript// Re-ranking用の設定
const rerankConfig = {
model: 'cross-encoder/ms-marco-MiniLM-L-6-v2',
topK: 10, // Re-rank対象数
returnTopK: 3, // 最終返却数
};
これらのパラメータを調整することで、用途に応じた最適な検索結果を得ることができます。
具体例
Chroma + OpenAI Embeddings での基本構築
最も一般的な RAG システムの構築例から始めます。
環境準備
まず必要なパッケージをインストールします:
bashyarn add langchain @langchain/openai @langchain/chroma
yarn add -D @types/node
基本的な VectorStore の構築
typescriptimport { Chroma } from '@langchain/chroma';
import { OpenAIEmbeddings } from '@langchain/openai';
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
環境変数と Embedding の初期化:
typescript// 環境設定
const embeddings = new OpenAIEmbeddings({
openAIApiKey: process.env.OPENAI_API_KEY,
modelName: 'text-embedding-3-small', // コスト効率重視
});
// テキスト分割器の設定
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 200,
});
VectorStore の作成と文書の追加:
typescript// Chromaインスタンスの作成
const vectorStore = new Chroma(embeddings, {
collectionName: 'rag-collection',
url: process.env.CHROMA_URL || 'http://localhost:8000',
});
// 文書データの準備と追加
async function addDocuments(texts: string[]) {
// テキストを適切なサイズに分割
const docs = await textSplitter.createDocuments(texts);
// メタデータの追加
const docsWithMetadata = docs.map((doc, index) => ({
...doc,
metadata: {
...doc.metadata,
source: `document_${index}`,
timestamp: new Date().toISOString(),
},
}));
// VectorStoreに追加
await vectorStore.addDocuments(docsWithMetadata);
console.log(
`${docsWithMetadata.length} documents added successfully`
);
}
基本的な検索と Retriever 作成
typescript// Retrieverの作成
const retriever = vectorStore.asRetriever({
k: 5, // 上位5件を取得
searchType: 'similarity_score_threshold',
searchKwargs: {
scoreThreshold: 0.7, // 類似度0.7以上
},
});
// 検索実行の例
async function searchExample(query: string) {
const relevantDocs = await retriever.getRelevantDocuments(
query
);
console.log(
`Found ${relevantDocs.length} relevant documents:`
);
relevantDocs.forEach((doc, index) => {
console.log(`\nDocument ${index + 1}:`);
console.log(
`Content: ${doc.pageContent.slice(0, 200)}...`
);
console.log(`Source: ${doc.metadata.source}`);
console.log(`Score: ${doc.metadata.score}`);
});
return relevantDocs;
}
この基本構成により、シンプルで効果的な RAG システムを構築できます。
FAISS を使った高速検索システム
大量文書での高速検索が必要な場合の FAISS 実装例です。
FAISS の初期設定
typescriptimport { FaissStore } from '@langchain/community/vectorstores/faiss';
import { OpenAIEmbeddings } from '@langchain/openai';
高性能設定での FAISS ストア作成:
typescript// 高性能Embedding設定
const embeddings = new OpenAIEmbeddings({
modelName: 'text-embedding-3-large', // 高精度モデル
dimensions: 1536, // 次元数明示
});
// FAISSインデックス作成
async function createFaissIndex(documents: Document[]) {
// バッチ処理での効率的なインデックス作成
const vectorStore = await FaissStore.fromDocuments(
documents,
embeddings
);
// インデックスの永続化
await vectorStore.save('./faiss_index');
console.log('FAISS index saved successfully');
return vectorStore;
}
高速検索のための最適化
typescript// 保存されたインデックスの読み込み
async function loadFaissIndex() {
const vectorStore = await FaissStore.load(
'./faiss_index',
embeddings
);
return vectorStore;
}
// 高速Retrieverの設定
async function createOptimizedRetriever() {
const vectorStore = await loadFaissIndex();
const retriever = vectorStore.asRetriever({
k: 10, // より多くの候補を取得
searchType: 'mmr', // Maximum Marginal Relevance使用
searchKwargs: {
fetchK: 50, // 事前に50件取得
lambda: 0.7, // 多様性重視
},
});
return retriever;
}
バッチ処理での効率的な文書追加
typescript// 大量文書の効率的な処理
async function addDocumentsBatch(
vectorStore: FaissStore,
documents: Document[],
batchSize: number = 100
) {
console.log(
`Processing ${documents.length} documents in batches of ${batchSize}`
);
for (let i = 0; i < documents.length; i += batchSize) {
const batch = documents.slice(i, i + batchSize);
await vectorStore.addDocuments(batch);
console.log(
`Processed batch ${
Math.floor(i / batchSize) + 1
}/${Math.ceil(documents.length / batchSize)}`
);
// メモリ管理のための小休止
if (i % (batchSize * 10) === 0) {
await new Promise((resolve) =>
setTimeout(resolve, 1000)
);
}
}
// 更新されたインデックスを保存
await vectorStore.save('./faiss_index');
}
この実装により、100 万文書規模でも高速な検索が可能になります。
Hybrid Retriever の実装
セマンティック検索とキーワード検索を組み合わせた高度な検索システムです。
Hybrid Retriever のアーキテクチャ
mermaidsequenceDiagram
participant User
participant HybridRetriever
participant DenseRetriever
participant SparseRetriever
participant Ranker
User->>HybridRetriever: クエリ送信
HybridRetriever->>DenseRetriever: セマンティック検索
HybridRetriever->>SparseRetriever: キーワード検索
DenseRetriever-->>HybridRetriever: 検索結果A
SparseRetriever-->>HybridRetriever: 検索結果B
HybridRetriever->>Ranker: 結果の統合・ランキング
Ranker-->>HybridRetriever: 統合結果
HybridRetriever-->>User: 最終結果
Dense Retriever の実装
typescript// セマンティック検索用のRetriever
class DenseRetriever {
private vectorStore: VectorStore;
constructor(vectorStore: VectorStore) {
this.vectorStore = vectorStore;
}
async retrieve(
query: string,
k: number = 10
): Promise<Document[]> {
const retriever = this.vectorStore.asRetriever({
k,
searchType: 'similarity',
});
return await retriever.getRelevantDocuments(query);
}
}
Sparse Retriever の実装
typescriptimport { BM25Retriever } from '@langchain/community/retrievers/bm25';
// キーワード検索用のRetriever
class SparseRetriever {
private bm25Retriever: BM25Retriever;
constructor(documents: Document[]) {
this.bm25Retriever =
BM25Retriever.fromDocuments(documents);
}
async retrieve(
query: string,
k: number = 10
): Promise<Document[]> {
this.bm25Retriever.k = k;
return await this.bm25Retriever.getRelevantDocuments(
query
);
}
}
Hybrid Retriever のメイン実装
typescript// Hybrid Retrieverの統合クラス
class HybridRetriever {
private denseRetriever: DenseRetriever;
private sparseRetriever: SparseRetriever;
private alpha: number; // Dense/Sparse重み調整
constructor(
denseRetriever: DenseRetriever,
sparseRetriever: SparseRetriever,
alpha: number = 0.7 // Denseを重視
) {
this.denseRetriever = denseRetriever;
this.sparseRetriever = sparseRetriever;
this.alpha = alpha;
}
async retrieve(
query: string,
k: number = 5
): Promise<Document[]> {
// 並列で両方の検索を実行
const [denseResults, sparseResults] = await Promise.all(
[
this.denseRetriever.retrieve(query, k * 2),
this.sparseRetriever.retrieve(query, k * 2),
]
);
// スコアの正規化と統合
const combinedResults = this.combineResults(
denseResults,
sparseResults
);
// 上位k件を返却
return combinedResults.slice(0, k);
}
private combineResults(
denseResults: Document[],
sparseResults: Document[]
): Document[] {
const scoreMap = new Map<string, number>();
const docMap = new Map<string, Document>();
// Dense結果のスコア処理
denseResults.forEach((doc, index) => {
const key = this.getDocKey(doc);
const normalizedScore =
(denseResults.length - index) / denseResults.length;
scoreMap.set(key, this.alpha * normalizedScore);
docMap.set(key, doc);
});
// Sparse結果のスコア処理と統合
sparseResults.forEach((doc, index) => {
const key = this.getDocKey(doc);
const normalizedScore =
(sparseResults.length - index) /
sparseResults.length;
const existingScore = scoreMap.get(key) || 0;
scoreMap.set(
key,
existingScore + (1 - this.alpha) * normalizedScore
);
docMap.set(key, doc);
});
// スコア順でソート
const sortedEntries = Array.from(
scoreMap.entries()
).sort(([, scoreA], [, scoreB]) => scoreB - scoreA);
return sortedEntries.map(([key]) => docMap.get(key)!);
}
private getDocKey(doc: Document): string {
// 文書の一意キーを生成(内容のハッシュまたはメタデータのID)
return doc.metadata.id || doc.pageContent.slice(0, 100);
}
}
使用例
typescript// Hybrid Retrieverの使用例
async function setupHybridRAG() {
// VectorStore(Dense用)
const vectorStore = new Chroma(embeddings, {
collectionName: 'hybrid-collection',
});
// 文書データの準備
const documents = await loadDocuments();
await vectorStore.addDocuments(documents);
// Retrieverインスタンス作成
const denseRetriever = new DenseRetriever(vectorStore);
const sparseRetriever = new SparseRetriever(documents);
// Hybrid Retriever作成
const hybridRetriever = new HybridRetriever(
denseRetriever,
sparseRetriever,
0.6 // バランス重視の設定
);
return hybridRetriever;
}
// 実際の検索実行
async function hybridSearch(query: string) {
const retriever = await setupHybridRAG();
const results = await retriever.retrieve(query, 5);
console.log(`Hybrid search results for: "${query}"`);
results.forEach((doc, index) => {
console.log(
`${index + 1}. ${doc.pageContent.slice(0, 150)}...`
);
});
return results;
}
この実装により、キーワード検索の精密性とセマンティック検索の柔軟性を両立した高精度な検索システムを構築できます。
まとめ
設計時の重要ポイント
効果的な RAG システムを構築するための重要な設計指針をまとめます:
1. VectorStore 選択の判断基準
選択要因 | 推奨 VectorStore | 理由 |
---|---|---|
プロトタイプ・小規模 | Chroma | 簡単セットアップ、永続化対応 |
中規模・性能重視 | FAISS | 高速検索、メモリ効率 |
大規模・エンタープライズ | Pinecone/Weaviate | スケーラビリティ、運用性 |
2. チャンクサイズの最適化方針
- 基本設定: 1000 文字、200 文字オーバーラップ
- 技術文書: 1500 文字(コード例含む場合)
- FAQ・短文: 500 文字(簡潔な回答用)
- 学術論文: 2000 文字(詳細な文脈が必要)
3. 検索戦略の選択指針
用途に応じた適切な検索方式の選択:
mermaidflowchart TD
start[検索要件分析] --> question{主な検索対象は?}
question -->|概念・意味| semantic[セマンティック検索]
question -->|キーワード・固有名詞| keyword[キーワード検索]
question -->|両方重要| hybrid[Hybrid検索]
semantic --> dense[Dense Retriever]
keyword --> sparse[Sparse Retriever]
hybrid --> combined[Hybrid Retriever]
dense --> optimize1[Embedding最適化]
sparse --> optimize2[BM25パラメータ調整]
combined --> optimize3[重み配分調整]
4. パフォーマンス最適化のポイント
- インデックス作成: バッチ処理で効率化
- 検索実行: キャッシュ機構の活用
- メモリ管理: 適切なクリーンアップ処理
- 並列処理: 複数 Retriever の同時実行
運用時の注意点
1. データ更新戦略
RAG システムの運用では、継続的なデータ更新が重要です:
- 増分更新: 新規文書のみをインデックスに追加
- 定期的な再構築: インデックス全体の品質維持
- バージョン管理: インデックスのバックアップと復旧体制
2. 検索品質の監視
システムの品質維持のための監視項目:
監視指標 | 目標値 | 改善アクション |
---|---|---|
検索精度 | 80%以上 | チャンクサイズ調整、Retriever 改善 |
レスポンス時間 | 1 秒以内 | インデックス最適化、キャッシュ活用 |
関連度スコア | 0.7 以上 | 閾値調整、フィルタリング改善 |
3. エラーハンドリング
堅牢な運用のための例外処理:
typescript// エラーハンドリングの例
async function safeRetrieve(
query: string,
maxRetries: number = 3
) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const results = await retriever.getRelevantDocuments(
query
);
return results;
} catch (error) {
console.error(`Attempt ${attempt} failed:`, error);
if (attempt === maxRetries) {
// フォールバック処理
return await fallbackSearch(query);
}
// 指数バックオフで再試行
await new Promise((resolve) =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);
}
}
}
4. セキュリティ考慮事項
エンタープライズ環境での重要な配慮:
- アクセス制御: ユーザー権限に基づく文書フィルタリング
- データ暗号化: 保存時・転送時の暗号化
- 監査ログ: 検索履歴と結果の記録
- PII 保護: 個人情報の適切な取り扱い
これらのポイントを押さえることで、実用的で安定した RAG システムを構築・運用できます。適切な設計と継続的な改善により、ユーザーにとって価値のある AI アプリケーションを提供していきましょう。
関連リンク
- article
LangChain で RAG 構築:Retriever・VectorStore の設計ベストプラクティス
- article
LangChain の PromptTemplate 最適化:再利用・バージョニング・評価手法
- article
LangChain × LCEL 徹底解説:Runnable で組む宣言的パイプライン
- article
LangChain の基本設計:Chains・Tools・Agents を図解で理解する
- article
LangChain 入門:ゼロから始める最新 LLM アプリ開発ガイド
- article
Svelte と GraphQL:最速データ連携のススメ
- article
Lodash の throttle・debounce でパフォーマンス最適化
- article
LangChain で RAG 構築:Retriever・VectorStore の設計ベストプラクティス
- article
Storybook で学ぶコンポーネントテスト戦略
- article
状態遷移を明文化する:XState × Jotai の堅牢な非同期フロー設計
- article
Jest で DOM 操作をテストする方法:document・window の扱い方まとめ
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来