RAG(Retrieval-Augmented Generation)

外部知識を「検索 → 取得 → context に挿入 → LLM に答えさせる」手法。 モデルが知らない最新情報や社内ドキュメントを扱える。

そもそもなぜ必要か

基本フロー

[ユーザー質問]
   ↓
[Embedding 化]
   ↓
[ベクトル DB 検索] ──→ 上位 N 件の関連チャンク
   ↓
[LLM プロンプトに挿入]
   ↓
[LLM が回答]
      

1. ドキュメント取り込み(Ingestion)

  1. ソース取得(PDF / HTML / Notion / Google Drive...)
  2. テキスト抽出(PDF → text)
  3. チャンク分割(500〜1500 トークン目安)
  4. Embedding 化
  5. ベクトル DB に保存

2. Embedding

テキストを固定次元の数値ベクトルに変換。意味が近いもの同士が空間上で近くなる。

主要モデル

import { OpenAI } from "openai"
const openai = new OpenAI()

const res = await openai.embeddings.create({
  model: "text-embedding-3-small",
  input: "テキスト"
})
const vector = res.data[0].embedding // number[1536]

3. ベクトル DB

選択肢特徴
Postgres + pgvector既存 RDB に追加、運用シンプル
Pineconeマネージド、本番向け
QdrantOSS / Rust 製、高速
WeaviateOSS、ハイブリッド検索
ChromaOSS、ローカル開発に
Cloudflare Vectorizeエッジで動く
Turbopufferサーバーレス、安価

pgvector の例

CREATE EXTENSION vector;

CREATE TABLE chunks (
  id bigserial PRIMARY KEY,
  content text,
  embedding vector(1536),
  metadata jsonb
);

CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops);

-- 類似検索
SELECT content, 1 - (embedding <=> $1) AS score
FROM chunks
ORDER BY embedding <=> $1
LIMIT 5;

4. チャンク分割の戦略

コードや表は独自に分割ロジックを組む。Markdown は見出しで分割すると相性が良い。

5. 検索(Retrieval)

類似検索

コサイン類似度や内積で近いベクトル N 件を取る。

ハイブリッド検索

Embedding(意味検索)+ BM25(全文検索)を併用。 固有名詞や型番は BM25 が強い。

Reranking

Top 50 を取得 → Cross-Encoder(Cohere Rerank / Voyage Rerank)で精緻に再ランキング → Top 5 を context に。

6. プロンプトに挿入

const chunks = await searchVectorDB(query)
const context = chunks.map(c => `[出典: ${c.source}]\n${c.content}`).join("\n\n---\n\n")

await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  system: `次のドキュメントを根拠に質問に答えてください。
根拠が見つからない場合は "わかりません" と答えてください。
回答には必ず [出典] を引用してください。

<documents>
${context}
</documents>`,
  messages: [{ role: "user", content: query }]
})

引用と Hallucination 防止

Anthropic Citations

Claude にドキュメントを渡すとき citations: { enabled: true } を有効化すると、 回答中に引用箇所をモデルが付与してくれる。

Contextual Retrieval(Anthropic 提案)

各チャンクに「このチャンクが文書全体のどの位置・話題に属するか」の50〜100 トークンの説明を Embedding 前に LLM で付与する。 従来 RAG より失敗率が大幅に低下すると Anthropic がブログで報告。

クエリの拡張

メタデータ・フィルタ

ベクトル検索 + 構造化フィルタの併用。「2025 年以降の文書だけ」「営業資料だけ」など。

RAG vs Long Context

Claude Opus 1M の登場で「全部 context に放り込めばいいのでは?」と思いがち。実態:

しかし小規模 (< 200K)かつ毎回違うドキュメントなら全文投入 + Prompt Caching が手軽で速い。

評価指標

失敗パターン

症状原因対処
関連ない情報が拾われるチャンクが大きすぎ / 小さすぎサイズ調整、Reranker
固有名詞が引けないEmbedding は語彙に弱いBM25 ハイブリッド
回答が context を無視System Prompt 弱い「context のみで答えよ」を強調
更新が反映されないキャッシュ / 再 Embedding 漏れID で upsert
表が壊れるテキスト抽出時に崩れるMarkdown 変換 / OCR + 構造化

進化系: Agentic RAG

固定パイプラインではなく、エージェントが「検索すべきか / どのクエリで検索するか / さらに深掘りするか」を判断する。 Tool Use で search ツールを与えるだけで自然と Agentic RAG になる。

最初はシンプルに

いきなりハイブリッド検索 + Reranker + HyDE をやらない。 「pgvector で類似検索 → context に投入」だけで 80% は機能する。失敗ケースを集めてから精緻化。