AI技術一覧に戻る

検索拡張生成(RAG)

RAGとは

検索拡張生成(Retrieval-Augmented Generation、RAG)は、大規模言語モデル(LLM)の生成能力を外部知識源からの情報検索と組み合わせるAIフレームワークです。RAGは、モデルが応答を生成する前に関連情報を検索して取り込むことで、より正確で最新の情報に基づいた回答を提供します。

RAGの目的

RAGの主な目的は以下の通りです:

RAGのアーキテクチャ

RAGは主に以下の2つの主要コンポーネントで構成されています:

コンポーネント 説明
検索器(Retriever) ユーザークエリに関連する情報を外部知識ベースから検索するコンポーネント。ベクトル検索、キーワード検索、ハイブリッド検索などの手法を使用
生成器(Generator) 検索された情報とユーザークエリを組み合わせて、最終的な応答を生成するLLM

RAGの基本的なワークフロー

  1. ユーザーがクエリを入力
  2. 検索器がクエリに関連する情報を知識ベースから検索
  3. 検索結果がLLMのプロンプトに組み込まれる
  4. LLMが検索結果とクエリを考慮して応答を生成
  5. 生成された応答がユーザーに返される

RAGの主要コンポーネント詳細

1. 知識ベース(Knowledge Base)

RAGシステムの情報源となるデータリポジトリです。以下のような形態があります:

2. インデクシングパイプライン

知識ベースを検索可能な形式に変換するプロセスです:

3. 検索コンポーネント

ユーザークエリに関連する情報を効率的に取得するシステムです:

4. プロンプトエンジニアリング

検索結果をLLMのプロンプトに効果的に組み込む技術です:

5. 応答生成と後処理

最終的な出力を生成し、品質を向上させるプロセスです:

RAGの実装パターン

RAGを実装する際の一般的なパターンには以下のようなものがあります:

1. 基本的なRAG

最もシンプルなRAG実装で、クエリに基づいて関連ドキュメントを検索し、それをLLMのプロンプトに追加します。

async function basicRAG(query) {
    // 1. クエリをベクトルに変換
    const queryEmbedding = await embedder.embed(query);
    
    // 2. ベクトルデータベースから関連ドキュメントを検索
    const relevantDocs = await vectorDB.similaritySearch(queryEmbedding, 3);
    
    // 3. 検索結果をプロンプトに組み込む
    const prompt = `
以下の情報を参考にして、質問に答えてください。
情報源:
${relevantDocs.map(doc => doc.content).join('\n\n')}

質問: ${query}
回答:`;
    
    // 4. LLMで応答を生成
    const response = await llm.generate(prompt);
    
    return response;
}
    

2. マルチステップRAG

複数のステップで検索と生成を行い、より複雑なクエリに対応します。

async function multiStepRAG(query) {
    // 1. クエリを分解して検索サブクエリを生成
    const prompt = `
以下の質問を検索するための具体的なサブクエリに分解してください。
各サブクエリは異なる側面に焦点を当てるべきです。
3つのサブクエリをJSON形式で返してください。

質問: ${query}`;
    
    const subQueriesResponse = await llm.generate(prompt);
    const subQueries = JSON.parse(subQueriesResponse).subQueries;
    
    // 2. 各サブクエリで検索を実行
    let allRelevantDocs = [];
    for (const subQuery of subQueries) {
        const queryEmbedding = await embedder.embed(subQuery);
        const docs = await vectorDB.similaritySearch(queryEmbedding, 2);
        allRelevantDocs = [...allRelevantDocs, ...docs];
    }
    
    // 3. 重複を除去し、関連性でランク付け
    const uniqueDocs = removeDuplicates(allRelevantDocs);
    const rankedDocs = rankByRelevance(uniqueDocs, query);
    
    // 4. 最終的な応答を生成
    const finalPrompt = `
以下の情報を参考にして、質問に答えてください。
情報源:
${rankedDocs.map(doc => doc.content).join('\n\n')}

質問: ${query}
回答:`;
    
    const response = await llm.generate(finalPrompt);
    
    return response;
}
    

3. 反復的RAG

初期の検索結果に基づいて追加の検索を行い、情報を段階的に収集します。

async function iterativeRAG(query) {
    // 1. 初期検索
    const queryEmbedding = await embedder.embed(query);
    const initialDocs = await vectorDB.similaritySearch(queryEmbedding, 3);
    
    // 2. 初期情報を分析して追加クエリを生成
    const analysisPrompt = `
以下の情報を分析し、質問に完全に答えるために必要な追加情報を特定してください。
追加で検索すべき3つのクエリをJSON形式で返してください。

既存の情報:
${initialDocs.map(doc => doc.content).join('\n\n')}

質問: ${query}`;
    
    const analysisResponse = await llm.generate(analysisPrompt);
    const additionalQueries = JSON.parse(analysisResponse).queries;
    
    // 3. 追加検索を実行
    let additionalDocs = [];
    for (const additionalQuery of additionalQueries) {
        const additionalEmbedding = await embedder.embed(additionalQuery);
        const docs = await vectorDB.similaritySearch(additionalEmbedding, 2);
        additionalDocs = [...additionalDocs, ...docs];
    }
    
    // 4. すべての情報を統合して最終応答を生成
    const allDocs = [...initialDocs, ...additionalDocs];
    const uniqueDocs = removeDuplicates(allDocs);
    
    const finalPrompt = `
以下の情報を参考にして、質問に包括的に答えてください。
情報源:
${uniqueDocs.map(doc => doc.content).join('\n\n')}

質問: ${query}
回答:`;
    
    const response = await llm.generate(finalPrompt);
    
    return response;
}
    

RAGの実装例

社内ドキュメント検索システム

インデクシングパイプラインの実装

class DocumentIndexer {
    constructor(embeddingModel, vectorDB) {
        this.embeddingModel = embeddingModel;
        this.vectorDB = vectorDB;
        this.chunkSize = 500;
        this.chunkOverlap = 50;
    }
    
    async indexDocument(document, metadata) {
        // ドキュメントからテキストを抽出
        const text = await this.extractText(document);
        
        // テキストをチャンクに分割
        const chunks = this.splitIntoChunks(text);
        
        // 各チャンクを処理
        for (let i = 0; i < chunks.length; i++) {
            const chunk = chunks[i];
            
            // チャンク固有のメタデータを作成
            const chunkMetadata = {
                ...metadata,
                chunkIndex: i,
                totalChunks: chunks.length,
                documentId: metadata.id,
                chunkText: chunk.substring(0, 100) + "..."
            };
            
            // チャンクの埋め込みベクトルを生成
            const embedding = await this.embeddingModel.embed(chunk);
            
            // ベクトルデータベースに保存
            await this.vectorDB.addItem({
                id: `${metadata.id}-chunk-${i}`,
                vector: embedding,
                metadata: chunkMetadata,
                content: chunk
            });
        }
        
        console.log(`Indexed document ${metadata.id} with ${chunks.length} chunks`);
    }
    
    splitIntoChunks(text) {
        // テキストを適切なサイズのチャンクに分割
        const chunks = [];
        let startIndex = 0;
        
        while (startIndex < text.length) {
            // チャンクの終了位置を計算
            let endIndex = Math.min(startIndex + this.chunkSize, text.length);
            
            // 文の途中で切らないように調整
            if (endIndex < text.length) {
                // 次のピリオド、疑問符、感嘆符を探す
                const nextSentenceEnd = text.indexOf('.', endIndex);
                const nextQuestionEnd = text.indexOf('?', endIndex);
                const nextExclamationEnd = text.indexOf('!', endIndex);
                
                // 最も近い文末を見つける
                const sentenceEnds = [nextSentenceEnd, nextQuestionEnd, nextExclamationEnd]
                    .filter(pos => pos !== -1)
                    .map(pos => pos + 1);
                
                if (sentenceEnds.length > 0) {
                    endIndex = Math.min(...sentenceEnds);
                }
            }
            
            // チャンクを追加
            chunks.push(text.substring(startIndex, endIndex));
            
            // 次のチャンクの開始位置を計算(オーバーラップを考慮)
            startIndex = endIndex - this.chunkOverlap;
        }
        
        return chunks;
    }
    
    async extractText(document) {
        // ドキュメントの種類に応じてテキスト抽出ロジックを実装
        // PDF、Word、HTML等の処理
        // ...
        
        return extractedText;
    }
}
        

RAG検索と応答生成の実装

class DocumentRAG {
    constructor(embeddingModel, vectorDB, llm) {
        this.embeddingModel = embeddingModel;
        this.vectorDB = vectorDB;
        this.llm = llm;
        this.topK = 5;
    }
    
    async query(userQuery) {
        try {
            // クエリの埋め込みを生成
            const queryEmbedding = await this.embeddingModel.embed(userQuery);
            
            // 関連ドキュメントを検索
            const searchResults = await this.vectorDB.search(
                queryEmbedding,
                this.topK,
                { includeMetadata: true }
            );
            
            // 検索結果がない場合
            if (searchResults.length === 0) {
                return {
                    answer: "申し訳ありませんが、この質問に関連する情報が見つかりませんでした。",
                    sources: []
                };
            }
            
            // 検索結果をプロンプトに組み込む
            const contextText = searchResults
                .map(result => `[ドキュメント: ${result.metadata.title}]\n${result.content}`)
                .join('\n\n');
            
            const prompt = `
あなたは社内ドキュメント検索アシスタントです。
以下の社内ドキュメントから抽出された情報を使用して、ユーザーの質問に答えてください。
情報が不足している場合は、正直に「この情報からは回答できません」と伝えてください。
回答には、使用した情報源のドキュメントタイトルを引用してください。

社内ドキュメント情報:
${contextText}

ユーザーの質問: ${userQuery}

回答:`;
            
            // LLMで応答を生成
            const response = await this.llm.generate(prompt);
            
            // 使用したソース情報を収集
            const sources = searchResults.map(result => ({
                title: result.metadata.title,
                url: result.metadata.url,
                lastUpdated: result.metadata.lastUpdated
            }));
            
            return {
                answer: response,
                sources: sources
            };
        } catch (error) {
            console.error("RAG query error:", error);
            return {
                answer: "検索処理中にエラーが発生しました。後でもう一度お試しください。",
                sources: []
            };
        }
    }
}
        

RAGの利点

RAGを導入することで得られる主な利点は以下の通りです:

RAGの課題と考慮事項

RAGを実装する際に考慮すべき課題には以下のようなものがあります:

実装のベストプラクティス

RAGの応用例

RAGは以下のようなさまざまな応用例があります:

RAGの発展と将来動向

RAG技術は急速に発展しており、以下のような方向性が見られます:

まとめ

検索拡張生成(RAG)は、大規模言語モデルの生成能力と外部知識源からの情報検索を組み合わせることで、より正確で最新の情報に基づいた応答を提供するAIフレームワークです。RAGは、モデルの幻覚を減少させ、特定ドメインの専門知識を統合し、透明性と検証可能性を向上させるなど、多くの利点を提供します。適切に実装されたRAGシステムは、企業内ナレッジベース、カスタマーサポート、専門分野のアシスタントなど、様々な応用分野で価値を発揮します。技術の発展に伴い、マルチモーダル対応や自己反省機能など、より高度なRAGシステムが今後も登場していくでしょう。