RAGって何? AIに「自社の知識」を教える方法

ミドリ: タツヤさん、社内のマニュアルや規程に基づいて正確に答えてくれるAIを作りたいんですけど、ChatGPTにそのまま聞いても的外れな回答が返ってくるんですよね……。

タツヤ: あるあるだね。しかもAIは自信満々に間違えるから厄介なんだよ。いわゆる「ハルシネーション」ってやつ。

ミドリ: そうなんです! それをどうにかしたくて。

タツヤ: そこで登場するのがRAG(Retrieval-Augmented Generation:検索拡張生成)。LLMが回答を生成するときに、外部のデータソースから関連情報を検索して、その情報をコンテキストとして渡す仕組みだよ。Gartner社の調査によれば、LLMを業務活用している企業の約60%がRAGを採用または導入を計画してる。

ミドリ: 6割も! かなり主流なんですね。

RAGの仕組み:3つのフェーズ

ミドリ(thinking): 具体的にはどういう仕組みなんですか?

タツヤ: 大きく3つのフェーズに分かれる。

フェーズ1:インデックス作成(事前準備)

タツヤ(nodding): まず社内文書やFAQ、マニュアルをベクトル化(Embedding)して、ベクトルデータベースに格納する。これは事前に一度やっておく処理で、新しい文書が追加されたら更新する。

フェーズ2:検索(Retrieval)

タツヤ: ユーザーの質問もベクトル化して、ベクトルDBから意味的に類似した文書を検索・取得する。

ミドリ: キーワードの完全一致じゃなくて、「意味の近さ」で検索するんですね。

タツヤ: そう。だから「有給の取り方」って聞いても「年次有給休暇の申請手続き」っていう文書がちゃんとヒットする。

フェーズ3:生成(Generation)

タツヤ: 検索で取得した文書をコンテキストとしてLLMに渡して、それに基づいた回答を生成する。LLMは自分の学習データじゃなくて、提供されたコンテキスト内の情報に基づいて回答するから、正確性が大幅に上がる。

RAGとFine-tuningの違い

ミドリ: Fine-tuning(ファインチューニング)との違いは何ですか? どっちを選べばいいんでしょう。

タツヤ(nodding): いい質問。比較表で見るとわかりやすいよ。

比較項目RAGFine-tuning
知識の更新リアルタイムに可能再学習が必要
初期コスト低〜中中〜高
運用コスト検索インフラの維持定期的な再学習
適したユースケース社内ナレッジ検索、FAQ特定のタスクやトーンの最適化
ハルシネーション大幅に軽減一定の改善
回答の根拠出典を提示可能提示困難

ミドリ(thinking): RAGは「出典を提示できる」のが強いですね。ビジネスだと「なぜその回答なのか」が大事ですもんね。

タツヤ: まさにそこ。多くのケースでは、まずRAGを導入して、必要に応じてFine-tuningを組み合わせるのが効果的だよ。

実装してみよう

ミドリ(thinking): 実際にどうやって作るんですか?

タツヤ: 順番にやっていこう。

ステップ1:環境準備

pip install langchain langchain-openai langchain-community
pip install chromadb faiss-cpu
pip install unstructured pypdf tiktoken

ステップ2:ドキュメントの読み込みとチャンク分割

タツヤ: RAGの精度を左右する最重要ステップがこのチャンキング。文書をどう分割するかで検索精度が決まる。

from langchain_community.document_loaders import PyPDFLoader, DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# PDFドキュメントの読み込み
loader = DirectoryLoader(
    "./documents/",
    glob="**/*.pdf",
    loader_cls=PyPDFLoader
)
documents = loader.load()

# チャンク分割
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,       # 1チャンクの最大文字数
    chunk_overlap=100,    # チャンク間のオーバーラップ
    separators=["\n\n", "\n", "。", "、", " "],  # 日本語向けの区切り文字
    length_function=len
)
chunks = text_splitter.split_documents(documents)
print(f"生成されたチャンク数: {len(chunks)}")

ミドリ: チャンクサイズってどのくらいがいいんですか?

タツヤ: ここが悩みどころなんだよね。

オーバーラップはチャンクサイズの15〜20%に設定するのが一般的。文の途中で切れちゃう問題を防げる。

ステップ3:ベクトルデータベースの構築

from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

# Embeddingモデルの初期化
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# ベクトルデータベースの作成
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db",
    collection_name="company_docs"
)

print(f"インデックスに格納したチャンク数: {vectorstore._collection.count()}")

ステップ4:検索と回答生成

タツヤ: ここでRAGチェーンを組み立てる。プロンプトが重要で、「コンテキストに含まれない情報は答えるな」って明示するのがポイント。

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

# Retriever(検索器)の設定
retriever = vectorstore.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 4}  # 上位4件を取得
)

# プロンプトテンプレート
template = """以下のコンテキスト情報のみに基づいて質問に回答してください。
コンテキストに含まれない情報については「その情報は見つかりませんでした」と回答してください。

コンテキスト:
{context}

質問: {question}

回答:"""

prompt = ChatPromptTemplate.from_template(template)
llm = ChatOpenAI(model="gpt-4o", temperature=0.1)

# RAGチェーンの構築
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()
)

# 実行
answer = rag_chain.invoke("有給休暇の申請手順を教えてください")
print(answer)

ミドリ(nodding): なるほど、検索結果をプロンプトに埋め込んでLLMに渡すんですね。

ステップ5:出典情報の付与

タツヤ(smiling): ビジネス利用では「どの文書に基づいた回答か」を明示するのが超重要。

from langchain_core.runnables import RunnableParallel

# 出典情報付きのRAGチェーン
rag_chain_with_sources = RunnableParallel(
    {"context": retriever, "question": RunnablePassthrough()}
).assign(
    answer=lambda x: (
        prompt.invoke({
            "context": format_docs(x["context"]),
            "question": x["question"]
        })
        | llm
        | StrOutputParser()
    ).invoke(x["question"])
)

result = rag_chain_with_sources.invoke("出張旅費の精算方法は?")
print("回答:", result["answer"])
print("出典:")
for doc in result["context"]:
    print(f"  - {doc.metadata.get('source', '不明')} (ページ {doc.metadata.get('page', '不明')})")

ミドリ: 「出典:社内規程第5章 p.12」みたいに出るんですね。これなら信頼できる!

検索精度をもっと上げるテクニック

ミドリ(nodding): 基本はわかりました。もっと精度を上げる方法はありますか?

タツヤ: もちろん。4つの高度なテクニックがある。

ハイブリッド検索

タツヤ: ベクトル検索(意味的類似度)とキーワード検索(BM25)を組み合わせる方法。ベクトル検索は意味の類似性に強いけど、固有名詞や型番の完全一致には弱い。BM25はその逆。両方使えば、検索精度が15〜25%向上するケースが報告されてる。

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

# BM25 Retriever
bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 4

# ベクトル検索 Retriever
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 4})

# ハイブリッド検索
ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.4, 0.6]  # ベクトル検索を重視
)

Re-ranking(再ランキング)

タツヤ: 検索結果をクロスエンコーダモデルで再ランキングして、最も関連性の高い文書を上位に並び替える手法。初回検索で多めに候補を取得して(例:20件)、Re-rankingで絞り込む(例:4件)のが効果的。

クエリ変換

ミドリ: ユーザーの質問をそのまま検索に使うのは良くないんですか?

タツヤ: 場合によるけど、検索に最適な形に変換した方が精度は上がる。

メタデータフィルタリング

タツヤ: 文書にメタデータ(部署、文書種別、作成日など)を付けて、検索時にフィルタリングする。ノイズ削減に効く。

retriever = vectorstore.as_retriever(
    search_kwargs={
        "k": 4,
        "filter": {"department": "人事部", "doc_type": "規程"}
    }
)

ミドリ: 人事の質問なら人事部の文書だけ検索する、ってことですね。賢い。

本番運用のベストプラクティス

ミドリ: 本番で運用するとき、特に気をつけることは?

評価指標の設定

タツヤ: RAGの品質を定量的に測るために、4つの指標を監視しよう。

RAGASなどの評価フレームワークを使えば、これらを自動的に算出できるよ。

ドキュメントの更新パイプライン

タツヤ: 社内文書は常に更新されるから、以下の仕組みが必要。

  1. 新規・更新文書の検知 — ファイルの変更監視または定期バッチ
  2. 差分インデックス更新 — 変更があったドキュメントのみ再ベクトル化
  3. 古いチャンクの削除 — 削除されたドキュメントに対応するベクトルを削除
  4. インデックスの整合性チェック — 定期的にソースデータとの一致を検証

セキュリティとアクセス制御

ミドリ: 機密文書を扱う場合は気をつけないとですよね。

タツヤ: 当然。4つのポイントを押さえよう。

導入効果の数字

ミドリ: 実際にRAGを導入すると、どのくらい効果が出るんですか?

タツヤ: 導入企業のデータを紹介するよ。

ミドリ(smiling): ハルシネーション70%削減はすごい……! 投資回収期間はどのくらいですか?

タツヤ: 社内規程・マニュアル・技術文書を対象としたRAGシステムだと、3〜6ヶ月が一般的。導入効果を実感しやすいプロジェクトだよ。

まとめ:RAGはLLMビジネス活用の「必須アーキテクチャ」

ミドリ: 今日の話をまとめると、どうなりますか?

タツヤ(nodding): ポイントは5つ。

ミドリ: 「AIが嘘をつかない仕組み」を作るのがRAGの本質ですね。

タツヤ(nodding): その通り。LLMをビジネスの現場で安全に使うなら、RAGは避けて通れない技術だよ。


次のアクション

ミドリ: 最後に、読者がまずやるべきことを教えてください。

タツヤ: 3ステップで進めよう。

  1. 対象ドキュメントの棚卸し — RAGシステムに取り込む文書(FAQ、マニュアル、規程など)をまず特定しよう
  2. プロトタイプの構築 — 本記事のコード例を参考に、小規模なRAGシステムを構築して精度を検証しよう
  3. 本格導入の設計 — ベクトルDBの選定、セキュリティ設計、運用設計まで含めた包括的なRAG導入は、Shimanto AI Solutionsにご相談ください。数多くのRAGプロジェクトで培ったノウハウをもとに、高精度かつ安定したシステムの構築をサポートします