RAG・実装参考

RAG(検索拡張生成)の設計・実装参考。

検証日は各節の badge を参照。

RAG・ベクトルDB 実装

検索拡張生成(RAG)の設計から実装まで。埋め込み・ベクトル検索・チャンキング戦略・Hybrid Search・評価まで網羅。

RAGパイプライン全体像

① Indexing(事前処理)
ドキュメントを読み込み → チャンク分割 → Embedding モデルでベクトル化 → ベクトルDBに保存。バッチで定期実行する。
技術選定
Loader: LangChain DocumentLoaders
Chunker: RecursiveCharacterTextSplitter
Embedding: text-embedding-3-small (OpenAI)
voyage-3 (Anthropic推奨)
DB: pgvector / Pinecone / Qdrant
② Retrieval(検索)
ユーザークエリをベクトル化 → コサイン類似度でTop-K検索 → BM25と組み合わせる Hybrid Search → Reranker で精度向上。
精度向上策
・Hybrid: Vector + BM25 (0.7/0.3)
・Reranker: Cohere Rerank / bge-reranker
・HyDE: 仮の回答でベクトル生成して検索
・MMR: 多様性を保った検索結果選択
③ Generation(生成)
取得したチャンクをコンテキストとしてLLMに渡す。ソース引用・不確実性の表現・ハルシネーション抑制プロンプトが重要。
プロンプト構造
System: 必ず提供コンテキストのみから回答。
不明な場合は「情報がありません」と答えよ
Context: {retrieved_chunks}
User: {question}
RAG 全体(事前 Indexing と質問時 Retrieval/Generation)
flowchart TB subgraph offline["Indexing 事前"] A[Load docs] --> B[Chunk] B --> C[Embed] C --> D[(Vector DB)] end subgraph online["Query 質問時"] E[User query] --> F[Hybrid Search] F --> G[Rerank] G --> H[LLM] D -.-> F end
質問 1 回のシーケンス(Hybrid + Rerank + 生成)
sequenceDiagram participant U as User participant App as App participant Emb as Embedding participant DB as Vector DB participant RR as Reranker participant LLM as LLM U->>App: question App->>Emb: query vector Emb-->>App: vector App->>DB: vector plus BM25 DB-->>App: top-K chunks App->>RR: rerank RR-->>App: top-3 App->>LLM: context and question LLM-->>App: answer App-->>U: answer and sources
document_chunks(pgvector の最小スキーマ)
erDiagram document_chunks { uuid id PK text content vector embedding jsonb metadata text collection timestamptz created_at }

Agentic RAG(発展)

Naive RAG は質問ごとに固定フロー(retrieve → generate)で動く。Agentic RAG は LLM が「検索が必要か」「取得結果は十分か」「クエリを書き換えるか」を判断し、ループしながら回答する。多段推論・鮮度・出典検証が必要なときに有効。導入方針は flow-11 RAG 向き不向き、マルチエージェント実装は Tool Use 参考 — マルチエージェント

Router / Adaptive RAG
LLM が質問を分類し、検索が必要かを先に判断してから retrieve する。挨拶・計算・既知の一般知識は検索をスキップし、latency とコストを抑える。
分岐例
「こんにちは」→ 直接回答
「ADR-0003 の決定理由は?」→ Vector DB 検索
「昨日の売上は?」→ SQL / API ツール
Self-RAG / CRAG
取得したチャンクを grade(関連性・十分性)し、不十分なら query rewrite や web fallback で再検索する。Corrective RAG(CRAG)は外部検索へのフォールバックが特徴。
ループ
retrieve → grade → OK? → generate
↓ NG
rewrite query → re-retrieve
Query decomposition
複雑な質問をサブクエリに分解し、並列検索してから統合する。「A と B の違いは?」「3社を比較して」など多要素質問向き。
分解例
「React と Vue の SSR 比較」
→ [React SSR], [Vue SSR], [比較観点]
→ 各結果を統合して回答
Naive RAG と Agentic RAG の違い
flowchart TB subgraph naive["Naive RAG"] NQ[User query] --> NR[Retrieve fixed] NR --> NG[Generate] end subgraph agentic["Agentic RAG"] AQ[User query] --> AR{Need search?} AR -->|No| AG[Generate direct] AR -->|Yes| AV[Retrieve] AV --> GR{Grade docs} GR -->|OK| AG2[Generate with context] GR -->|NG| RW[Rewrite query] RW --> AV end
状況 推奨 理由
単一ドメイン FAQ・社内規程 Q&A Naive RAG(Hybrid + Rerank) フローが単純で十分。Agentic は過剰
多段推論・比較・複合質問 Query decomposition + Agentic 1回の retrieve では文脈が足りない
検索結果の品質が不安定 Self-RAG / CRAG grade と rewrite で精度を自己修正
挨拶・雑談とナレッジ検索が混在 Router / Adaptive RAG 不要な retrieval を避けコスト削減
Self-RAG 風パイプライン(LangGraph StateGraph)
from typing import Literal, TypedDict
from langgraph.graph import StateGraph, END

class RAGState(TypedDict):
    question: str
    query: str
    documents: list[str]
    grade: str          # "relevant" | "irrelevant"
    generation: str
    retries: int

def retrieve(state: RAGState) -> RAGState:
    # 既存 Hybrid Search を呼ぶ(上記「Hybrid Search」タブ参照)
    docs = hybrid_search(state["query"], top_k=5)
    return {**state, "documents": docs}

def grade_documents(state: RAGState) -> RAGState:
    # LLM に関連性を判定させる(structured output 推奨)
    verdict = llm_grade(state["question"], state["documents"])
    return {**state, "grade": verdict}

def rewrite_query(state: RAGState) -> RAGState:
    new_q = llm_rewrite(state["question"], state["documents"])
    return {**state, "query": new_q, "retries": state["retries"] + 1}

def generate(state: RAGState) -> RAGState:
    answer = llm_generate(state["question"], state["documents"])
    return {**state, "generation": answer}

def route_after_grade(state: RAGState) -> Literal["generate", "rewrite", "end"]:
    if state["grade"] == "relevant":
        return "generate"
    if state["retries"] >= 2:
        return "end"  # 打ち切り — 「情報がありません」
    return "rewrite"

graph = StateGraph(RAGState)
graph.add_node("retrieve", retrieve)
graph.add_node("grade", grade_documents)
graph.add_node("rewrite", rewrite_query)
graph.add_node("generate", generate)
graph.set_entry_point("retrieve")
graph.add_edge("retrieve", "grade")
graph.add_conditional_edges("grade", route_after_grade, {
    "generate": "generate", "rewrite": "rewrite", "end": END,
})
graph.add_edge("rewrite", "retrieve")
graph.add_edge("generate", END)
app = graph.compile()
💡 上の retrieve / hybrid_search は本ページ「実装コード — Hybrid Search」タブの関数をそのまま使う。Agentic は検索基盤が安定してから足す。
Router + RAG ツール(Anthropic Tool Use)
import anthropic

client = anthropic.Anthropic()

tools = [
    {
        "name": "search_knowledge_base",
        "description": "社内ドキュメント・ADR・仕様書を検索する。固有名詞・手順・ポリシーに関する質問で使う。挨拶や一般常識には使わない。",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "検索クエリ(日本語可)"},
                "collection": {"type": "string", "enum": ["docs", "adr", "runbook"]},
            },
            "required": ["query"],
        },
    },
]

def run_agentic_rag(question: str, max_iter: int = 5) -> str:
    messages = [{"role": "user", "content": question}]
    for _ in range(max_iter):
        resp = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=4096,
            system="必要なときだけ search_knowledge_base を呼ぶ。検索結果のみから回答し、不明ならそう述べる。",
            tools=tools,
            messages=messages,
        )
        if resp.stop_reason == "end_turn":
            return next(b.text for b in resp.content if hasattr(b, "text"))
        tool_results = []
        for block in resp.content:
            if block.type == "tool_use":
                chunks = hybrid_search(block.input["query"])  # 既存 RAG 関数
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": format_chunks(chunks),
                })
        messages.append({"role": "assistant", "content": resp.content})
        messages.append({"role": "user", "content": tool_results})
    return "最大イテレーションに達しました"
💡 LLM が検索要否を判断する最小 Agentic RAG。Self-RAG 相当の grade は tool_result に「関連チャンクなし」を返し、LLM に再検索を促す形でも実装できる。
よくある失敗と対策
失敗パターン 対策
❌ retrieval ループが止まらない grade が常に NG になり rewrite → retrieve を繰り返し、latency / コストが爆増 ✅ 対策 max_retries(2〜3)で打ち切り。grade は structured output + 閾値固定。LangSmith / Langfuse でトレース
❌ Agentic にしたが精度が上がらない chunking・Hybrid Search・Reranker が未調整のままループだけ増やしている ✅ 対策 まず Naive RAG の faithfulness を RAGAS で 0.85 以上に。Agentic はその上の改善策
❌ Router が検索をスキップしすぎる 社内固有名詞の質問でも「一般知識」と誤判定 ✅ 対策 Router プロンプトに「社内用語・ADR・Runbook は必ず search」を明示。迷ったら検索側に倒す
Agentic RAG を AI に依頼するプロンプト例
Self-RAG パイプライン
LangGraph で Self-RAG パイプラインを実装してください。
・retrieve → grade → generate / rewrite の条件分岐
・grade は structured output(relevant / irrelevant)
・max_retries=2、打ち切り時は「情報がありません」
・retrieve は既存 hybrid_search 関数を利用
Router 付き RAG
Anthropic Tool Use で Router 付き RAG を実装してください。
・search_knowledge_base ツールは社内 docs/adr/runbook のみ
・挨拶・計算・一般常識はツールを呼ばず直接回答
・検索結果のみから回答、出典を末尾に列挙

実装コード

ドキュメントIndexing パイプライン(Python)
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import PGVector
import os

# ── 1. ドキュメントロード ────────────────────────────────────
def load_documents(source_dir: str):
    loader = DirectoryLoader(
        source_dir,
        glob="**/*.pdf",
        loader_cls=PyPDFLoader,
        show_progress=True,
    )
    return loader.load()

# ── 2. チャンキング戦略 ──────────────────────────────────────
# chunk_size と overlap はドメインによって要調整
# 技術文書: 1000-1500 / FAQ: 300-500 / コード: 500-800
def chunk_documents(docs, chunk_size=1000, chunk_overlap=200):
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        separators=["

", "
", "。", ".", " ", ""],  # 日本語対応
        length_function=len,
    )
    chunks = splitter.split_documents(docs)
    print(f"  {len(docs)} docs → {len(chunks)} chunks")
    return chunks

# ── 3. Embeddingモデル選択 ───────────────────────────────────
# OpenAI text-embedding-3-small: コスト安・高品質
# Voyage voyage-3: Anthropic推奨・多言語強い
# BGE-M3: OSS・日本語強い・セルフホスト可
embedding_model = OpenAIEmbeddings(
    model="text-embedding-3-small",
    dimensions=1536,
)

# ── 4. pgvector に保存 ───────────────────────────────────────
CONNECTION_STRING = os.getenv("DATABASE_URL")
COLLECTION_NAME = "knowledge_base"

def index_documents(source_dir: str):
    docs   = load_documents(source_dir)
    chunks = chunk_documents(docs)

    # バッチでUpsert(大量データは100件ずつ)
    vectorstore = PGVector.from_documents(
        documents=chunks,
        embedding=embedding_model,
        collection_name=COLLECTION_NAME,
        connection_string=CONNECTION_STRING,
        pre_delete_collection=False,  # 差分更新
    )
    print(f"✅ Indexed {len(chunks)} chunks to pgvector")
    return vectorstore

# ── 5. メタデータフィルタリング ──────────────────────────────
# ソース・日付・カテゴリなどでフィルタリングして精度を上げる
def search_with_filter(query: str, category: str = None):
    vectorstore = PGVector(
        collection_name=COLLECTION_NAME,
        connection_string=CONNECTION_STRING,
        embedding_function=embedding_model,
    )

    filter_dict = {}
    if category:
        filter_dict["category"] = category

    results = vectorstore.similarity_search_with_score(
        query, k=5, filter=filter_dict
    )

    return [
        {"content": doc.page_content, "source": doc.metadata.get("source"), "score": score}
        for doc, score in results
        if score > 0.75  # 類似度スコアでフィルタリング
    ]
💡 チャンキング戦略のポイント:① 親チャンク(大)で文脈を保持し、子チャンク(小)で精度よく検索する「Parent Document Retriever」が効果的 ② 日本語は separators に「。」を追加 ③ chunk_overlap で文脈の途切れを防ぐ
pgvector セットアップ(PostgreSQL)
-- ── PostgreSQL に pgvector 拡張をインストール ──────────────
CREATE EXTENSION IF NOT EXISTS vector;

-- ── ドキュメントチャンクテーブル ─────────────────────────────
CREATE TABLE document_chunks (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    content     TEXT NOT NULL,
    embedding   VECTOR(1536),            -- text-embedding-3-small の次元数
    metadata    JSONB DEFAULT '{}',      -- source, page, category など
    collection  TEXT NOT NULL DEFAULT 'default',
    created_at  TIMESTAMPTZ DEFAULT NOW()
);

-- ── インデックス(近似最近傍探索)──────────────────────────────
-- IVFFlat: 高速だが精度は近似(大量データに)
CREATE INDEX ON document_chunks
    USING ivfflat (embedding vector_cosine_ops)
    WITH (lists = 100);  -- lists = sqrt(行数) が目安

-- HNSW: より高精度・メモリ使用量大(中規模に)
-- CREATE INDEX ON document_chunks
--     USING hnsw (embedding vector_cosine_ops)
--     WITH (m = 16, ef_construction = 64);

-- ── 検索クエリ ────────────────────────────────────────────────
-- コサイン類似度でTop-5取得
SELECT
    id,
    content,
    metadata->>'source' AS source,
    1 - (embedding <=> $1::vector) AS similarity  -- <=> はコサイン距離
FROM document_chunks
WHERE collection = 'knowledge_base'
    AND metadata->>'category' = 'technical'       -- メタデータフィルタ
ORDER BY embedding <=> $1::vector                -- コサイン距離でソート
LIMIT 5;

-- ── メタデータフィルタ付きHybrid(pgvector + tsvector)─────
SELECT
    id, content, metadata->>'source' AS source,
    1 - (embedding <=> $1::vector) AS vector_score,
    ts_rank(to_tsvector('japanese', content), query) AS bm25_score
FROM document_chunks,
     to_tsquery('japanese', $2) query
WHERE to_tsvector('japanese', content) @@ query
    OR (embedding <=> $1::vector) < 0.3
ORDER BY
    (1 - (embedding <=> $1::vector)) * 0.7 +
    ts_rank(to_tsvector('japanese', content), query) * 0.3 DESC
LIMIT 10;
💡 pgvector の選択基準:① 既存 PostgreSQL があれば追加コストゼロ ② 〜100万件規模なら IVFFlat インデックスで十分 ③ 100万件超・低レイテンシ必須なら Pinecone/Qdrant を検討 ④ Supabase は pgvector をマネージドで提供している
RAG評価(RAGAS)
from ragas import evaluate
from ragas.metrics import (
    faithfulness,          # 回答がコンテキストに忠実か(ハルシネーション検出)
    answer_relevancy,      # 回答が質問に関連しているか
    context_precision,     # 取得コンテキストの精度
    context_recall,        # 必要な情報を取得できているか
)
from datasets import Dataset

# ── 評価データセット準備 ─────────────────────────────────────
eval_data = {
    "question": [
        "有給休暇の申請方法を教えてください",
        "リモートワーク手当の条件は何ですか",
    ],
    "answer": [
        "有給休暇は社内システム HRCore から申請...",  # RAGの回答
        "リモートワーク手当は月額5000円で...",
    ],
    "contexts": [
        ["就業規則第10条:有給休暇は...", "申請手順: 1. HRCoreにログイン..."],
        ["リモートワーク規程: 月額5,000円を支給..."],
    ],
    "ground_truth": [
        "HRCore システムから申請し、上長承認後に取得できます",  # 正解
        "月額5,000円、週3日以上のリモート勤務が条件です",
    ],
}

dataset = Dataset.from_dict(eval_data)

# ── 評価実行 ─────────────────────────────────────────────────
results = evaluate(
    dataset=dataset,
    metrics=[faithfulness, answer_relevancy, context_precision, context_recall],
)

print(results)
# faithfulness:      0.92  ← 1.0に近いほどハルシネーションなし
# answer_relevancy:  0.88  ← 1.0に近いほど質問に的確
# context_precision: 0.85  ← 取得コンテキストのノイズが少ない
# context_recall:    0.90  ← 必要な情報を取り逃していない

# ── 継続的評価(CI/CDに組み込む) ───────────────────────────
# GitHub Actions でRAG変更時に自動評価
# 閾値を下回ったら PR をブロックする
THRESHOLDS = {
    "faithfulness": 0.85,
    "answer_relevancy": 0.80,
    "context_precision": 0.75,
    "context_recall": 0.80,
}

for metric, threshold in THRESHOLDS.items():
    score = results[metric]
    status = "✅" if score >= threshold else "❌"
    print(f"{status} {metric}: {score:.2f} (threshold: {threshold})")
💡 RAGASの4指標:① faithfulness(最重要)= ハルシネーションがないか ② answer_relevancy = 質問ズレがないか ③ context_precision = 無関係なチャンクを取ってないか ④ context_recall = 必要な情報を取り逃していないか。週次でモニタリングして劣化を検知する。

AIへの指示パターン

RAG実装をAIに依頼するプロンプト例
チャンク最適化
以下のドキュメント種別に最適なチャンキング戦略を提案してください。
① 法律文書(条文・定義が多い) ② Markdownの技術ドキュメント ③ 会議議事録
各ドキュメントについて chunk_size・overlap・separator・メタデータ設計を示してください。
Hybrid Search実装
pgvector を使った Hybrid Search(ベクトル検索 + tsvector全文検索)を実装してください。
・Python(SQLAlchemy + asyncpg)
・重み付きスコアリング(ベクトル70% + BM25 30%)
・日本語形態素解析(pgroonga または pg_bigm)対応

RAGで成し遂げたいこと — ユースケース別設計

RAGは「LLMが知らない情報を使って回答させる」技術だが、ユースケースによって何を重視するか(精度・速度・鮮度・コスト)が大きく異なる。目的を明確にしてから設計する。

社内ナレッジBotのイメージ

社内ナレッジBot — 「就業規則・制度・手順書を自然言語で検索したい」

成し遂げたいこと

  • 就業規則・社内規程を「有給はいつから取れる?」のように質問して即答
  • ソース(条文番号・ページ)付きで回答し、担当者への問い合わせを70%削減
  • 情報が古くなったら差分だけ再インデックス

設計のポイント

  • チャンクサイズ 500〜800 token(条文単位で区切る)
  • メタデータに 規程名・改定日・カテゴリ を付与
  • Faithfulness(ハルシネーション検出)を最重要指標に
  • 「情報がない場合は答えない」プロンプトが必須

技術スタック例(コスト重視)

flowchart LR PDF --> Loader --> Embed --> DB[(pgvector)] DB --> LLM[Claude haiku]

PDF → LangChain PyPDFLoader → text-embedding-3-small → pgvector(Supabase)→ Claude claude-haiku-4-5

カスタマーサポート自動化のイメージ

カスタマーサポート自動化 — 「FAQと過去チケットから即座に回答案を生成したい」

成し遂げたいこと

  • 新着チケットに対して過去の解決済みチケットと FAQ から回答案を自動生成
  • オペレーターが「送信 or 編集」を選ぶだけにして処理時間を50%短縮
  • 製品バージョン・エラーコードなどのキーワードを確実にヒットさせる

設計のポイント

  • Hybrid Search 必須:エラーコード・型番はBM25、意味検索はVector
  • 製品バージョンでメタデータフィルタを掛けて古い情報を除外
  • チャンクサイズ 300〜500(FAQ は Q&A 単位で区切る)
  • レイテンシ優先のため Reranker は軽量モデルを選択

技術スタック例(品質重視)

flowchart LR CSV --> Chunk --> Embed --> DB[(Qdrant)] DB --> Rerank --> LLM[Claude sonnet]

Zendesk CSV → チャンキング → voyage-3(多言語) → Qdrant → Cohere Rerank → Claude claude-sonnet-4-6

コードベース検索のイメージ

コードベース理解・検索 — 「20万行のコードを自然言語で検索・説明させたい」

成し遂げたいこと

  • 「認証周りの処理はどのファイルにある?」を即座に特定
  • 新メンバーのオンボーディング時間を短縮(コードを読んで説明)
  • PR レビュー時に「同じパターンが他にあるか」を検索

設計のポイント

  • チャンクは関数・クラス単位で区切る(行数ではなくAST解析)
  • メタデータに ファイルパス・言語・モジュール名・関数名 を付与
  • コード用 Embedding モデル(voyage-code-3)が汎用より精度高
  • Git の差分更新で変更ファイルだけ再インデックス

技術スタック例

flowchart LR AST[tree-sitter] --> Embed --> DB[(pgvector)] DB --> Agent[Claude Code]

tree-sitter(AST分割)→ voyage-code-3 → pgvector → Claude Code(コンテキストとして渡す)

リアルタイム情報RAGのイメージ

リアルタイム情報への対応 — 「常に最新の情報で回答させたい(ニュース・株価・仕様)」

成し遂げたいこと

  • ニュース記事・製品仕様書が更新されたら即座にインデックスへ反映
  • 古いチャンクが検索に出てこないよう鮮度管理
  • 「この情報はいつ時点のものか」を回答に必ず含める

設計のポイント

  • メタデータに published_at を付与し、検索時に日付フィルタ
  • Incremental Indexing:変更差分のみ再ベクトル化してコスト削減
  • 古いバージョンを is_latest=false フラグで除外(物理削除しない)
  • Streaming 生成でレスポンス体感速度を改善

技術スタック例

flowchart LR Feed[RSS Webhook] --> Delta --> Reindex --> DB[(Qdrant)] DB --> LLM[Claude sonnet]

RSS/Webhook → 差分検出 → 再インデックス → Qdrant(メタデータフィルタ強力) → Claude claude-sonnet-4-6

周辺知識

Embedding モデル比較
モデル 次元数 日本語 コスト 特徴・用途
text-embedding-3-small 1,536 $0.02/1M tokens 汎用・最安。まずこれで始める
text-embedding-3-large 3,072 $0.13/1M tokens 高精度が必要な本番環境向け
voyage-3 1,024 $0.06/1M tokens Anthropic推奨。多言語・長文に強い
voyage-code-3 1,024 $0.06/1M tokens コード検索専用。コードRAGに必須
BGE-M3(OSS) 1,024 無料(自己ホスト) 日本語最強クラス。セルフホスト前提
ベクトルDB 選定基準
pgvector

既存PostgreSQLに追加できる。〜100万件規模。RDSの他テーブルとJOINできる点が強力。Supabaseがマネージドで提供。

✓ 既存PG環境がある・結合クエリが必要

Qdrant

Rust製で高速・低メモリ。メタデータフィルタが強力(payload filter)。OSSでセルフホスト可。Dockerで即起動できる。

✓ 高スループット・複雑なフィルタ条件

Pinecone

フルマネージド。インフラ不要で10億件以上のスケールに対応。コストは高め。名前空間でマルチテナント管理が容易。

✓ 大規模・インフラ管理したくない

チャンキング戦略の選び方
ドキュメント種別 推奨サイズ 区切り方 注意点
社内規程・法律文書 500〜800 token 条文・項番単位 条文番号をメタデータに必ず含める
FAQ / Q&A 200〜400 token Q&Aペア単位(分割しない) QとAを必ず同じチャンクに含める
技術ドキュメント(MD) 800〜1200 token 見出し(##)単位 MarkdownHeaderTextSplitter を使う
ソースコード 関数・クラス単位 AST解析(tree-sitter) 行数ではなく構造で分割する
会議議事録 300〜600 token 話題・アクションアイテム単位 日付・参加者をメタデータに追加
💡 Parent Document Retriever パターン:大きなチャンク(親)で文脈を保持し、小さなチャンク(子)でベクトル検索する。子が当たったら親全体をコンテキストとして渡す。精度と文脈保持の両立に効果的。
よくある失敗と対策
失敗パターン 対策
❌ 固有名詞がヒットしない 「ABC-1234エラー」「モデルXR-7」など型番・コードをベクトル検索だけで探すと見つからない ✅ 対策 Hybrid Search(BM25)を必ず組み合わせる。キーワードはBM25の得意分野
❌ 関係ない回答が返ってくる Top-K で取得したチャンクに無関係なものが混じり、回答が汚染される ✅ 対策 ① 類似度スコアで足切り(cosine < 0.75 は除外)② Reranker で再スコアリング ③ メタデータフィルタで絞り込み
❌ 「知りません」と言うべき場面で嘘をつく コンテキストに情報がないのに LLM が知識から回答してしまう(ハルシネーション) ✅ 対策 System プロンプトに「コンテキスト外の情報を使わない。不明なら"情報がありません"と答えよ」を明示。RAGAS の faithfulness で定期測定
❌ インデックスが肥大化して遅くなる 更新のたびに全件再インデックスしているため、時間とコストが増大し続ける ✅ 対策 Incremental Indexing:ハッシュ比較で変更のあったドキュメントだけ再ベクトル化。古いチャンクは論理削除(is_active=false

AIへの指示パターン(続き)

RAG実装をAIに依頼するプロンプト例
コードRAG構築
Pythonリポジトリを対象に、関数・クラス単位でチャンクするコードRAGを構築してください。
・tree-sitter でAST解析してチャンク分割
・voyage-code-3 でベクトル化
・メタデータ: ファイルパス・関数名・引数・docstring
・pgvector に保存してClaude Code から参照できる形に
評価パイプライン
RAGASを使った自動評価パイプラインをGitHub Actionsで構築してください。
・PR マージ時に自動でfaithfulness・answer_relevancy を計測
・faithfulness < 0.85 のとき PR をブロック
・結果をPRコメントに投稿