1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RAG完全ガイド2026 ─ 検索拡張生成の仕組みから企業実装まで

1
Last updated at Posted at 2026-02-13

2026年のRAGが解決する問題

ChatGPT が「自分は2021年までの情報しか知らない」と答えるように、LLM(大規模言語モデル)には根本的な制限があります。それが「ハルシネーション」──自信を持って嘘をつく現象です。

2026年時点で、RAG(Retrieval Augmented Generation)は単なる研究概念ではなく、企業のエンタープライズAI導入における必須技術に進化しました。このガイドは、基礎から実装パターンまで、RAGについて幅広く解説します。

RAGの根本的な仕組み

従来のLLMとRAGの違い

┌─────────────────────────────────┐
│ 従来のLLM(例:ChatGPT 3.5)    │
├─────────────────────────────────┤
│                                 │
│ ユーザー質問                      │
│    ↓                            │
│ LLMの事前学習知識                │
│ (学習済み重みから直接生成)     │
│    ↓                            │
│ 回答生成                          │
│ (時に幻想的で古い情報も含む)   │
│                                 │
│ 問題:                           │
│ × 知識の日付が固定              │
│ × ハルシネーション問題           │
│ × 独自データへのアクセス不可     │
│                                 │
└─────────────────────────────────┘

┌──────────────────────────────────┐
│ RAG強化型LLM(推奨構成)          │
├──────────────────────────────────┤
│                                  │
│ ユーザー質問                       │
│    ↓                             │
│ ┌────────────────────────────┐  │
│ │ ステップ1:関連情報の検索   │  │
│ │  ├─ ベクトル化              │  │
│ │  ├─ ベクトルDB照合          │  │
│ │  └─ 最も関連度高い文書 取得  │  │
│ └────────────────────────────┘  │
│    ↓                             │
│ ┌────────────────────────────┐  │
│ │ ステップ2:検索結果を含む  │  │
│ │ プロンプト構築              │  │
│ └────────────────────────────┘  │
│    ↓                             │
│ LLM推論(ファクト付き)           │
│    ↓                             │
│ 正確で根拠のある回答             │
│                                  │
│ メリット:                        │
│ ○ リアルタイム情報対応           │
│ ○ 企業固有データ活用             │
│ ○ ハルシネーション低減           │
│ ○ 出典を明記可能               │
│                                  │
└──────────────────────────────────┘

RAGの核心:3つのステップ

[1] Retrieval(検索)
    ├─ ユーザー質問をベクトル化
    ├─ ベクトルDB内から類似度検索
    └─ 関連文書スニペットを抽出

    [2] Augmentation(拡張)
    ├─ 検索結果をLLMプロンプトに埋め込む
    ├─ 「以下の情報を参考に答えてください」
    └─ テンプレート化されたプロンプト構築

    [3] Generation(生成)
    ├─ LLMが検索結果をベースに回答生成
    ├─ 出典の明記が自動的に可能
    └─ ハルシネーション大幅削減

ベクトルデータベースの選定ガイド

2026年の主要ベクトルDB比較表

DB名 スケーラビリティ 検索速度 メモリ効率 セットアップ難度 企業対応 推奨用途
FAISS 極速 不向 ローカル開発、デモ
Pinecone 極高 SaaS選好企業、フルマネージド求める
Chroma 極低 迅速プロトタイピング、学習用
Qdrant オンプレミス重視、セルフホスト
Milvus 極高 中高 大規模データセット、複雑クエリ
Weaviate グラフDB的利用、複合検索

実装パターン別の推奨

スタートアップ / プロトタイプ段階

  • Chroma → 極めてシンプル、5分で起動

中堅企業 / スケーラビリティ必要

  • Pinecone (SaaS)または Qdrant (セルフホスト)

大規模エンタープライズ

  • Milvus (自社インフラ完全コントロール)

完全なRAG実装パターン

Part A: LangChain を用いた実装(推奨)

# -*- coding: utf-8 -*-
"""
LangChain を使用したRAG実装
2026年版

構成:
- ドキュメント読み込み(PDF, テキスト等)
- チャンク分割(適切な粒度)
- ベクトル化とDB保存
- 検索・生成パイプライン
"""

from langchain.document_loaders import (
    PyPDFLoader,
    DirectoryLoader,
    TextLoader
)
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from typing import List, Tuple
import os

class RAGPipeline:
    """企業向けRAGパイプライン"""

    def __init__(self, api_key: str = None, persist_dir: str = "./chroma_db"):
        """
        Args:
            api_key: OpenAI APIキー
            persist_dir: ベクトルDB永続化ディレクトリ
        """
        if api_key:
            os.environ["OPENAI_API_KEY"] = api_key

        self.persist_dir = persist_dir
        self.embeddings = OpenAIEmbeddings(
            model="text-embedding-3-small",  # 2026年版で軽量化
            dimensions=512  # 元の1536→512で30%削減
        )
        self.llm = ChatOpenAI(
            model_name="gpt-4-turbo",
            temperature=0.1  # 事実ベースの回答に特化
        )
        self.vector_store = None
        self.retriever = None

    def ingest_documents(self, document_paths: List[str]) -> int:
        """
        複数の文書フォーマットを読み込み

        Args:
            document_paths: PDFやテキストファイルのパスリスト

        Returns:
            読み込まれたチャンク数
        """
        documents = []

        for path in document_paths:
            if path.endswith('.pdf'):
                loader = PyPDFLoader(path)
                docs = loader.load()
                documents.extend(docs)
                print(f"○ PDFロード完了: {path} ({len(docs)}ページ)")

            elif path.endswith('.txt'):
                loader = TextLoader(path, encoding='utf-8')
                docs = loader.load()
                documents.extend(docs)
                print(f"○ テキストロード完了: {path}")

            elif os.path.isdir(path):
                loader = DirectoryLoader(
                    path,
                    glob="**/*.pdf",
                    loader_cls=PyPDFLoader
                )
                docs = loader.load()
                documents.extend(docs)
                print(f"○ ディレクトリロード完了: {path} ({len(docs)}ファイル)")

        print(f"\n📄 総文書数: {len(documents)}")

        # チャンク分割(極めて重要なステップ)
        chunks = self._chunk_documents(documents)
        print(f"📦 チャンク数: {len(chunks)}")

        # ベクトルDB構築
        self.vector_store = Chroma.from_documents(
            documents=chunks,
            embedding=self.embeddings,
            persist_directory=self.persist_dir
        )

        self.vector_store.persist()
        self.retriever = self.vector_store.as_retriever(
            search_kwargs={"k": 5}  # Top-5の関連文書を取得
        )

        return len(chunks)

    def _chunk_documents(self, documents: List) -> List:
        """
        ドキュメントをインテリジェントに分割

        チャンク戦略の重要性:
        - 小さすぎる → コンテキスト欠落
        - 大きすぎる → 冗長性、ノイズ増加
        """
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=1000,          # 1000文字(日本語で約300-500単語)
            chunk_overlap=200,         # 文脈の連続性を保証
            separators=["\n\n", "\n", "", "", " ", ""]
        )

        chunks = text_splitter.split_documents(documents)
        return chunks

    def create_rag_qa_chain(self) -> RetrievalQA:
        """
        RAG用QA チェーンを構築
        """
        # カスタムプロンプトテンプレート
        prompt_template = """あなたは企業のナレッジワーカーです。
以下の文脈に基づいて、正確かつ簡潔に質問に答えてください。

文脈:
{context}

質問: {question}

回答:
1. まず、文脈から得られた事実ベースの情報を述べてください
2. 必要に応じて、その情報の出典を明記してください
3. 文脈に無い情報は「文脈に記載されていません」と明確に述べてください

回答:"""

        prompt = PromptTemplate(
            template=prompt_template,
            input_variables=["context", "question"]
        )

        qa_chain = RetrievalQA.from_chain_type(
            llm=self.llm,
            chain_type="stuff",  # 「stuff」型:全関連文書をプロンプトに詰め込む
            retriever=self.retriever,
            return_source_documents=True,
            chain_type_kwargs={"prompt": prompt}
        )

        return qa_chain

    def query(self, question: str, verbose: bool = True) -> dict:
        """
        質問を処理し、回答と根拠を返す
        """
        qa_chain = self.create_rag_qa_chain()

        response = qa_chain({"query": question})

        result = {
            "question": question,
            "answer": response["result"],
            "source_documents": response["source_documents"],
            "source_summary": self._summarize_sources(response["source_documents"])
        }

        if verbose:
            print(f"\n🔍 質問: {question}")
            print(f"\n💬 回答:\n{response['result']}")
            print(f"\n📚 出典文書:")
            for i, doc in enumerate(response["source_documents"], 1):
                print(f"  {i}. {doc.metadata.get('source', 'Unknown')}")

        return result

    def _summarize_sources(self, documents: List) -> str:
        """出典の簡潔な要約"""
        sources = set()
        for doc in documents:
            source = doc.metadata.get('source', 'Unknown')
            sources.add(source)
        return ", ".join(sources)

    def load_persisted_index(self):
        """
        前回保存したベクトルDB を読み込む
        (ドキュメント再処理不要)
        """
        self.vector_store = Chroma(
            persist_directory=self.persist_dir,
            embedding_function=self.embeddings
        )
        self.retriever = self.vector_store.as_retriever(
            search_kwargs={"k": 5}
        )
        print("○ ベクトルDB読み込み完了")


# 実装例
if __name__ == "__main__":
    # ステップ1: RAGパイプラインを初期化
    rag = RAGPipeline(
        api_key="sk-xxx",  # 本番環境では環境変数から取得
        persist_dir="./company_knowledge_db"
    )

    # ステップ2: 企業ドキュメントを読み込み
    documents = [
        "./docs/company_handbook.pdf",
        "./docs/product_specifications.txt",
        "./docs/technical_guidelines/"
    ]

    chunk_count = rag.ingest_documents(documents)
    print(f"○ インジェスト完了: {chunk_count}チャンク")

    # ステップ3: 質問を処理
    questions = [
        "当社の退職金制度について教えてください",
        "Product X の仕様で、セキュリティ要件は?",
        "新入社員研修の期間は?"
    ]

    for q in questions:
        result = rag.query(q, verbose=True)
        print("\n" + "="*60 + "\n")

Part B: ハイブリッド検索(密度検索 + 疎検索)

# ハイブリッド検索の実装
# 意味的に似た文書も、キーワード的に完全一致する文書も両方取得

from langchain.retrievers import BM25Retriever
from langchain.retrievers import EnsembleRetriever

class HybridRAGPipeline(RAGPipeline):
    """疎検索+密度検索の融合RAG"""

    def create_hybrid_retriever(self):
        """
        BM25(疎検索)+ ベクトル検索(密検索)の融合
        """
        # BM25リトライバー(従来的なキーワード検索)
        bm25_retriever = BM25Retriever.from_documents(
            self.documents,
            k=5
        )

        # ベクトルリトライバー(意味ベース)
        vector_retriever = self.vector_store.as_retriever(
            search_kwargs={"k": 5}
        )

        # アンサンブル統合
        ensemble_retriever = EnsembleRetriever(
            retrievers=[bm25_retriever, vector_retriever],
            weights=[0.4, 0.6]  # BM25: 40%, ベクトル: 60%
        )

        return ensemble_retriever

高度なRAGパターン

パターン1:クエリ変換(Query Transformation)

from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate

class QueryTransformationRAG:
    """
    ユーザー質問を最適な検索クエリに自動変換
    例:「昨年の売上は?」→ 検索キーワード最適化
    """

    def __init__(self, llm, retriever):
        self.llm = llm
        self.retriever = retriever

    def transform_query(self, user_question: str) -> str:
        """
        LLMが質問を最適な検索クエリに変換
        """
        transform_prompt = PromptTemplate(
            input_variables=["question"],
            template="""以下のユーザー質問から、最適な検索キーワードを生成してください。
複数形式の回答に対応できるように、複数の検索キーワードを生成してください。

ユーザー質問: {question}

最適な検索キーワード(カンマ区切り):"""
        )

        chain = LLMChain(llm=self.llm, prompt=transform_prompt)
        transformed = chain.run(question=user_question)

        return transformed.strip()

パターン2:マルチホップ検索(Multi-Hop Retrieval)

class MultiHopRAG:
    """
    複雑な質問を複数のステップに分割して検索
    例:「A社がなぜB業界に参入したか」
    → ステップ1: A社の戦略方針取得
    → ステップ2: B業界の市場情報取得
    → ステップ3: 両者を統合して回答生成
    """

    def multi_hop_query(self, complex_question: str) -> dict:
        """マルチホップ検索の実装"""
        # ステップ1: 質問を分解
        sub_questions = self._decompose_question(complex_question)

        # ステップ2: 各サブ質問を検索
        contexts = []
        for sub_q in sub_questions:
            context = self.retriever.get_relevant_documents(sub_q)
            contexts.extend(context)

        # ステップ3: 統合生成
        final_answer = self.llm.generate(
            prompt=f"以下の複数の情報を統合して答えてください: {complex_question}\n\n情報: {contexts}"
        )

        return final_answer

パターン3:リランキング(Re-ranking)

from sentence_transformers import CrossEncoder

class RerankingRAG:
    """
    検索結果をLLMの観点で再ランク付け
    → より適切な文書を上位に
    """

    def __init__(self, model_name: str = "cross-encoder/ms-marco-MiniLM-L-12-v2"):
        self.reranker = CrossEncoder(model_name)

    def rerank_results(self, query: str, documents: List) -> List:
        """
        検索結果を再ランク付け
        """
        # 各文書と質問のマッチスコアを計算
        pairs = [(query, doc.page_content) for doc in documents]
        scores = self.reranker.predict(pairs)

        # スコアでソート
        ranked = sorted(
            zip(documents, scores),
            key=lambda x: x[1],
            reverse=True
        )

        return [doc for doc, score in ranked]

企業へのRAG導入パターン

デプロイメント構成(推奨)

┌────────────────────────────────────────┐
│     企業ユーザー(社員)                 │
│     Slack / Teams Bot / Web UI          │
└──────────────┬─────────────────────────┘
               │
        ┌──────▼──────────┐
        │  API Gateway    │
        │  認証・ロギング  │
        └──────┬──────────┘
               │
        ┌──────▼──────────────────┐
        │ RAGオーケストレーション  │
        │ (LangChain/LlamaIndex) │
        └──────┬───────────────┬──┘
               │               │
        ┌──────▼──┐      ┌─────▼──────┐
        │ LLM     │      │ ベクトルDB  │
        │ (GPT-4) │      │ (Pinecone) │
        └─────────┘      └─────────────┘
               │               │
        ┌──────▼───────────────▼─────┐
        │    社内ドキュメント DB       │
        │  - 就業規則                │
        │  - 製品仕様                │
        │  - 技術ガイドライン        │
        │  - Q&A集                   │
        └────────────────────────────┘

エンタープライズ対応チェックリスト

  • ☑️ アクセス制御 :部署ごとの検索結果フィルタリング
  • ☑️ 監査ログ :全検索クエリと回答を記録
  • ☑️ キャッシング :頻出質問への高速対応
  • ☑️ フィードバック :回答の正確性を継続的に評価
  • ☑️ 定期更新 :ドキュメントの増分インデックス化

評価指標とファインチューニング

RAGシステムの評価指標

from typing import List, Tuple

class RAGEvaluator:
    """RAGシステムの品質評価"""

    @staticmethod
    def faithfulness(answer: str, context: str) -> float:
        """
        忠実性:回答が検索文脈に基づいているか
        スコア: 0.0-1.0
        """
        # 実装例(簡易版)
        # 本番環境ではファインチューンされたモデルを使用
        overlap = len(set(answer.split()) & set(context.split()))
        return min(overlap / len(answer.split()), 1.0)

    @staticmethod
    def relevance(retrieved_doc: str, question: str) -> float:
        """
        関連性:検索文書が質問に対して適切か
        """
        # ベクトルの類似度を計算
        pass

    @staticmethod
    def answer_relevance(answer: str, question: str) -> float:
        """
        回答関連性:回答が質問に正面から答えているか
        """
        pass

# ベンチマーク評価例
evaluation_results = {
    "faithfulness": 0.92,      # 92%の回答が文脈に忠実
    "relevance": 0.88,         # 検索結果の関連性88%
    "answer_relevance": 0.95,  # 回答が質問に適切に応答
    "latency_ms": 520,         # 平均遅延 520ms
}

プライバシーと規制対応

オンプレミスRAG(機密情報向け)

class PrivacyPreservingRAG:
    """
    機密情報を外部に送信しないローカルRAG構成
    """

    def __init__(self):
        # オンプレミスモデル
        from llama_cpp import Llama

        # 例: Mistral 7B(メモリ効率的)
        self.local_llm = Llama(
            model_path="./models/mistral-7b.gguf",
            n_gpu_layers=-1  # GPU加速
        )

        # ローカルエンベッディング
        from sentence_transformers import SentenceTransformer
        self.embeddings = SentenceTransformer(
            "sentence-transformers/paraphrase-mpnet-base-v2"
        )

        # ローカルベクトルDB(FAISS)
        import faiss
        self.vector_db = faiss.IndexFlatL2(768)

    def process_confidential_query(self, query: str) -> str:
        """
        全て社内で処理 → 情報漏洩ゼロ
        """
        # クラウドに一切送信しない
        embedding = self.embeddings.encode(query)
        results = self.vector_db.search(embedding, k=5)
        answer = self.local_llm(results)
        return answer

2026年の新展開

アジェンティックRAG(Agentic RAG)

from langchain.agents import AgentExecutor, Tool
from langchain.tools import tool

class AgenticRAG:
    """
    複数の外部ツールと連携する自律的RAG
    """

    @tool
    def search_documents(query: str) -> str:
        """社内ドキュメント検索"""
        pass

    @tool
    def search_web(query: str) -> str:
        """Web検索(リアルタイム情報)"""
        pass

    @tool
    def calculate(expression: str) -> float:
        """計算機"""
        pass

    @tool
    def query_database(sql: str) -> str:
        """データベースクエリ"""
        pass

    # これらのツールをLLMに装備
    # → LLMが必要に応じてツールを選択・実行

一般的な落とし穴と対策

落とし穴1:「チャンク境界での情報断絶」

# × 悪い例:固定長チャンク
chunk_size = 500  # 文字で機械的に分割
# → 文の途中で切られることがある

# ○ 良い例:セマンティック分割
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
    separators=["\n\n", "", ""],  # 意味的な区切り目で分割
    chunk_size=1000
)

落とし穴2:「低品質なエンベッディングモデル」

2026年では、エンベッディングモデルの選定が極めて重要:

モデル 次元数 速度 精度 推奨用途
text-embedding-3-small 512 最速 本番推奨
text-embedding-3-large 3072 精度重視
multilingual-e5-large 1024 多言語対応

落とし穴3:「ハルシネーション抑制不足」

# 予防策

# 1) 厳格なプロンプト制約
STRICT_PROMPT = """
以下のドキュメントのみを参考に答えてください。
ドキュメントに無い情報は「記載されていません」と回答してください。
推測や補完は厳禁です。
"""

# 2) 回答の自信度スコア
confidence_score = self._estimate_confidence(
    answer=answer,
    sources=source_docs
)
if confidence_score < 0.7:
    answer = "申し訳ございません。信頼度が低いため正確な回答ができません。"

# 3) 事後検証
def verify_answer(answer: str, sources: List) -> bool:
    """回答が出典に含まれているか確認"""
    # 実装: 各出典との適合度をスコア化
    pass

実装ロードマップ

3ヶ月実装計画

【第1ヶ月】
├─ Week 1-2: PoC環境構築(Chroma + OpenAI)
├─ Week 2-3: パイロット用ドキュメント準備(50-100個)
└─ Week 4: 初期評価・フィードバック

【第2ヶ月】
├─ ベクトルDBをプロダクション環境へ(Pinecone or Qdrant)
├─ セキュリティ・アクセス制御の実装
├─ 監査ログシステム構築
└─ ユーザー受入テスト

【第3ヶ月】
├─ ドキュメント増分インジェスト(完全データセット)
├─ パフォーマンスチューニング
├─ チーム向け教育・トレーニング
└─ 本番リリース

まとめ

2026年時点で、RAGは実験段階から実装必須技術へ進化しました。単なるLLMではなく、企業の知識資産(ドキュメント、データベース、ナレッジベース)とLLMを融合させることで、信頼性の高いAIアシスタント、コンプライアンス対応(監査ログ、出典明記)、リアルタイム情報への対応、ハルシネーション削減を実現できます。

参考資料

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?