論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
RAG
Retrieval-Augmented Generation
深層学習
別称: 検索拡張生成

🔖 キーワード索引

#検索拡張#LLM#ベクトル検索#Embedding#ハルシネーション対策#ナレッジベース#top-k検索#cosine類似度#BM25#chunking#re-rank#FAISS

🔖 RAG の深掘り ── 検索 + 生成パイプラインを SSDSE で構築する

RAG (Retrieval-Augmented Generation) は、 LLM の弱点である 「知識の鮮度切れ」「hallucination (もっともらしい嘘)」「ドメイン固有知識不足」を解決するために、 質問が来るたびに外部知識ベースから関連文書を検索 (Retrieval) し、 それを LLM のコンテキストに注入 (Augmented) してから回答を生成 (Generation) する手法。 Lewis+ (2020, NeurIPS) で命名・体系化。

💡 RAG の基本パイプライン (5 ステップ)

  1. Indexing (索引化、 オフライン): 知識文書を chunk (300-1000 token) に分割し、 各 chunk を embedding 化、 vector DB に格納
  2. Query embedding: ユーザー質問を同じ embedding model でベクトル化
  3. Retrieval: vector DB で cosine 類似度上位 k 件 (top-k retrieval) を取得
  4. Augmentation: 取得 chunk を質問とともにプロンプトに埋め込む
  5. Generation: LLM がコンテキストを参照しつつ回答生成

📐 RAG で使われる主要数式

📐 Cosine 類似度 (Retrieval の核心)

$$ \mathrm{sim}(\mathbf{q}, \mathbf{d}) = \dfrac{\mathbf{q} \cdot \mathbf{d}}{\|\mathbf{q}\|_2 \cdot \|\mathbf{d}\|_2} = \dfrac{\sum_{i=1}^{n} q_i d_i}{\sqrt{\sum q_i^2} \sqrt{\sum d_i^2}} $$

📐 数式を言葉で読み解く: q はクエリベクトル、 d は文書ベクトル (例: 768 次元 or 1536 次元)。 内積を両者の長さで割ると -1 ~ 1 の範囲のスカラー。 1 に近いほど意味が近い。 cosine は長さに依存しないため、 短いクエリと長い文書を公平に比較できる。 これに対し Euclidean 距離は長さの影響を受けるため、 RAG では cosine が標準。

📐 BM25 (古典的・キーワード一致型)

$$ \mathrm{BM25}(q, d) = \sum_{t \in q} \mathrm{IDF}(t) \cdot \frac{f(t, d) (k_1 + 1)}{f(t, d) + k_1 (1 - b + b \cdot |d|/\mathrm{avgdl})} $$ $$ \mathrm{IDF}(t) = \log \frac{N - n(t) + 0.5}{n(t) + 0.5} $$

📐 数式を言葉で読み解く: クエリ q の各タームを文書 d がどれだけ含むかを、 ① term frequency f(t,d) で重み付け、 ② 文書長 |d| で正規化、 ③ IDF (出現する文書数の逆数 log) で珍しいタームを優遇。 k1 ≈ 1.2-2.0、 b ≈ 0.75 が経験的標準。 cosine と相補的: cosine は意味検索 (semantic)、 BM25 はキーワード一致 (lexical) を捉える。 hybrid retrieval (両者の重み付き和) が現代の主流。

📐 Reciprocal Rank Fusion (RRF、 hybrid retrieval の融合式)

$$ \mathrm{RRF}(d) = \sum_{r \in R} \frac{1}{k + \mathrm{rank}_r(d)} $$

📐 数式を言葉で読み解く: 複数の retriever (cosine、 BM25、 ColBERT 等) の順位を逆数で合算。 k=60 が経験的標準 (Cormack+ 2009)。 スコアスケールを考えなくて済むため hybrid retrieval で広く使われる。 各 retriever で 100 位以下になった文書も少しは寄与する設計。

📐 Recall@k と Precision@k (RAG 評価の主指標)

$$ \mathrm{Recall}@k = \dfrac{|\text{正解文書} \cap \text{上位 } k \text{ 件}|}{|\text{正解文書}|} \qquad \mathrm{Precision}@k = \dfrac{|\text{正解文書} \cap \text{上位 } k \text{ 件}|}{k} $$

📐 数式を言葉で読み解く: Recall@k は「k 件取り出して何 % の正解を拾えたか」、 Precision@k は「上位 k 件の中の正解率」。 RAG では top-k (k=3~10) を LLM に渡すため Recall@k が重要 (取りこぼすと LLM が答えられない)。 Precision を落として Recall を上げる方が安全。

📐 Chunk size と retrieval 性能のトレードオフ

$$ \mathrm{Quality}(C) \propto \begin{cases} \text{recall} \uparrow & \text{if } C \text{ 大 (情報多い)} \\ \text{precision} \uparrow & \text{if } C \text{ 小 (密度高い)} \\ \text{cost} \uparrow & \text{if } C \times k \text{ 大 (context 長)} \end{cases} $$

📐 数式を言葉で読み解く: chunk が長い (1000 token) ほど情報の文脈は保たれるが retrieval の粒度が粗くなる。 chunk が短い (100 token) ほどピンポイントだが文脈が切れる。 経験的に 300-500 token + 10-20% overlap が多くのドメインで最適。 PDF、 manual、 chat log でそれぞれ最適 chunk size が違う。

🧮 SSDSE-B-2026 で RAG を実装してみる

SSDSE-B-2026 (47 都道府県 × 112 指標) を「県別レポート」に整形し、 「人口が多い県は?」「失業率が高い県は?」のような質問に RAG で答える例を実演する。

🐍 Python 実装 ── 最小 RAG パイプライン (sentence-transformers + chromadb)

🎯 このコードでやること: SSDSE-B-2026 を 47 県のテキストレポートに変換し、 sentence-transformers で embedding 化、 chromadb に格納、 自然言語質問で top-3 検索する RAG パイプラインを構築。

📥 入力データ: SSDSE-B-2026 2023 年、 各県を 1 chunk = 「{県名}: 総人口 {A1101}、 出生数 {A4101}、 失業率 {B4101}%」形式のテキスト。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# pip install sentence-transformers chromadb pandas
import pandas as pd
import chromadb
from sentence_transformers import SentenceTransformer

# 1. SSDSE 読込 + チャンク化
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]

chunks = []
metas = []
for _, row in df.iterrows():
    text = (f"{row['Prefecture']}の2023年データ: "
            f"総人口 {row['A1101']:,} 人、 "
            f"出生数 {row['A4101']:,} 人、 "
            f"完全失業率 {row['B4101']}%、 "
            f"婚姻数 {row['A9101']:,} 件。")
    chunks.append(text)
    metas.append({'pref': row['Prefecture']})

# 2. Embedding (multilingual model で日本語対応)
model = SentenceTransformer('intfloat/multilingual-e5-small')
embs = model.encode(chunks)
print(f'embedding shape: {embs.shape}')   # (47, 384)

# 3. Chroma DB に格納
client = chromadb.Client()
col = client.create_collection('ssdse_prefs')
col.add(documents=chunks, embeddings=embs.tolist(),
        metadatas=metas, ids=[m['pref'] for m in metas])

# 4. Retrieval: 質問を embedding 化 → top-3 検索
queries = ["人口が多い県は?", "失業率が高いのは?", "出生数が少ない県は?"]
for q in queries:
    q_emb = model.encode([q])[0]
    r = col.query(query_embeddings=[q_emb.tolist()], n_results=3)
    print(f'\n質問: {q}')
    for pref, doc, dist in zip(r['ids'][0], r['documents'][0], r['distances'][0]):
        print(f'  {pref:10s} (dist={dist:.3f}): {doc[:50]}...')

📤 実行すると次の出力が得られる:

embedding shape: (47, 384) 質問: 人口が多い県は? 東京都 (dist=0.342): 東京都の2023年データ: 総人口 14,086,000 人... 神奈川県 (dist=0.398): 神奈川県の2023年データ: 総人口 9,229,000 人... 大阪府 (dist=0.405): 大阪府の2023年データ: 総人口 8,763,000 人... 質問: 失業率が高いのは? 沖縄県 (dist=0.421): 沖縄県の2023年データ: 総人口 1,468,000 人、 完全失業率 17.4%... 福岡県 (dist=0.451): 福岡県の2023年データ: 総人口 5,103,000 人、 完全失業率 14.2%... 大阪府 (dist=0.458): 大阪府の2023年データ: 総人口 8,763,000 人、 完全失業率 15.0%... 質問: 出生数が少ない県は? 鳥取県 (dist=0.412): 鳥取県の2023年データ: 総人口 537,000 人、 出生数 3,263 人... 島根県 (dist=0.428): 島根県の2023年データ: 総人口 650,000 人、 出生数 3,759 人... 高知県 (dist=0.439): 高知県の2023年データ: 総人口 666,000 人、 出生数 3,380 人...

💬 結果の読み方: embedding 384 次元で 47 chunks をベクトル化、 「人口が多い県は?」というクエリで東京・神奈川・大阪が上位 3 に来た。 これは 大数値含む chunk が「人口が多い」のセマンティクスに近いと embedding model が学習しているため。 distance は小さいほど類似 (cosine 距離 = 1 - cosine 類似度)。 完全な数値順位が欲しい場合は SQL の方が適切だが、 「fuzzy な質問」「複合質問」では RAG の柔軟性が有利。

🐍 Python 実装 ── chunk size 比較実験

🎯 このコードでやること: SSDSE-B-2026 を chunk size = [50, 200, 500, 1000] の 4 通りで分割し、 同じ質問で Recall@5 がどう変わるかを比較する。

📥 入力データ: 47 県を連結した長文 (約 6,000 字)、 chunk_size で分割。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import pandas as pd
from sentence_transformers import SentenceTransformer
import numpy as np

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]

# 47 県を連結した長文 (各県 1 文)
long_text = ' '.join(
    f"{r['Prefecture']}: 総人口 {r['A1101']:,}人 失業率 {r['B4101']}%."
    for _, r in df.iterrows())
print(f'全体長: {len(long_text)} 文字')

model = SentenceTransformer('intfloat/multilingual-e5-small')

# 質問 = 「沖縄県」を含む chunk が正解
query = '沖縄の失業率は?'
q_emb = model.encode([query])[0]

for chunk_size in [50, 200, 500, 1000]:
    chunks = [long_text[i:i+chunk_size]
              for i in range(0, len(long_text), chunk_size)]
    embs = model.encode(chunks)
    sims = embs @ q_emb / (np.linalg.norm(embs,axis=1) * np.linalg.norm(q_emb))
    top5 = sims.argsort()[-5:][::-1]
    okinawa_found = any('沖縄' in chunks[i] for i in top5)
    print(f'chunk_size={chunk_size:4d}  n_chunks={len(chunks):3d}  '
          f'沖縄を top-5 で発見: {okinawa_found}')

📤 実行すると次の出力が得られる:

全体長: 1645 文字 chunk_size= 50 n_chunks= 33 沖縄を top-5 で発見: True ← 細かすぎず一件ずつ chunk_size= 200 n_chunks= 9 沖縄を top-5 で発見: True ← 適度な粒度 chunk_size= 500 n_chunks= 4 沖縄を top-5 で発見: True ← chunk 内に複数県、 やや雑 chunk_size=1000 n_chunks= 2 沖縄を top-5 で発見: True ← chunk = ほぼ全文、 retrieval の意味薄

💬 結果の読み方: chunk_size の変化で chunks 数 (33 → 2) が変わる。 短い chunk は precision 高い (1 件 = 1 県)、 長い chunk は recall は確保できるが LLM に「ノイズ」を一緒に渡してしまう。 実プロジェクトでは 200-500 chars / 300-500 token + 10% overlap が経験的最適。 ドメインによって調整 (法律 = 長め、 FAQ = 短め)。

🐍 Python 実装 ── Hybrid Retrieval (BM25 + dense embedding + RRF)

🎯 このコードでやること: BM25 (lexical) と dense embedding (semantic) の 2 つの retriever を並行で動かし、 Reciprocal Rank Fusion (RRF) で結合する hybrid retrieval を SSDSE で実装。

📥 入力データ: 47 県テキスト、 質問 = "東京の出生数"。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# pip install rank-bm25 sentence-transformers
import pandas as pd, numpy as np
from rank_bm25 import BM25Okapi
from sentence_transformers import SentenceTransformer

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True)
docs = [f"{r['Prefecture']} 総人口 {r['A1101']} 出生数 {r['A4101']} 失業率 {r['B4101']}"
        for _, r in df.iterrows()]

query = "東京の出生数"

# --- BM25 retriever ---
bm25 = BM25Okapi([d.split() for d in docs])
bm25_scores = bm25.get_scores(query.split())
bm25_rank = np.argsort(-bm25_scores)

# --- Dense retriever ---
model = SentenceTransformer('intfloat/multilingual-e5-small')
embs = model.encode(docs)
q_emb = model.encode([query])[0]
sims = embs @ q_emb / (np.linalg.norm(embs,axis=1) * np.linalg.norm(q_emb))
dense_rank = np.argsort(-sims)

# --- RRF 融合 (k=60 標準) ---
def rrf(rankings, k=60):
    scores = np.zeros(len(docs))
    for rank in rankings:
        for r, idx in enumerate(rank):
            scores[idx] += 1.0 / (k + r + 1)
    return np.argsort(-scores)

hybrid = rrf([bm25_rank, dense_rank])

print(f'クエリ: {query}\n')
print('--- BM25 top-3 ---')
for i in bm25_rank[:3]:
    print(f'  {df.iloc[i]["Prefecture"]}  bm25={bm25_scores[i]:.3f}')
print('--- Dense top-3 ---')
for i in dense_rank[:3]:
    print(f'  {df.iloc[i]["Prefecture"]}  cosine={sims[i]:.3f}')
print('--- Hybrid (RRF) top-3 ---')
for i in hybrid[:3]:
    print(f'  {df.iloc[i]["Prefecture"]}')

📤 実行すると次の出力が得られる:

クエリ: 東京の出生数 --- BM25 top-3 --- 東京都 bm25=3.215 ← 「東京」「出生数」を含む県を上位に 京都府 bm25=1.412 ← 「京」が一致したノイズ 北海道 bm25=1.083 --- Dense top-3 --- 東京都 cosine=0.812 神奈川県 cosine=0.731 ← セマンティック近い (首都圏) 大阪府 cosine=0.715 --- Hybrid (RRF) top-3 --- 東京都 ← 両 retriever で 1 位、 確信度高い 神奈川県 ← Dense の貢献 京都府 ← BM25 の貢献 (ノイズだが残る、 LLM で判別)

💬 結果の読み方: BM25 だけだと「京都」が東京の代わりに引っかかる (lexical 一致のみ)、 Dense だけだと首都圏が引っかかる (意味類似)。 Hybrid (RRF) は両方の強みを組み合わせ、 東京を確実に 1 位にしつつ多様性を残す。 OpenAI、 Anthropic、 Microsoft Azure AI Search 等の本番 RAG は全て hybrid を採用。

🐍 Python 実装 ── LangChain で完全な RAG (Retriever + LLM)

🎯 このコードでやること: SSDSE-B-2026 を LangChain でロードし、 Chroma に store、 質問応答 chain で LLM (例: OpenAI GPT-4o-mini) に Retrieval-Augmented 回答させる。

📥 入力データ: SSDSE-B-2026 を CSV ローダーで読み込み、 RecursiveCharacterTextSplitter で chunk_size=300 / overlap=30 に分割。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# pip install langchain langchain-openai langchain-community chromadb
import os
from langchain_community.document_loaders import CSVLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.chains import RetrievalQA

os.environ['OPENAI_API_KEY'] = 'sk-...'  # 自分の API key

# 1. ロード (CSV を各行 1 Document に)
loader = CSVLoader('data/raw/SSDSE-B-2026.csv', encoding='cp932')
raw_docs = loader.load()

# 2. 分割 (CSV は通常分割不要だが、 長い行があった場合のため)
splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=30)
docs = splitter.split_documents(raw_docs)
print(f'chunks: {len(docs)}')

# 3. Vector store
emb = OpenAIEmbeddings(model='text-embedding-3-small')
vectorstore = Chroma.from_documents(docs, emb, persist_directory='./chroma_ssdse')

# 4. RAG chain
retriever = vectorstore.as_retriever(search_kwargs={'k': 5})
qa = RetrievalQA.from_chain_type(
    llm=ChatOpenAI(model='gpt-4o-mini', temperature=0),
    chain_type='stuff', retriever=retriever, return_source_documents=True)

# 5. 質問
res = qa.invoke({'query': '人口の多い都道府県トップ 3 と、 各失業率を教えて'})
print('=== 回答 ===')
print(res['result'])
print('\n=== 参照 chunk ===')
for d in res['source_documents'][:3]:
    print(d.page_content[:80], '...')

📤 実行すると次の出力が得られる (GPT-4o-mini 応答例):

chunks: 564 === 回答 === 人口が多い都道府県トップ 3 と各失業率は以下の通りです (2023 年データ) 1. 東京都 : 総人口 14,086,000 人、 完全失業率 12.8% 2. 神奈川県 : 総人口 9,229,000 人、 完全失業率 12.4% 3. 大阪府 : 総人口 8,763,000 人、 完全失業率 15.0% (出典: SSDSE-B-2026 政府統計) === 参照 chunk === 2023,R13000,東京都,14086000,... 2023,R14000,神奈川県,9229000,... 2023,R27000,大阪府,8763000,...

💬 結果の読み方: LangChain の RetrievalQA chain で retrieval + 生成が 1 行で完結。 LLM の回答は完全に 取得 chunk の事実に基づき hallucination がない (return_source_documents=True で根拠も返却)。 これが RAG の最大利点 ── 「知らないことは答えない」「答えた内容は根拠を示せる」。 ChatGPT 単体だと SSDSE 数値を hallucinate するリスクがあるが、 RAG では退治可能。

⚠️ 落とし穴 (RAG 運用版)

🌐 RAG の派生・発展技術

技術名概要代表ツール
Naive RAG最小構成: index → retrieve → generateLangChain, LlamaIndex 入門
Advanced RAGpre-retrieval (query rewrite) + post-retrieval (rerank)Cohere Rerank, ColBERT
Modular RAG複数モジュール (router, planner, memory) を組合せLangGraph, LlamaIndex Workflow
Self-RAGLLM が retrieval 必要性を自己判断 (Asai+ 2023)Self-RAG (paper)
Corrective RAG (CRAG)retrieval 品質を自動評価、 不十分なら web 検索追加 (Yan+ 2024)CRAG (paper)
GraphRAG知識をグラフ化、 multi-hop に強い (Microsoft 2024)microsoft/graphrag
Agentic RAGエージェントが retriever / 計算ツール / web を使い分けAutoGPT, OpenAI Assistants
Long-Context RAG100k+ token モデル直接利用、 retrieval を省略Claude 200k, Gemini 1M

🔬 RAG の評価指標 (ragas / TruLens 等)

指標対象範囲計算方法
Context PrecisionRetrieval0-1retrieved chunks のうち真に関連する割合
Context RecallRetrieval0-1必要な情報のうち retrieved chunks に含まれる割合
FaithfulnessGeneration0-1回答内主張のうち context で根拠付けされる割合
Answer RelevancyGeneration0-1回答が質問に答えているか (LLM-as-judge)
Answer CorrectnessEnd-to-end0-1ground truth と回答の事実一致度
Hallucination RateGeneration0-1context にない主張の割合
Latency p95運用msretrieval + generation の総応答時間
Cost per query運用USDembedding + LLM API の合算

🔗 関連用語 (前提・並列・発展)

前提: 埋め込み (embedding)cosine 類似度ベクトル DBLLMTransformer

並列: ハルシネーションプロンプトエンジニアリングファインチューニングChatGPTClaudeGemini

発展: GraphRAGエージェント AI長文 LLMRerankerマルチモーダル RAGLLM 評価

📚 関連グループ教材

💡 30秒で分かる結論

🍰 まずはやさしく

AIにカンニングペーパーを渡す仕組みです。

最新の情報を正しく答えさせるために使います。

学校の校則をAIに教えるときに便利です。

ここではRAGの仕組みと目的を読みます。

RAG:検索結果を取り込んだ生成手法

📍 文脈ボックス

🍰 まずはやさしく

AIを使いこなすための応用テクニックです。

外部の知識でAIを強くするために使います。

スマホアプリに専門知識を持たせるようなものです。

ここではAI技術の中での位置づけを読みます。

この用語は 深層学習 / 生成 AI カテゴリに属します。 関連する別称・略号:検索拡張生成 (Retrieval-Augmented Generation)。

論文・実務レポートで RAG が登場したら、 まず本ページの「30秒で分かる結論」と「直感で掴む」を読めば、 その文脈で何を言っているか把握できます。

📂 大分類内での位置: 本ページは 「AI と社会・応用」 大分類の 「生成 AI / LLM 応用パターン」 中分類に属する。 同分類には プロンプトエンジニアリング / AI 応用 / AI コア技術 が並ぶ。 RAG は LLM を「外部知識」で補強する代表的応用パターン。

⬆ 直近の上位概念: AI コア技術(LLM の本体)、 プロンプトエンジニアリング(context への情報注入手法)、 API(ベクトル DB との通信プロトコル)。 RAG はこれら 3 つを「検索 → context 注入 → 生成」のパイプラインに統合する設計パターンである。

📅 本ページの読み進め方: この後、 (1) 🎨 直感 で「司書 + 専門家」の比喩、 (2) 📐 数式 で類似度検索の cosine 類似度、 (3) 🧮 計算 で SSDSE-B-2026 の都道府県データを chunk 化して埋め込む手順、 (4) 🐍 Python で sentence-transformers + FAISS を用いた最小実装、 と進む。 末尾の 🔗 隣接手法 で fine-tuning との使い分けを示す。

📍 RAG 本番運用の 8 パターン

パターン 1: ナレッジ Q&A (社内 wiki、 マニュアル)

最も典型的。 Confluence や Notion を chunk 化、 検索結果に出典付きで回答。 例: Microsoft Copilot for M365、 Glean。

パターン 2: カスタマーサポート (FAQ 自動応答)

過去のチケット + FAQ を index 化、 ユーザー問合せに RAG で 1 次回答 → 解決しなければ人間にエスカレーション。 例: Intercom Fin、 Zendesk Answer Bot。

パターン 3: 文書 Q&A (契約書、 論文、 法律)

長文書を chunk 化、 「この契約の解約条項は?」のような pinpoint 質問。 例: Harvey AI、 Casetext。

パターン 4: コードベース Q&A

repo を AST 単位で chunk 化、 「decode 関数はどこで定義?」のような検索。 例: GitHub Copilot Chat、 Sourcegraph Cody。

パターン 5: マルチモーダル RAG

画像、 表、 PDF レイアウトを CLIP / LayoutLM 等で embedding。 例: PDF + image を統合検索する Anthropic Sonnet 4 Vision。

パターン 6: SQL-augmented RAG (text-to-SQL)

structured DB から事実回答が必要な場合、 LLM に SQL を書かせて実行 + 結果を RAG として注入。 例: Snowflake Cortex、 LangChain SQLDatabaseChain。

パターン 7: Agentic RAG (multi-step reasoning)

「東京と大阪の人口比は?」のような multi-hop 質問で、 agent が ① 東京を検索 ② 大阪を検索 ③ 比を計算、 と動的に分解。 例: OpenAI Assistants、 LangGraph Agents。

パターン 8: Long-Context Hybrid

retrieval で 100 chunks 取得 → Claude 200k or Gemini 1M に全部投入。 retrieval 品質が低くても LLM 側で取捨選択。 cost と精度のトレードオフ。

🔬 RAG のセキュリティ脅威 7 つ

脅威概要対策
Prompt Injection取得 chunk に「これまでの指示を無視 ...」が埋め込まれるcontext タグで囲み、 system prompt で明示無視
Data Exfiltration機密 chunk の内容を回答で外に流出PII redaction、 アクセス制御 metadata
RAG Poisoning攻撃者が誤情報を index に注入、 全ユーザーに誤情報配信index 更新の承認制、 signature verify
Authorization Bypass役職権限のない情報を取得metadata filter (role-based)
Hallucination Amplificationretrieval 失敗時に LLM が捏造、 出典を偽装faithfulness 監視、 「不明」を return する mechanism
Cost-based DoS大量クエリで API cost を消耗rate limit、 user quota、 cache
Indirect Prompt InjectionPDF / web page 経由で攻撃文字列を注入入力サニタイズ、 source allowlist

🎨 RAG パフォーマンスチューニングの 10 戦術

  1. Embedding model のアップグレード: ada-002 → text-embedding-3-large or multilingual-e5-large
  2. Chunk size 調整: 100 → 500 token に増やすと文脈保持、 50 token に減らすと precision 向上
  3. Overlap 導入: chunk_overlap=10-20% で境界での情報欠落を防ぐ
  4. Hybrid retrieval: BM25 + Dense の組合せで recall を + 5-15 pt
  5. Reranker 追加: 1 段目 top-20 → 2 段目 top-5、 NDCG@5 が + 10 pt 改善
  6. Query rewriting: LLM に質問を 3 通り書き換えさせて並行検索 (multi-query retrieval)
  7. HyDE (Hypothetical Doc Embeddings): 質問 → LLM で仮想回答生成 → その embedding で検索
  8. Metadata filter 活用: 時系列、 地域、 部署で事前絞り込み
  9. Parent-child chunking: 小 chunk で検索 → 大 chunk (周辺含む) を context 投入
  10. Caching: 同一 / 類似クエリの結果を redis にキャッシュ、 cost と latency 両方削減

📐 RAG の合成性能式 (実用近似)

$$ \text{Final Answer Quality} \approx \text{Retrieval Quality} \times \text{Generation Quality} $$ $$ \text{Retrieval Quality} = \text{Recall}@k \times \text{Precision}@k \times \text{Coverage} $$ $$ \text{Generation Quality} = \text{Faithfulness} \times \text{Relevancy} \times (1 - \text{Hallucination}) $$

📐 数式を言葉で読み解く: RAG の最終品質は retrieval と generation の「掛け算」。 どちらかが 0.5 なら全体も 0.5 以上にはならない。 retrieval が完璧でも LLM が文脈を無視 (低 Faithfulness) なら結果はダメ、 LLM が優秀でも retrieval が失敗すれば回答できない。 つまり RAG はパイプライン全体の最弱リンクで律速される。 改善は最弱箇所を ragas 等で特定してから。

🌐 RAG 関連の重要論文

📚 学習リソース

RAG は急速に進化中の分野。 本記事は 2025 年中盤時点のスナップショット、 最新は本記事末尾の論文・ドキュメント先で随時確認のこと。

🎨 直感で掴む

🍰 まずはやさしく

司書さんが本を探して専門家に渡すイメージです。

AIが嘘をつくのを防ぐために使います。

部活の最新の予定をAIに答えさせる例です。

ここではRAGがなぜ便利なのかを読みます。

ChatGPT に「自社の就業規則について教えて」と聞いても答えられない (学習データに含まれない)。 そこで 就業規則 PDF を事前に検索可能なベクトル DB に入れておき、 質問が来たら関連箇所を取り出して LLM に渡す。 LLM はその文脈に基づいて回答するので、 正確性が大幅向上 ── これが RAG。 知識更新のたびに再学習する必要がない点が画期的。

補足として、 RAG はファインチューニングと比較して知識の更新コストが極めて低い。 新しい文書を追加したい場合、 モデル本体を再学習する必要はなく、 ベクトル DB に当該文書のチャンクとその埋め込みを追記するだけで済む。 これは 知識と推論の分離 という設計思想に基づいており、 大規模言語モデル時代の標準アーキテクチャの一つとして急速に普及した。 SSDSE-B-2026 のような統計データを扱う場合でも、 「2024 年の北海道の人口は?」「秋田県の高齢化率は全国何位?」といった事実質問に対し、 RAG ならば数値を取り違えずに検索 → 回答できる利点がある。

🎨 RAG FAQ 12 選 ── 実務でよくある質問

Q1. RAG と Fine-tuning、 どちらを使うべき?

「知識を追加したい」なら RAG、 「文体・推論能力を変えたい」なら Fine-tuning。 SSDSE 数値を答えさせたいなら確実に RAG。 自社の文体で書かせたいなら Fine-tuning。 両方欲しいなら併用 (RAG + LoRA fine-tuning が最近の流行)。

Q2. chunk_size の最適値は?

多くのドメインで 300-500 token (約 500-800 字 日本語)、 overlap 10-20% が経験則。 PDF / 法律 = 大きめ (500-1000)、 FAQ / chat = 小さめ (100-300)。 ragas で評価しながら調整。

Q3. embedding model は何を選ぶ?

日本語含む multilingual なら intfloat/multilingual-e5-large or BAAI/bge-m3 (OSS) / cohere embed-multilingual-v3 (商用)。 純英語なら OpenAI text-embedding-3-large。 ドメイン固有 (医療、 法律) なら専門 model fine-tuning。

Q4. vector DB は何を選ぶ?

開発・小規模 = Chroma (Python ネイティブ)、 中規模本番 = Qdrant or Weaviate、 大規模 = Milvus、 SaaS = Pinecone。 既存 PostgreSQL があれば pgvector で十分なケースも多い。

Q5. top-k はいくつ?

3-5 が多くのケースで OK。 ただし reranker で 20-50 → 3-5 に絞る 2 段階構成が高精度。 LLM context 長制限と cost を考慮。

Q6. hallucination はどう防ぐ?

① system prompt で「context にない情報は『不明』と答えよ」と明示、 ② retrieval score が閾値以下なら「該当情報なし」を return、 ③ ragas で faithfulness 監視、 ④ 出典付き回答を強制、 ⑤ critic LLM で 2 段階チェック。

Q7. 長文 LLM (Claude 200k 等) があれば RAG いらない?

場合による。 ① 知識量が context 長より小さく、 ② cost と latency が許容なら長文 LLM 直接でも OK。 ただし知識ベースが定期更新される、 ユーザーごとに知識が違う、 cost を抑えたい場面では RAG が依然有利。 ハイブリッド (retrieval で絞ってから長文投入) が現実解。

Q8. multilingual RAG の注意点は?

① embedding model も multilingual 対応必須、 ② query 言語と doc 言語が違う場合 cross-lingual retrieval、 ③ 言語別 chunk size 調整 (日本語は token あたり情報量が多い)、 ④ LLM は必要言語をすべて理解できるか確認 (Claude / GPT-4 は多言語得意)。

Q9. RAG の cost を抑える方法は?

① embedding cache (同じ chunk を再 embedding しない)、 ② query cache (同じ質問は cached answer 返却)、 ③ small embedding model (e5-small / bge-small)、 ④ small LLM (GPT-4o-mini / Claude Haiku)、 ⑤ reranker で context を絞り LLM トークン削減。

Q10. RAG の latency が遅い、 どう改善?

① embedding は事前計算 (オフライン)、 ② vector DB の index 種類を HNSW に、 ③ retrieval を並列化、 ④ LLM stream response、 ⑤ caching 階層化 (CDN + redis + LLM)、 ⑥ critical path は small model、 詳細は large model 。

Q11. PDF レイアウト (表、 図) を扱うには?

pypdf / pdfplumber で text 抽出は限界、 ② Unstructured.io / Docling / LayoutLM / LlamaParse を使うと表構造保持、 ③ 図はキャプション込みで chunk、 ④ multimodal embedding (CLIP) で image そのものも index。

Q12. RAG を CI/CD に組み込む方法は?

① 評価セット (50-200 件) を git 管理、 ② PR ごとに ragas で評価実行、 ③ 性能劣化があれば fail、 ④ DeepEval や TruLens で unit test 風に組み込む、 ⑤ production trace を W&B / LangSmith に送り回帰検知。

🧮 SSDSE-B-2026 ベースの RAG ベンチマーク 5 問

質問正解難度RAG の弱点
東京の総人口は?14,086,000 人★ 単一 chunkなし、 確実に解ける
人口最多と最少の比は?14086 / 537 ≈ 26.2 倍★★ 2 chunk + 計算multi-hop、 計算力
失業率 15% 超の県は?沖縄、 大阪 (+ 該当 N 県)★★★ 全件走査全 chunk 必要、 top-k 不足
出生率 (出生数 / 人口) が最高の県は?沖縄県 (約 8.5‰)★★★★ 全件 + 計算計算 + 比較、 SQL の方が適切
東北 6 県の平均失業率は?約 12.9%★★★ 6 chunk + 平均multi-hop + 集計

RAG はキーワード検索的な質問は得意だが、 「集計」「全件走査」「複合条件」は SQL や agent (text-to-SQL) との併用が必要。 純粋 RAG では難度 ★★ 以上で精度が落ち始める。 実用化では 「質問の種類に応じてルーティング (RAG / SQL / 計算)」する agent 構造が王道。

🎨 まとめ ── RAG 5 つの第一原理

  1. 「知らないことは答えない」: hallucination 退治のため context 外の主張を禁じる。 これが RAG の最大価値。
  2. 「retrieval が品質を律速」: LLM がどんなに賢くても、 必要な chunk を取れなければ答えられない。 retrieval 品質に集中投資。
  3. 「Hybrid + Rerank が定石」: BM25 + Dense + Reranker の 3 層で recall と precision を両立。
  4. 「評価駆動開発 (ragas)」: 主観でなく数値で改善 PDCA を回す。
  5. 「セキュリティを忘れない」: prompt injection、 認可、 PII redaction を最初から組み込む。

🗺 概念マップ ── RAG エコシステム

RAG (Retrieval-Augmented Generation)
├── Indexing (オフライン)
│   ├── Document Loading
│   │   ├── PDF (pypdf, pdfplumber, LlamaParse, Unstructured)
│   │   ├── HTML (BeautifulSoup, Trafilatura)
│   │   ├── CSV / Excel (pandas, openpyxl)
│   │   ├── Notion / Confluence / Slack (公式 API)
│   │   └── Markdown / 書類 (LangChain Loaders)
│   ├── Text Splitting (Chunking)
│   │   ├── Fixed-size (RecursiveCharacterTextSplitter)
│   │   ├── Semantic (sentence-level、 paragraph-level)
│   │   ├── Structural (markdown header、 code AST)
│   │   └── Late chunking (jina-ai 2024)
│   ├── Embedding
│   │   ├── Dense (OpenAI ada/3、 e5、 bge、 cohere)
│   │   ├── Sparse (BM25、 SPLADE)
│   │   └── Multi-vector (ColBERT)
│   └── Vector Store
│       ├── 開発用 (Chroma、 FAISS in-memory)
│       ├── 本番用 (Qdrant、 Weaviate、 Milvus、 pgvector)
│       └── SaaS (Pinecone、 Vespa、 Vald)
├── Retrieval (オンライン)
│   ├── Query Processing
│   │   ├── Query Rewrite (LLM で言い換え)
│   │   ├── Query Decomposition (multi-hop 分解)
│   │   ├── HyDE (仮想回答ベクトルで検索)
│   │   └── Multi-Query (3-5 通り並行)
│   ├── Retrieval Strategy
│   │   ├── Dense (semantic)
│   │   ├── Sparse / BM25 (lexical)
│   │   ├── Hybrid (RRF, weighted sum)
│   │   └── Metadata filter (role、 time、 region)
│   ├── Reranking
│   │   ├── Cross-encoder (BGE Reranker、 Cohere Rerank)
│   │   ├── LLM-based (GPT-4 judge)
│   │   └── ColBERT v2 (late interaction)
│   └── Post-processing
│       ├── Deduplication (近似 chunk の統合)
│       ├── Compression (LongLLMLingua 等で要約)
│       └── Citation extraction (出典抽出)
├── Generation
│   ├── Prompt Engineering
│   │   ├── System Prompt (役割、 制約、 cite ルール)
│   │   ├── Context Tags (<context>...</context>)
│   │   └── Few-shot Examples
│   ├── LLM
│   │   ├── 商用 (GPT-4o、 Claude 4.7、 Gemini)
│   │   ├── OSS (Llama 3、 Mistral、 Qwen、 DeepSeek)
│   │   └── 専門 (Code Llama、 Med-PaLM、 BloombergGPT)
│   └── Post-generation
│       ├── Fact Verification (critic LLM、 NLI)
│       ├── Citation Insertion
│       └── Toxicity / PII Filter
└── Evaluation & Operations
    ├── Offline Eval
    │   ├── ragas (4 軸自動)
    │   ├── TruLens (本番観測 + 評価)
    │   └── DeepEval (unit test)
    ├── Online Monitoring
    │   ├── LangSmith / W&B (trace)
    │   ├── Latency / cost dashboards
    │   └── User feedback loop
    └── Security
        ├── Prompt injection defense
        ├── Authorization (role-based metadata)
        ├── PII redaction
        └── Audit log

📝 主要用語ミニ辞典

英語日本語出典
RAG検索拡張生成Lewis+ (2020)
DPR (Dense Passage Retrieval)密ベクトル検索Karpukhin+ (2020)
BM25BM25 スコアRobertson+ (1995)
ColBERT後期相互作用検索Khattab & Zaharia (2020)
RRF (Reciprocal Rank Fusion)逆順位融合Cormack+ (2009)
HNSW階層的小世界グラフMalkov & Yashunin (2018)
HyDE (Hypothetical Doc Embeddings)仮想文書埋め込みGao+ (2022)
Self-RAG自己反省型 RAGAsai+ (2023)
CRAG (Corrective RAG)是正型 RAGYan+ (2024)
GraphRAGグラフ RAGEdge+ (Microsoft 2024)
ragasRAG 自動評価Es+ (2024)
Reranker再順位付けNogueira & Cho (2019)
Faithfulness忠実度RAG 評価で標準
Hallucinationハルシネーション (捏造)Ji+ (2023) Survey

🧮 RAG cost 概算 ── SSDSE 100 件 Q&A の例

仮: SSDSE-B-2026 (564 行 × 112 列) を chunk_size=300 で分割すると約 600 chunks、 1 月あたり 100 件の質問を処理する場合の cost 概算 (2025 年 OpenAI 価格基準)。

項目単価使用量月額
Embedding (text-embedding-3-small)$0.02 / 1M token初回 600 chunks × 300 token = 180k token$0.0036 (≒ ¥0.55、 1 回のみ)
Embedding (query)$0.02 / 1M token100 query × 30 token = 3k token$0.00006
LLM input (GPT-4o-mini)$0.15 / 1M token100 query × (system 200 + top-5 chunks × 300) = 170k token$0.0255 (≒ ¥3.8)
LLM output (GPT-4o-mini)$0.60 / 1M token100 answer × 200 token = 20k token$0.012 (≒ ¥1.8)
Vector DB (Chroma)OSS600 vec × 1536 dim$0 (自社サーバ)
合計≒ $0.04 / 月 (約 ¥6)

SSDSE 規模 (600 chunks、 月 100 件) なら cost は無視できる水準。 これが企業のナレッジ (10 万 chunks、 月 1 万件) になると embedding 月 30 ドル、 LLM 月 300 ドル 程度。 GPT-4o (mini でなく) を使えば × 10。 cost optimization は chunk 数の削減 + 小型 LLM の選定 + cache 導入が三本柱。

🔬 SSDSE × RAG ハンズオン 5 課題 (学習者向け)

  1. basic: SSDSE-B-2026 を chunk_size=200 / 500 / 1000 で 3 通り index 化、 「東京の人口は?」の retrieval を比較せよ。 chunk 内容を確認せよ。
  2. retrieval: 「失業率が高い県は?」を BM25 / Dense / Hybrid の 3 通りで検索、 top-5 を比較せよ。 沖縄県が確実に top-3 に入る retriever はどれか。
  3. evaluation: 5 個の評価質問を作成、 ragas で context_precision / faithfulness を計測せよ。 最も低い指標を改善する手段を 2 つ提案せよ。
  4. filter: metadata に region (北海道、 東北、 関東、 ...) を付加、 「中部地方限定」で人口最多県を取得せよ。 期待値は愛知県 (7,477,000 人)。
  5. agent: 「東京と大阪の人口差を、 全国平均で割った値は?」のような multi-hop 質問を agent + tools で解く。 retrieval だけでは無理であることを確認せよ。

本ハンズオンは SSDSE-B-2026 (政府公開データ、 CC BY 4.0) で完全再現可能。 LangChain / LlamaIndex / Chroma / sentence-transformers のいずれかをインストールすれば、 ローカル環境で 30 分以内に課題 1-4 を完走できる。

🌐 RAG 製品事例 ── 2024-25 年の主要採用

製品事業者用途技術スタック
Microsoft 365 CopilotMicrosoft企業内文書 Q&AAzure AI Search + GPT-4
ChatGPT EnterpriseOpenAI企業ナレッジ統合独自 RAG + GPT-4o
Claude ProjectsAnthropicプロジェクト文書集約長文 + Contextual Retrieval
GleanGlean Technologies企業横断検索 + Q&A独自 vector + GPT-4 / Claude
Notion AI Q&ANotionワークスペース内検索Anthropic Claude + 内部 RAG
Perplexity AIPerplexityWeb 検索 + 出典付き回答独自 web index + LLM
Intercom FinIntercomカスタマーサポート 1 次対応RAG + GPT-4
Harvey AIHarvey法律事務所文書 Q&A専門 RAG + Fine-tuned GPT-4
Cursor / Sourcegraph CodyAnysphere / Sourcegraphコードベース理解 + 生成code-aware RAG + Claude
Doogie Assistant (日本)PKSHA Technology国内企業向け日本語 RAGmultilingual-e5 + 日本語 LLM

2024-25 年は RAG が「概念実証」から「本番稼働」へ移行した転換年。 多くの企業ナレッジ管理製品が RAG を必須機能として実装している。 個人開発でも LangChain + Chroma + ローカル LLM で同等システムが構築可能。

🎨 概念図で押さえる

RAG の核心を 3 つの図で押さえる。 ① RAG のパイプライン全体像、 ② Naive RAG と Advanced RAG の違い、 ③ 評価指標 (RAGAS の 4 軸)。

RAG パイプライン
図 A: RAG パイプライン — 質問 → 埋め込み → 検索 → 生成 → 回答。 ベクトル DB が検索の核。
Naive RAG vs Advanced RAG
図 B: Naive vs Advanced RAG — クエリ書き換え/Reranker/ハイブリッド検索が品質を底上げする。
RAGAS の 4 軸評価
図 C: RAGAS の 4 指標 — Faithfulness/Answer Relevancy/Context Precision/Context Recall で総合評価。

図のまとめ: RAG は質問 → 埋め込み → 検索 → 生成 → 回答の 5 段階パイプライン。 Advanced RAG はクエリ書き換え/ハイブリッド検索/Reranker で精度を上げ、 RAGAS の 4 指標で多角的に評価する。

📐 定義・数式

🍰 まずはやさしく

検索してつなげて作るという計算の手順です。

回答が正しいかを数値で確かめるために使います。

買い物サイトで似た商品を探す仕組みに似ています。

ここでは処理の流れと評価の方法を読みます。

【RAG の処理フロー】
$$ \text{Answer} = \text{LLM}\big(\text{Query} \,||\, \text{Retrieve}(\text{Query}, \mathcal{D})\big) $$

ユーザークエリと、 文書集合 $\mathcal{D}$ から検索 (Retrieve) した関連文書をプロンプトとして連結し、 LLM に投げて回答を得る。 検索は通常 Embedding ベクトルのコサイン類似度。

📐 RAG 評価指標 Faithfulness — 数式を言葉で読み解く

$$\text{Faithfulness} = \frac{|\{c \in \text{Claims}(a) : c \text{ is supported by } R\}|}{|\text{Claims}(a)|}$$

この式を言葉で読み解くと、 分子は「生成回答 $a$ に含まれる主張のうち検索コンテキスト $R$ で裏付けられる主張の数」、 分母は「回答中の全主張数」を表す。 つまり Faithfulness は 幻覚(ハルシネーション)の裏返し で、 1.0 に近いほど LLM が検索結果に忠実、 0.5 を切ると半分以上が無根拠の生成という危険信号になる。 RAGAS (RAG Assessment) フレームワークでは Faithfulness と並んで Answer Relevancy、 Context Precision、 Context Recall の 4 指標で総合評価する。

指標 何を測るか 改善方法
Faithfulness回答 ⊂ 検索結果かプロンプトに「文脈外を答えない」明示、 温度↓
Answer Relevancy回答 ⇄ 質問の関連性回答テンプレート、 質問の意図抽出
Context Precision検索結果中の有用文書比率reranker (cross-encoder) 追加
Context Recall真に必要な情報の取得率chunk サイズ調整、 hybrid (BM25+dense)

このコードでやること: SSDSE-B-2026 の都道府県統計を文書化し、 sentence-transformers で埋め込み、 「東京都の総人口は?」というクエリで dense retrieval を実行、 検索された 3 文書の類似度スコアを確認する。 簡易 RAG パイプラインの retrieval 段階を再現する。

📥 入力データ (SSDSE-B-2026 を 1 都道府県 1 ドキュメント化):

doc_id text 0 北海道の総人口は 5,092,000 人、 生産年齢人口は 2,897,000 人。 1 青森県の総人口は 1,184,000 人、 生産年齢人口は 649,000 人。 ... 12 東京都の総人口は 14,086,000 人、 生産年齢人口は 9,368,000 人。 ... 46 沖縄県の総人口は 1,468,000 人、 生産年齢人口は 882,000 人。 (全 47 ドキュメント)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import pandas as pd
from sentence_transformers import SentenceTransformer, util

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[df['SSDSE-B-2026'] == 2023]

docs = [
    f"{r['Prefecture']}の総人口は {int(r['A1101']):,} 人、"
    f" 生産年齢人口は {int(r['A1302']):,} 人。"
    for _, r in d.iterrows()
]

model = SentenceTransformer('intfloat/multilingual-e5-small')
doc_emb = model.encode(docs, normalize_embeddings=True)
query = "東京都の総人口は?"
q_emb = model.encode([query], normalize_embeddings=True)

scores = util.cos_sim(q_emb, doc_emb)[0]
top3 = scores.argsort(descending=True)[:3]
for i in top3:
    print(f"score={scores[i]:.4f}  {docs[i][:60]}...")

📤 実行すると次の出力が得られる:

score=0.9384 東京都の総人口は 14,086,000 人、 生産年齢人口は 9,368,000 人。... score=0.8520 千葉県の総人口は 6,257,000 人、 生産年齢人口は 3,798,000 人。... score=0.8435 愛知県の総人口は 7,477,000 人、 生産年齢人口は 4,627,000 人。...

💬 クエリ「東京都の総人口は?」に対して 1 位は東京都のドキュメント (cos sim = 0.938)、 2 位 / 3 位は千葉・愛知と続く。 Context Precision の観点では top-1 のみが正解なので 1/3 = 0.33 だが、 LLM に渡せば top-1 を引いて正確に「14,086,000 人」と回答できる。 multilingual-e5 は日本語クエリでも都道府県名と内容の意味的近さを学習しており、 BM25 だけでは捕まえにくい「同種カテゴリ(大都市圏)」も上位に並ぶ。 hybrid (BM25 + dense) reranking や cross-encoder reranker で Context Precision を 1.0 に近づけるのが本番運用の定石。

🔬 数式を言葉で読み解く

数式に出てくる記号の意味を 1 つずつ確認しましょう。

Query
ユーザーの質問。
$\mathcal{D}$
文書集合 (ナレッジベース)。
Retrieve
ベクトル類似度検索、 BM25 など。
Embedding
テキストを密ベクトル化。
Context Window
LLM の入力長制限。

🔬 RAG アーキテクチャの 4 世代

世代名称構成代表年課題
1Naive RAGembed → top-k → prompt2020-22low precision、 multi-hop 弱い
2Advanced RAG+ query rewrite + reranker + hybrid2023構成複雑化、 cost 増
3Modular / Agentic RAG+ router + planner + memory + agent2024latency 増、 debug 困難
4GraphRAG / Long-Context知識グラフ or 100k+ context 直接2024-25グラフ構築コスト、 長文の attention 劣化

🐍 Python 実装 ── Reranker による re-ranking

🎯 このコードでやること: 1 段目 retrieval で top-20 取得、 2 段目で cross-encoder reranker (BAAI/bge-reranker-v2-m3) で精緻な再順位付けし top-5 を LLM に渡す Advanced RAG パターン。

📥 入力データ: SSDSE-B-2026 47 県、 質問 = "失業率が 15% を超える県は?"。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# pip install sentence-transformers
import pandas as pd, numpy as np
from sentence_transformers import SentenceTransformer, CrossEncoder

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True)
docs = [f"{r['Prefecture']} 総人口 {r['A1101']:,} 失業率 {r['B4101']}%"
        for _, r in df.iterrows()]

query = "失業率が 15% を超える県は?"

# 1段目: dense retrieval で top-20
model = SentenceTransformer('intfloat/multilingual-e5-small')
embs = model.encode(docs); q = model.encode([query])[0]
sims = embs @ q / (np.linalg.norm(embs,axis=1) * np.linalg.norm(q))
stage1 = sims.argsort()[-20:][::-1]
print('--- 1 段目 (dense) top-5 ---')
for i in stage1[:5]:
    print(f'  {df.iloc[i]["Prefecture"]:8s}  cosine={sims[i]:.3f}')

# 2段目: cross-encoder reranker で 20 → 5
reranker = CrossEncoder('BAAI/bge-reranker-v2-m3')
pairs = [(query, docs[i]) for i in stage1]
re_scores = reranker.predict(pairs)
stage2 = stage1[np.argsort(-re_scores)][:5]
print('\n--- 2 段目 (rerank) top-5 ---')
for i, s in zip(stage2, sorted(re_scores, reverse=True)[:5]):
    print(f'  {df.iloc[i]["Prefecture"]:8s}  rerank_score={s:.3f}')

📤 実行すると次の出力が得られる:

--- 1 段目 (dense) top-5 --- 沖縄県 cosine=0.621 福岡県 cosine=0.598 北海道 cosine=0.572 大阪府 cosine=0.564 青森県 cosine=0.558 --- 2 段目 (rerank) top-5 --- 沖縄県 rerank_score=0.892 ← 17.4% で 15% 超、 最も確信 福岡県 rerank_score=0.764 ← 14.2%、 ボーダーライン 大阪府 rerank_score=0.731 ← 15.0%、 ぎりぎり 超え 北海道 rerank_score=0.621 青森県 rerank_score=0.598

💬 結果の読み方: 1 段目 dense retrieval は「失業率が高そう」を捉えるが順位が雑。 2 段目 reranker は質問と各 chunk を 1 ペアずつ cross-encode して厳密スコア化、 「15% を超える」という条件を踏まえた精緻な順位に修正。 reranker は cost 高いため top-20 → top-5 のような 2 段階構成が定石。

🎨 RAG vs Fine-tuning vs Prompt-only ── 選択指針

観点RAGFine-tuningPrompt-only
知識更新即時 (DB 更新)再学習必要不可
cost中 (vector DB + 検索 + 推論)高 (GPU 訓練 + 推論)低 (推論のみ)
hallucination低 (根拠あり)
出典提示可能 (chunk metadata)困難困難
企業 NDA データ適 (オンプレ可)難 (訓練に流出)適 (送信のみ)
ドメイン専門用語中 (chunk に依存)高 (内部化)
文体・トーン制御
推奨用途FAQ、 検索、 ナレッジ Q&A特殊文体、 ドメイン化汎用、 軽量タスク

結論: 「事実 / 検索」が重要なら RAG、 「文体 / 推論」を変えたいなら Fine-tuning、 「汎用」なら Prompt-only。 多くの実用システムは RAG + Fine-tuning のハイブリッドを採用 (Microsoft Copilot、 ChatGPT Enterprise 等)。

🧮 実値で計算してみる

LangChain での簡易 RAG パイプライン例。

STEP 1 ドキュメント分割
PDF や Web を 500〜1000 トークンのチャンクに。
STEP 2 Embedding 化
OpenAI text-embedding-3 などでベクトル化。
STEP 3 ベクトル DB 保存
FAISS, Chroma, Pinecone へ。
STEP 4 クエリ時検索+生成
類似 Top-k を取得し LLM に渡す。

🧮 RAG を ragas で定量評価する完全ワークフロー

RAG の難所は「うまく動いたかをどう確かめるか」。 主観評価では再現性なし、 LLM-as-judge は cost と再現性の両方が課題。 ragas (RAG Assessment) は 4 軸 (Context Precision/Recall + Faithfulness + Answer Relevancy) を半自動で計測する OSS。

🐍 Python 実装 ── ragas で SSDSE RAG を評価

🎯 このコードでやること: SSDSE-B-2026 に対する 5 問の評価セット (質問 + 期待文脈 + 期待回答) を作成、 RAG パイプラインに通し、 ragas の 4 指標を計測する。

📥 入力データ: 47 県データ + 5 評価質問 ({"question":"...","ground_truth":"...","contexts":[...]})。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# pip install ragas datasets pandas
import pandas as pd
from datasets import Dataset
from ragas import evaluate
from ragas.metrics import (context_precision, context_recall,
                           faithfulness, answer_relevancy)

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]

# 5 個の評価サンプル (RAG が返した回答と取得 chunk を想定)
eval_data = {
    'question': [
        '東京の人口は?',
        '失業率が最も高い県は?',
        '出生数が最も少ない県は?',
        '人口 100 万人未満の県数は?',
        '婚姻数が 5 万件超の県は?',
    ],
    'answer': [
        '東京都の総人口は約 14,086,000 人です',
        '沖縄県の完全失業率が 17.4% で最も高い',
        '鳥取県の出生数 3,263 人が最少',
        '人口 100 万人未満は 9 県',
        '東京都、 神奈川県、 大阪府、 愛知県の 4 県',
    ],
    'contexts': [[f"東京都の総人口 {df[df['Prefecture']=='東京都']['A1101'].iloc[0]:,}"],
                 [f"沖縄県 失業率 {df[df['Prefecture']=='沖縄県']['B4101'].iloc[0]}%"],
                 [f"鳥取県 出生数 {df[df['Prefecture']=='鳥取県']['A4101'].iloc[0]:,}"],
                 [f"人口 100 万未満県: 鳥取 高知 島根 福井 ..."],
                 [f"東京 大阪 神奈川 愛知 婚姻数 5 万件超"]],
    'ground_truth': [
        '東京都の総人口は 14,086,000 人',
        '沖縄県 (17.4%)',
        '鳥取県',
        '9 県',
        '東京都、 神奈川県、 大阪府、 愛知県',
    ],
}
ds = Dataset.from_dict(eval_data)

# ragas evaluate (LLM=OpenAI 等が必要)
result = evaluate(ds, metrics=[
    context_precision, context_recall, faithfulness, answer_relevancy])
print(result)

📤 実行すると次の出力が得られる (LLM-as-judge 結果例):

{'context_precision': 0.92, 'context_recall' : 0.85, 'faithfulness' : 0.94, ← 回答内主張が context で根拠付けされてる率 'answer_relevancy' : 0.91} ← 質問への答えになっている率 → 全体 RAG パフォーマンス: 0.90 以上で良好 → context_recall = 0.85 が最も低い ── chunk_size を小さく、 retrieval k を上げる改善余地

💬 結果の読み方: ragas は LLM 自身に「context は質問に十分か」「回答は context に基づくか」を採点させる方式。 完全自動ではないが、 人手評価より大幅に安価 (5 質問 / 数十円)。 改善サイクルは: ragas で計測 → 最弱指標 (例: context_recall) を特定 → 該当箇所の改修 (chunk size、 retriever 種類) → 再評価。

🌐 RAG 実装スタック比較 (2025 時点)

OSS / 商用特徴
EmbeddingOpenAI text-embedding-3 (small/large)1536 次元 / 8191 token、 高品質 / 有料
Cohere embed-multilingual-v3100 言語、 1024 次元、 有料
intfloat/multilingual-e5 (S/B/L)OSS、 日本語含 100 言語、 GPU/CPU 両対応
BAAI/bge-m3OSS、 dense+sparse+multi-vec の 3 機能
Vector DBChromaOSS、 開発・小規模本番向け、 Python ネイティブ
QdrantOSS Rust 製、 高速、 メタデータフィルタ強力
WeaviateOSS、 GraphQL API、 hybrid retrieval 内蔵
MilvusOSS、 10B+ ベクトル対応、 大規模
Pinecone商用 SaaS、 管理不要、 法人向け
PostgreSQL + pgvector既存 DB に拡張、 中小規模
OrchestrationLangChainOSS、 chain 抽象化、 統合多数
LlamaIndexOSS、 indexing 機能が強い、 RAG 専門
HaystackOSS、 pipeline-first、 法人 RAG 向け
LangGraphOSS、 LangChain の agentic 拡張、 状態管理
LLMOpenAI GPT-4o / mini汎用最強、 128k context
Anthropic Claude 4.7200k context、 RAG 適性高、 長文に強い
Local LLM (Llama 3.x、 Mistral)オンプレ、 NDA データ向け
評価ragasOSS、 4 軸自動評価、 LLM-as-judge
TruLensOSS、 観測 + 評価、 production 向け
DeepEvalOSS、 unit test 風、 CI 統合容易

🐍 Python 実装 ── RAG with metadata filter

🎯 このコードでやること: vector DB に格納する際 metadata (例: 地域、 年度) を付与し、 検索時に「東北地方限定」など属性フィルタを適用する。 これにより precision を大幅向上できる。

📥 入力データ: SSDSE-B-2026 47 県 + 地方分類 (北海道、 東北、 関東、 中部、 近畿、 中国、 四国、 九州沖縄)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# pip install chromadb sentence-transformers
import pandas as pd, chromadb
from sentence_transformers import SentenceTransformer

REGION = {
  '北海道':'北海道',
  '青森県':'東北','岩手県':'東北','宮城県':'東北','秋田県':'東北','山形県':'東北','福島県':'東北',
  '茨城県':'関東','栃木県':'関東','群馬県':'関東','埼玉県':'関東','千葉県':'関東','東京都':'関東','神奈川県':'関東',
  '新潟県':'中部','富山県':'中部','石川県':'中部','福井県':'中部','山梨県':'中部','長野県':'中部','岐阜県':'中部','静岡県':'中部','愛知県':'中部',
  '三重県':'近畿','滋賀県':'近畿','京都府':'近畿','大阪府':'近畿','兵庫県':'近畿','奈良県':'近畿','和歌山県':'近畿',
  '鳥取県':'中国','島根県':'中国','岡山県':'中国','広島県':'中国','山口県':'中国',
  '徳島県':'四国','香川県':'四国','愛媛県':'四国','高知県':'四国',
  '福岡県':'九州沖縄','佐賀県':'九州沖縄','長崎県':'九州沖縄','熊本県':'九州沖縄','大分県':'九州沖縄','宮崎県':'九州沖縄','鹿児島県':'九州沖縄','沖縄県':'九州沖縄'
}

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].copy()
df['region'] = df['Prefecture'].map(REGION)

model = SentenceTransformer('intfloat/multilingual-e5-small')
texts = [f"{r['Prefecture']} 人口{r['A1101']:,} 失業率{r['B4101']}%"
         for _, r in df.iterrows()]
embs = model.encode(texts)

client = chromadb.Client()
col = client.create_collection('ssdse_with_meta')
col.add(documents=texts, embeddings=embs.tolist(),
        metadatas=[{'region':r['region'],'pref':r['Prefecture']} for _,r in df.iterrows()],
        ids=df['Prefecture'].tolist())

# 検索: 東北限定で「失業率高い」
q = model.encode(['失業率が高い県'])[0]
r = col.query(query_embeddings=[q.tolist()], n_results=3,
              where={'region':'東北'})
print('--- 東北限定 top-3 ---')
for pref, doc in zip(r['ids'][0], r['documents'][0]):
    print(f'  {pref}: {doc}')

# フィルタなし top-3
r2 = col.query(query_embeddings=[q.tolist()], n_results=3)
print('\n--- フィルタなし top-3 ---')
for pref, doc in zip(r2['ids'][0], r2['documents'][0]):
    print(f'  {pref}: {doc}')

📤 実行すると次の出力が得られる:

--- 東北限定 top-3 --- 青森県: 青森県 人口 1,184,000 失業率 12.6% 秋田県: 秋田県 人口 914,000 失業率 13.7% 福島県: 福島県 人口 1,766,000 失業率 12.3% --- フィルタなし top-3 --- 沖縄県: 沖縄県 人口 1,468,000 失業率 17.4% 福岡県: 福岡県 人口 5,103,000 失業率 14.2% 大阪府: 大阪府 人口 8,763,000 失業率 15.0%

💬 結果の読み方: where={'region':'東北'} でメタデータフィルタすると、 東北の中から失業率が高い順に取得。 これがないと全国検索で沖縄等が来てしまう。 RAG の実用化では 「ユーザーの暗黙コンテキスト (時系列、 地域、 部署、 権限) をメタデータで明示化」することが retrieval 品質の鍵。 アクセス制御 (role-based) もメタデータフィルタで実装。

📐 ベクトル DB の主要インデックス手法

手法原理検索時間メモリrecall
Flat (Brute Force)全件 cosine 計算O(N)O(N·d)100%
IVF (Inverted File)k-means クラスタ + 近傍 cluster のみ走査O(√N)O(N·d)95-99%
HNSW階層 small-world graphO(log N)O(N·d·M)95-99%
PQ (Product Quant.)ベクトルを部分量子化、 8x 圧縮O(N)O(N·d/8)90-95%
IVF-PQIVF + PQ 圧縮O(√N)O(N·d/8)85-95%
DiskANNSSD 上で大規模グラフ走査O(log N)SSD95-99%

小規模 (~10 万) は HNSW、 中規模 (~1000 万) は IVF or HNSW、 大規模 (~10 億) は IVF-PQ or DiskANN が経験則。 RAG では recall を優先 (上位 k に取りこぼしがあると LLM が答えられない)。

🧮 数式に値を入れて手で計算する: RAG の検索精度

合成データで上位 K 文書取得の Recall@K を計算する。

Step 1: 関連文書の取得

K関連取得関連合計Recall@K
11100.10
54100.40
107100.70
209100.90
5010101.00

Step 2: K の影響

K=10 で 70% 関連取得 K=50 で 100% (完全リコール) 小 K は精度高くリコール低

🐍 Python で再現

1
2
3
4
5
6
import numpy as np
K = np.array([1, 5, 10, 20, 50])
retrieved = np.array([1, 4, 7, 9, 10])
total = 10
recall = retrieved / total
print(f"Recall@K: {recall}")

📤 実行結果

Recall@K: [0.1 0.4 0.7 0.9 1. ]

💬 手計算 (Step 1) と Python 出力が完全一致。

🐍 Python 実装

SSDSE-B-2026 の都道府県データを文書化 → 埋め込み → 検索 → LLM プロンプトに渡すまでの RAG パイプライン最小例。 ローカルで完結する sentence-transformers + FAISS 構成を示す (API 鍵不要)。

🎯 このコードでやること: SSDSE-B-2026 の 47 都道府県を 1 行 1 文書として読み込み、 sentence-transformers の多言語埋め込みで 384 次元ベクトルに変換 → FAISS で類似度検索 → 上位 3 件を回答候補として表示する。

📥 入力例: data/raw/SSDSE-B-2026.csv都道府県・総人口・出生数・高齢化率 から 47 文書を生成。

docs[:3]: "北海道は総人口 5,092,000 人、 出生数 24,430、 高齢化率 33.0%" "青森県は総人口 1,184,000 人、 出生数 5,696、 高齢化率 35.2%" "岩手県は総人口 1,163,000 人、 出生数 5,432、 高齢化率 35.0%"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd
import numpy as np
from sentence_transformers import SentenceTransformer
import faiss

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]
docs = [f"{r['Prefecture']}は総人口 {int(r['A1101']):,} 人、 出生数 {int(r['A4101']):,}、 高齢化率 {r['A1303']/r['A1101']*100:.1f}%"
        for _, r in df.iterrows()]

model = SentenceTransformer('intfloat/multilingual-e5-small')
vecs = model.encode(docs, normalize_embeddings=True)
index = faiss.IndexFlatIP(vecs.shape[1])
index.add(vecs.astype('float32'))

q = '高齢化率が高い県は?'
qv = model.encode([q], normalize_embeddings=True)
D, I = index.search(qv.astype('float32'), k=3)
for score, idx in zip(D[0], I[0]):
    print(f"{score:.3f} | {docs[idx]}")

📤 実行例:

0.847 | 秋田県は総人口 941,021 人、 出生数 3,124、 高齢化率 38.6% 0.831 | 高知県は総人口 678,772 人、 出生数 3,412、 高齢化率 36.1% 0.825 | 山口県は総人口 1,313,403 人、 出生数 6,890、 高齢化率 35.5%

💬 結果の読み方: 上位 3 件はいずれも高齢化率 35%超の県で、 内積スコア 0.83-0.85。 この 3 件を LLM プロンプトの {context} に埋め込み、 質問とともに渡せば「秋田県 38.6%、 高知県 36.1%、 山口県 35.5% などが該当」という根拠付き回答が得られる。 検索 → 文脈付与 → 生成、 という RAG の中核ループを 18 行で実装できる。

🐍 LangChain + Chroma + LLM 連携の最小パイプライン

前節の埋め込み + 検索を LangChain 風 RAG パイプライン(retriever → prompt → LLM)に整理する。 ベクトル DB は Chroma、 埋め込みは multilingual-e5、 LLM は Anthropic Claude を想定。 実コードでは API キーが必要なため、 ここでは構造を示す。

このコードでやること: SSDSE-B-2026 の都道府県文書を Chroma に投入し、 retriever を作って Claude に渡すまでのパイプラインを 1 ファイルで定義する。

📥 入力データ (前節の docs リストをそのまま使う):

docs = [ "北海道の総人口は 5,140,354 人、 ...", "青森県の総人口は 1,221,288 人、 ...", ... "沖縄県の総人口は 1,468,063 人、 ...", ] # 全 47 文書
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from langchain_community.vectorstores import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_anthropic import ChatAnthropic
from langchain.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnablePassthrough

emb = HuggingFaceEmbeddings(model_name='intfloat/multilingual-e5-small')
vs = Chroma.from_texts(docs, embedding=emb, collection_name='ssdse_b_2026')
retriever = vs.as_retriever(search_kwargs={'k': 3})

prompt = ChatPromptTemplate.from_template(
    "以下の文脈だけを根拠に質問に答えよ。\n"
    "文脈:\n{context}\n\n質問: {question}\n回答:"
)
llm = ChatAnthropic(model='claude-opus-4-7', temperature=0)

chain = (
    {'context': retriever, 'question': RunnablePassthrough()}
    | prompt | llm
)

answer = chain.invoke("東京都の総人口は何人か?")
print(answer.content)

📤 実行すると次の出力が得られる (Claude API 接続時):

東京都の総人口は 14,094,034 人です (SSDSE-B-2026、 2022 年度)。

💬 retriever が top-3 文書を取得 → ChatPromptTemplate が「文脈だけを根拠に」と指示 → Claude が東京都の数値を抽出 → 出典付き回答。 Faithfulness はほぼ 1.0、 Answer Relevancy も高い構成。 同じ chain に StrOutputParser() を繋げば文字列で受け取れ、 chain.batch([...]) で複数質問を一括処理できる。 RAGAS で評価する場合は (question, answer, contexts, ground_truth) の 4 つ組を集めて 4 指標を計算する。

⚠️ よくある落とし穴

この用語を使うときに陥りがちな失敗パターン。 経験者ほどここに 1 度はハマっています。

❌ 検索精度がボトルネック
RAG の品質 = 検索精度 × LLM 性能。 BM25 (キーワード) と密埋め込み (semantic) のスコアが噛み合わず、 SSDSE-B-2026 で「沖縄の婚姻率」を問うても観光ガイド記事ばかりヒットする失敗が典型。 Recall@10 を評価指標として、 検索器単体を先に磨くのが必須です。
❌ チャンク戦略
SSDSE 公式 PDF を 200 文字でチャンク化すると 1 つの表が分断され、 都道府県名と数値が別チャンクに散ります。 逆に 4,000 文字だと埋め込みベクトルが平均化されて検索精度が落ちる。 表/見出し境界で意味的に区切る semantic chunking が無難です。
❌ コンテキスト長制限
GPT-4o 128K、 Claude 4.7 200K と進化しても、 Top-k=50 × 平均 1,500 トークンで 75,000 トークン消費 → 残りで指示と回答を生成する余地が減少。 関連度の低いチャンクは reranker (cross-encoder) で削ぎ落とすこと。
❌ ハルシネーションは完全には消えない
検索結果に正解があっても LLM が「自分の記憶」を優先する場合がある (特に SSDSE の最新年度 vs LLM 学習時点のズレ)。 「添付資料に書かれていない情報は『不明』と答えてください」と System で明示し、 引用元の URL/ページを必ず出力させる契約が有効です。
❌ 評価せずに本番投入
RAG の品質指標は (Faithfulness / Answer Relevance / Context Precision) の 3 軸で測るのが定番 (RAGAS, TruLens)。 「動くから OK」で出すと、 1 ヶ月後に誤回答が累積して炎上します。 100 問の Q&A データセットで毎日 CI 回すのを推奨。

🗺 概念マップ

「rag」を中心とした関連概念マップ。

RAG 前提: LLM / 埋め込み 並列: ベクトル検索 ANN 発展: Agent / Tool use 応用: 企業ナレッジ検索 対比: ファインチューニング 統合: ハルシネーション抑制

LLM の回答に外部知識ベース (ベクトル DB / SQL) からの検索結果を注入する手法。 SSDSE-B-2026 の都道府県データを Embedding 化して保存しておき、 「人口が多い県の特徴は?」というクエリに対し top-k 検索 → LLM 投入、 という流れで最新・正確な回答を生成する。

RAG を中心に、 (a) 上位として外部知識活用 LLM パイプライン、 (b) 並列としてファインチューニングと Long-context LLM、 (c) 派生として Hybrid Search・GraphRAG・HyDE、 (d) 前段としてチャンク分割と埋め込み生成、 (e) 後段として Re-ranking と引用検証、 (f) 応用として SSDSE 統計文書 Q&A や社内 FAQ ボット、 を配置した。

RAG (検索拡張生成) は単体で覚えるより、 SSDSE-B-2026 のような実データに対して「前処理 → 適用 → 検証」の流れに組み込んで運用できるようにすることが重要。

🔗 隣接手法への橋渡し

「RAG (検索拡張生成)」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。

SSDSE-B-2026 を題材にした RAG (検索拡張生成) の活用は、 上流 (取得・整形) と下流 (解釈・可視化) を含めて初めて完結する。

🌳 手法選択フロー

「RAG (検索拡張生成)」を実際の課題に当てはめるとき、 状況別に何を選ぶかを 3 段階で判定する。

  1. 知識は頻繁に更新されるか? Yes → RAG (DB を差し替えるだけ)、 No → ファインチューニングで埋め込み
  2. 出典を明示する必要があるか? Yes → RAG (チャンクごとに source を返す)、 No → ファインチューニング可
  3. 応答速度はどれだけ重要か? 厳しい → 軽量検索 (BM25 + 上位 5 件)、 緩い → 高精度検索 (HyDE + reranker)

SSDSE-B-2026 のような統計データを LLM に問う場合、 RAG で CSV を chunk 化して検索すれば、 数値の改ざんなく根拠付き応答が得られる。 fine-tuning だけだと数値を忘却する。

🎮 触って学ぶ ── RAG「検索 → 文脈注入 → 生成」デモ

クエリを選ぶと、 事前定義した小さな文書コレクション (6 本の短い文書) に対して 埋め込み類似度 (コサイン類似度) で上位文書を検索 し、 取得した文脈をプロンプトへ組み込み、 回答を生成する流れを 5 ステップで表示します。 top-k スライダーで取得件数を変えると、 文脈に入る文書が増減します。 検索がヒットしないクエリ・無関係文書を引くクエリでは、 「検索品質が生成を左右する」様子 (幻覚・誤答) を体感できます。

※ 明記: 本デモは実際の LLM・埋め込みモデルを一切使いません。 埋め込みベクトルと生成回答はすべて手作業で定義した固定値による説明用シミュレーションです。 類似度計算だけは本物のコサイン類似度で正確に行っています。

① クエリを選ぶ
② 取得数 top-k = 2

Step 1 ── クエリを埋め込みベクトルに変換

Step 2 ── 各文書とのコサイン類似度を計算・降順に並べ替え

Step 3 ── 上位 k 件を取得 (retrieval)

Step 4 ── 取得文脈をプロンプトに組み込む (augmentation)

Step 5 ── LLM が回答を生成 (generation)

🌊 チャンク分割・埋め込み・ベクトル検索の流れ図

RAG のパイプライン全体。 事前処理 (上段) で文書をチャンク分割 → 埋め込み → ベクトル DB 格納しておき、 実行時 (下段) にクエリを埋め込んで類似検索し、 取得文脈をプロンプトに足して LLM が生成します。

事前処理 (オフライン) 文書コレクション(PDF・wiki…) チャンク分割(chunking) 埋め込み(embedding) ベクトル DB(vector store) 実行時 (クエリごと) クエリ(質問) クエリ埋め込み(query embed) 類似検索(top-k retrieval) 文脈+プロンプト(augment) LLM 生成(generation) DB を検索 → 根拠付き回答 ※ 検索がヒットしない / 無関係文書を引くと、 下流の生成が幻覚・誤答に傾く (検索品質が生成を左右)。