ClaudeでRAGを実装する|検索拡張生成の設計パターンと実践例

AI・自動化
スポンサーリンク

📋 Claude Code コマンド指示書(クリックで展開)

.claude/commands/ に保存して /コマンド で実行

---
description: "ClaudeでRAGを実装する|検索拡張生成の設計パターンと実践例"
---

# ClaudeでRAGを実装する|検索拡張生成の設計パターンと実践例

この指示書は https://akahara-vlab.com/claude-rag-patterns/ の内容をClaude Codeコマンドとして実行するためのものです。

## 概要

ClaudeでRAG(検索拡張生成)を実装する方法を解説。200Kコンテキストを活かした設計、embedding選択、チャンク戦略、プロンプト設計、MCP連携、実装例まで。

## 使い方

1. このテキストを `.claude/commands/claude-rag-patterns.md` に保存
2. Claude Codeで `/claude-rag-patterns` と入力して実行

## 指示

上記の記事の知識をもとに、ユーザーの質問に回答してください。
記事URL: https://akahara-vlab.com/claude-rag-patterns/

※ 平文なので中身を確認してから使ってください。安全性は目視で確認できます。

わさびです。

LLMに「自社のドキュメントを読ませて質問に答えさせたい」。この要望を実現するのがRAG(Retrieval-Augmented Generation / 検索拡張生成)。

Claudeは200Kトークンのコンテキストウィンドウを持っている。これは日本語で約10万文字、新書3〜4冊分。この巨大なコンテキストを活かしたRAG設計は、他のLLMとは違うアプローチが取れる。

スポンサーリンク

RAGの基本概念

RAGは3ステップで動く:

  1. ユーザーの質問を受け取る
  2. 関連するドキュメントを検索する(Retrieval)
  3. 検索結果をプロンプトに含めてLLMに回答させる(Generation)

LLMは学習データに含まれない情報(社内文書、最新ニュース等)を知らない。検索で補完してあげることで、正確な回答が可能になる。

よくある誤解:「ファインチューニングで自社データを学習させればいい」。これは大抵うまくいかない。ファインチューニングはLLMの「スタイル」を変えるもので、「知識」を追加するのはRAGの役割。

Claudeの200Kコンテキストを活かす設計

従来のRAGは「コンテキストウィンドウが小さいから、検索精度が超重要」だった。4Kトークンしかなければ、本当に関連する数チャンクしか入れられない。

Claudeは200Kトークン。これは設計を根本的に変える。

アプローチ従来のRAGClaude向けRAG
コンテキスト4K〜8K200K
検索精度の重要度極めて高い中程度(量で補える)
チャンク数3〜5個50〜100個でも可
リランキングほぼ必須なくても動く

つまり「検索でちょっと多めに引っかけて、全部Claudeに渡す」という力技が通用する。検索精度のチューニングに費やす時間を大幅に減らせる。

ただし200Kに詰め込むほどコストは上がる。精度とコストのバランスは考える必要がある。

embeddingの選択肢

検索の基盤になるのがembedding(埋め込みベクトル)。テキストを数値のベクトルに変換して、ベクトル間の距離で類似度を測る。

主要なembeddingモデル

モデル提供元次元数日本語対応料金
text-embedding-3-largeOpenAI3,072良好$0.13/100万tok
text-embedding-3-smallOpenAI1,536良好$0.02/100万tok
nomic-embed-textNomic (Ollama)768まあまあ無料(ローカル)
multilingual-e5-largeMicrosoft1,024良好無料(ローカル)

Anthropicは独自のembeddingモデルを提供していない。外部のembeddingモデルと組み合わせて使う。

コストを抑えるならOllamaでnomic-embed-textをローカル実行するのがおすすめ。品質を優先するならOpenAIのtext-embedding-3-largeが安定している。

チャンク戦略

ドキュメントをどう分割(チャンク)するかで検索精度が変わる。

チャンクサイズの目安

サイズ向いているケース
200〜500トークンFAQ、短い項目
500〜1,000トークン技術文書、マニュアル
1,000〜2,000トークン長い記事、論文

分割の方法

単純な文字数分割はやめたほうがいい。文の途中で切れると検索精度が落ちる。

おすすめの分割方法:

  • Markdownの見出し(##)で分割
  • 段落(空行2つ)で分割
  • 意味的なまとまりで分割

オーバーラップ

チャンク同士に50〜100トークンの重複を持たせると、文脈が途切れにくくなる。

defchunk_text(text, chunk_size=1000, overlap=100):
    chunks = []
    start = 0
    while start < len(text):
        end = start + chunk_size
        chunks.append(text[start:end])
        start = end - overlap
    return chunks

プロンプト設計

検索結果をClaudeに渡すときのプロンプト設計が、RAGの品質を決める。

基本パターン

system_prompt = """あなたは社内文書に基づいて質問に回答するアシスタントです。

ルール:
- 提供されたコンテキストに基づいてのみ回答する
- コンテキストに情報がない場合は「この情報は見つかりませんでした」と答える
- 回答の根拠となるドキュメント名を明記する"""

user_prompt = f"""以下のコンテキストを参照して、質問に答えてください。

<context>
{retrieved_documents}
</context>

質問:{user_question}"""

ポイント:

  • XMLタグでコンテキストを明確に区切る(Claudeはタグ構造に強い)
  • 「コンテキストにない情報は答えない」を明記する(ハルシネーション防止)
  • ソースの明記を求める(回答の信頼性向上)

複数ドキュメントの渡し方

context_parts = []
for i, doc in enumerate(retrieved_docs):
    context_parts.append(f"""<document index="{i+1}" source="{doc['source']}">
{doc['content']}
</document>""")

context = "nn".join(context_parts)

各ドキュメントにインデックスとソース情報をつけると、Claudeが引用しやすくなる。

citation機能

Claude APIにはcitation(引用)機能がある。回答の中でどのドキュメントのどの部分を参照したかを構造化データで返してくれる。

importanthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "document",
                    "source": {
                        "type": "text",
                        "media_type": "text/plain",
                        "data": "社内規定: 有給休暇は年間20日付与される。..."
                    },
                    "title": "就業規則.txt",
                    "citations": {"enabled": True}
                },
                {
                    "type": "text",
                    "text": "有給休暇は何日もらえますか?"
                }
            ]
        }
    ]
)

for block in response.content:
    if block.type == "text":
        print(block.text)
        if hasattr(block, "citations") and block.citations:
            for cite in block.citations:
                print(f"  出典:{cite.document_title} -{cite.cited_text}")

citation機能を使うと、ハルシネーション検出がしやすくなる。回答に引用が付いていなければ、LLMが知識から答えている(=不正確な可能性がある)と判断できる。

MCP連携

MCP(Model Context Protocol)を使えば、Claude DesktopやClaude Codeから直接ベクトルDBに問い合わせるRAGシステムを構築できる。

MCPサーバーとして検索機能を公開すれば、Claudeが自分で「必要な情報を検索する」ようになる。人間がプロンプトに検索結果を貼り付ける必要がなくなる。

詳しくはClaude MCP完全ガイドを参照。

Python実装例

最小構成のRAGシステム:

importanthropic
importnumpyasnp
frompathlibimport Path

# --- 1. ドキュメントの読み込みとチャンク化 ---
defload_and_chunk(directory: str, chunk_size: int = 800) -> list[dict]:
    chunks = []
    for path in Path(directory).glob("*.md"):
        text = path.read_text(encoding="utf-8")
        # 見出しで分割
        sections = text.split("n## ")
        for section in sections:
            if len(section.strip()) > 50:
                chunks.append({
                    "source": path.name,
                    "content": section.strip()
                })
    return chunks

# --- 2. embeddingの生成(OpenAI) ---
fromopenaiimport OpenAI

openai_client = OpenAI()

defget_embeddings(texts: list[str]) -> list[list[float]]:
    response = openai_client.embeddings.create(
        model="text-embedding-3-small",
        input=texts
    )
    return [item.embedding for item in response.data]

# --- 3. 検索 ---
defsearch(query: str, chunks: list[dict], embeddings: np.ndarray, top_k: int = 10) -> list[dict]:
    query_embedding = get_embeddings([query])[0]
    query_vec = np.array(query_embedding)

    # コサイン類似度
    similarities = np.dot(embeddings, query_vec) / (
        np.linalg.norm(embeddings, axis=1) * np.linalg.norm(query_vec)
    )

    top_indices = np.argsort(similarities)[-top_k:][::-1]
    return [chunks[i] for i in top_indices]

# --- 4. 回答生成 ---
claude_client = anthropic.Anthropic()

defanswer(query: str, retrieved: list[dict]) -> str:
    context = "nn".join([
        f"[{doc['source']}]n{doc['content']}" for doc in retrieved
    ])

    response = claude_client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=1024,
        system="提供されたコンテキストに基づいて回答してください。情報がない場合はその旨伝えてください。",
        messages=[
            {
                "role": "user",
                "content": f"<context>n{context}n</context>nn質問:{query}"
            }
        ]
    )
    return response.content[0].text

本番では、embeddingの保存にベクトルDB(Chroma、Pinecone、pgvector等)を使う。上の例はnumpyで素朴にやっているが、数千件までならこれで十分動く。

よくある失敗パターン

1. チャンクが大きすぎる

5,000トークンのチャンクだと、ノイズが多くて精度が下がる。500〜1,000トークンに分割する。

2. メタデータを無視している

チャンクに「いつの情報か」「どのドキュメントか」のメタデータがないと、古い情報と新しい情報の区別がつかない。

3. 「何でも答えさせる」設計

コンテキストにない情報まで答えさせると、ハルシネーションが増える。「情報がなければ答えない」というガードレールが重要。

4. embeddingモデルの日本語性能を検証していない

英語では高精度でも、日本語では精度が落ちるモデルがある。必ず日本語のテストクエリで評価する。

まとめ

ClaudeでRAGを実装するときのポイント:

  • 200Kコンテキストのおかげで「多めに検索して全部渡す」戦略が使える
  • XMLタグで構造化するとClaudeの理解精度が上がる
  • citation機能でハルシネーションを検出できる
  • embeddingはAnthropicにはないので外部モデルを使う

RAGは「LLMに知識を与える」最も実用的な方法。ファインチューニングより手軽で、効果も高い。

甲羅の中に知識を溜め込むのは得意なほうだと思う。

あわせて読みたい

見てもらえるだけで応援になります

このブログはアフィリエイトリンクで運営されています。以下のリンクから気になるサービスをチェックしてもらえると、僕たちの活動の支えになります。


この記事を書いたのは わさび(ニホンイシガメ / 3歳 / VTuberあかはら。の家族)です。

あかはらVラボ — Claude特化の情報を発信中。

この記事が参考になったら|以下のリンクから見てもらえるだけで、ブログ運営の応援になります。

  • NordVPN

    AI活用時のデータ保護に。VPNで通信を暗号化。



  • AI開発環境やブログ運営に。初期費用無料、月額296円から。

コメント

タイトルとURLをコピーしました