RAGとは何か、なぜ必要か
RAG(Retrieval-Augmented Generation)は、LLMの回答生成に外部知識を組み合わせる手法だ。LLM単体には学習データのカットオフという制約があり、社内ドキュメントや最新情報を参照できない。RAGはこの問題を、「必要なときに必要な情報を検索して文脈に追加する」というアプローチで解決する。
2024〜2025年にかけてRAGは急速に普及し、企業の社内FAQ、法律・医療分野の情報検索、コードベース検索など多くのユースケースで採用された。しかし単純な実装では精度が低く、「RAGを入れたけど使い物にならない」という声も多かった。本記事では、精度を上げるための具体的な手法を整理する。
基本アーキテクチャ
RAGシステムは大きく「インデックス構築フェーズ」と「検索・生成フェーズ」に分かれる。
インデックス構築フェーズでは、ドキュメントを読み込み、チャンクに分割し、各チャンクをEmbeddingベクトルに変換してベクトルDBに保存する。
検索・生成フェーズでは、ユーザーのクエリをEmbeddingに変換し、類似するチャンクをベクトルDBから取得し、それをコンテキストとしてLLMに渡して回答を生成する。
シンプルな実装例を示す。
from openai import OpenAI
import numpy as np
client = OpenAI()
def embed(text):
response = client.embeddings.create(
input=text,
model="text-embedding-3-small"
)
return response.data[0].embedding
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# 検索
def retrieve(query, documents, top_k=3):
query_emb = embed(query)
scored = [(doc, cosine_similarity(query_emb, embed(doc))) for doc in documents]
return sorted(scored, key=lambda x: x[1], reverse=True)[:top_k]
これは動くが、実運用に耐える精度は出ない。問題の多くはチャンキングとリランキングにある。
チャンキング戦略
チャンキングはRAGの精度に最も大きく影響する工程だ。チャンクが大きすぎると不要な情報が混入し、小さすぎると文脈が失われる。
固定長チャンキング
最もシンプルな方法で、一定のトークン数(例: 512トークン)でテキストを分割する。実装が簡単だが、文の途中で切れることがある。オーバーラップを設けることで文脈の断絶を緩和できる。
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=50,
separators=["\n\n", "\n", "。", ".", " "]
)
chunks = splitter.split_text(document)
セマンティックチャンキング
文のEmbeddingを計算し、意味的な類似度が急激に変化する境界でチャンクを切る方法だ。段落や文書構造を無視せず、トピックの区切りを自動的に検出できる。固定長より精度が上がるが、処理コストがかかる。
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
splitter = SemanticChunker(embeddings, breakpoint_threshold_type="percentile")
chunks = splitter.split_text(document)
階層型チャンキング(Parent-Child)
大きなチャンク(親)と小さなチャンク(子)を両方保持する手法だ。検索は粒度の細かい子チャンクで行い、取得後に親チャンクを返すことで、精度と文脈の豊富さを両立する。
LlamaIndexの ParentDocumentRetriever やLangChainの同名クラスがこのパターンを実装している。実務では最もバランスが良い選択肢の一つだ。
Embeddingモデルの選び方
2026年時点で選択肢になるEmbeddingモデルを整理する。
OpenAI text-embedding-3-large は現時点でも高精度で、多言語対応も良好だ。日本語文書でも安定した品質が出る。コストは3-smallの約6倍なので、まず3-smallで試してから必要なら切り替える。
Cohere embed-multilingual-v3.0 は多言語に特化したモデルで、日英混在の文書に強い。Cohereは「検索特化のEmbedding」という設計思想を持っており、RAGでの精度は高評価が多い。
ローカルモデルとしては、intfloat/multilingual-e5-large がコストゼロで使えるモデルの中では精度が高い。Ollamaでも動かせるので、プライバシー要件が厳しい場合の選択肢になる。
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("intfloat/multilingual-e5-large")
# E5モデルはプレフィックスが必要
vectors = model.encode([f"passage: {chunk}" for chunk in chunks])
リランキングで精度を大幅改善
ベクトル検索で取得した候補(top-20など)を、より精度の高いモデルで再スコアリングしてtop-3に絞るのがリランキングだ。計算コストが高いモデルを全文書に適用するのは現実的でないが、候補を絞った後ならコストを抑えられる。
CohereのRerank APIを使う例を示す。
import cohere
co = cohere.Client("your-api-key")
def rerank(query, documents, top_n=3):
results = co.rerank(
query=query,
documents=documents,
top_n=top_n,
model="rerank-multilingual-v3.0"
)
return [documents[r.index] for r in results.results]
# ベクトル検索で20件取得してから、リランキングで3件に絞る
candidates = vector_search(query, top_k=20)
final_docs = rerank(query, candidates, top_n=3)
リランキングを追加するだけで精度が10〜20%改善するケースは珍しくない。コストと精度のトレードオフを見ながら導入を検討する価値がある。
ハイブリッド検索
ベクトル検索(セマンティック検索)はキーワード検索の弱点を補うが、逆にキーワード検索が得意とする「固有名詞」「モデル番号」「コード」のような語に弱い。両方を組み合わせるのがハイブリッド検索だ。
ElasticsearchやOpenSearchでは標準機能として提供されており、PineconeやWeaviateなどのベクトルDBでも対応が進んでいる。スコアの統合にはRRF(Reciprocal Rank Fusion)がよく使われる。
def reciprocal_rank_fusion(rankings, k=60):
scores = {}
for ranking in rankings:
for rank, doc_id in enumerate(ranking):
if doc_id not in scores:
scores[doc_id] = 0
scores[doc_id] += 1 / (k + rank + 1)
return sorted(scores.items(), key=lambda x: x[1], reverse=True)
# bm25_results と vector_results を統合
fused = reciprocal_rank_fusion([bm25_results, vector_results])
ベクトルDB選定
プロダクション向けのベクトルDBは複数の選択肢がある。
Pineconeはマネージドサービスで、スケールアップを気にせず使える。小規模なら無料枠がある。Weaviateはセルフホスティングもマネージドも選べるOSSで、ハイブリッド検索を標準サポートしている。pgvectorはPostgreSQLの拡張機能で、既存のRDS環境に追加するだけで使える。ベクトルDB専用の管理コストを避けたい場合に向く。
まとめ
RAGの精度は、チャンキング・Embedding・リランキングの組み合わせで決まる。最初からすべてを最適化しようとせず、まず固定長チャンキングと安価なEmbeddingモデルで動かし、精度の問題が出たらセマンティックチャンキングとリランキングを追加するという段階的なアプローチが実用的だ。
「RAGは難しい」というイメージがあるが、適切な手法を順番に重ねていけば着実に精度を改善できる。2026年時点ではツールやライブラリも成熟しており、本記事で紹介した手法はどれも数百行以内のコードで実装できる。