Claude RAGの構築手順 — 埋め込みモデルの選び方と引用付き回答
Claudeに埋め込みモデルはなく、検索側はVoyage AIなど外部で用意します。埋め込みモデルの選び方と、search_resultブロックで出典付きの回答を返す手順、つまずきどころを示します。
ClaudeでRAGを組むとき、Claudeが担う範囲はどこまでか
Claudeに埋め込み(embedding)モデルはありません。Anthropicの公式ドキュメントも「自社の埋め込みモデルは提供していない」と明言し、Voyage AI(MongoDB傘下)を例に複数のベンダーを比べて選ぶよう案内しています。検索用のベクトルは外部のモデルで作り、Claudeは検索結果を読んで出典付きで答える役を担います。RAG(Retrieval-Augmented Generation、検索拡張生成)をClaudeで組むなら、検索側と生成側を別々に用意してMessages APIでつなぐ形になります。
RAGを組む3つの部品
① 検索用ベクトル
Voyage AIなどの外部ベンダーが担当します。ドキュメントとクエリを埋め込み、ベクトルDBで近傍探索します。
② 検索精度の前処理
チャンクの先頭に文脈の一文を足すContextual Retrieval、またはモデル側で文脈を持たせるcontextualized embeddingを使います。
③ 引用付きの生成
Claudeが担当する部分です。
search_resultブロックで検索結果を渡し、根拠にした箇所へ出典を付けさせます。
必要なのはAnthropic APIキーと、埋め込み用のAPIキーです。すでにElasticsearchやベクトルDBの検索基盤を持っているなら、①②は手元の仕組みのまま③だけを足せば成立します。Claudeが検索結果の中身を読んで答えを組み立てる部分は、検索側の実装がどうであっても同じ形で使えるためです。
まず、検索基盤が本当に必要な規模かを確かめる
RAGの実装コストは、ベクトルDBの運用、チャンク分割の調整、検索精度の評価と、後から効いてきます。その前に、知識ベースが小さければ検索を省けるかを確認する価値があります。チャンク分割の幅は、RAGのチャンクサイズを評価する手順で測って決められます。
全文を渡すか、検索を組むか
全文を毎回渡す
Prompt Cachingで知識ベース全体をキャッシュし、質問ごとに添えます。検索基盤が要らず、検索漏れも起きません。
検索基盤を組む
プロンプトに収まらない規模でも扱えます。代わりに、チャンク分割・埋め込み・検索精度の評価を自分で持ちます。
200,000トークンはおおよそ500ページ分の資料にあたり、Anthropicのエンジニアリングブログが示している目安です。同じブログは、Prompt Cachingによって頻繁に使うプロンプトの遅延を2倍以上、コストを最大90%減らせると説明しています。キャッシュの仕組みと適用判断はPrompt Cachingの解説記事にあります。
境界付近の資料量なら、まず全文キャッシュで動かしてみて、回答の取りこぼしや速度に不満が出た段階でRAGへ移る進め方が無駄を出しません。移行後も、③の引用付き生成はそのまま残せます。
Voyage AIの埋め込みモデルを選び、クエリと文書を区別して生成する
公式ドキュメントが挙げる最新世代はvoyage-4系の4モデルです。コンテキスト長はいずれも32,000トークンで、出力次元は256・512・1024・2048から選びます。既定は1024で、voyage-4-nanoだけは2048です。voyage-4-nanoだけはApache 2.0のオープンウェイトで、Hugging Faceから入手できます。
用途ごとの選び方は次のとおりです。
| 用途 | 選ぶモデル |
|---|---|
| まず試す・汎用 | 選ぶモデルvoyage-4 |
| 検索品質を最優先 | 選ぶモデルvoyage-4-large |
| 遅延とコストを優先 | 選ぶモデルvoyage-4-lite |
| 自前環境で動かす | 選ぶモデルvoyage-4-nano(オープンウェイト) |
| 文脈ごとベクトル化 | 選ぶモデルvoyage-context-4 |
特定分野なら、金融のvoyage-finance-2、法務のvoyage-law-2、コードのvoyage-code-4といった専用モデルがあります。法務向けのコンテキスト長は16,000トークンで、voyage-4系の半分です。画像や動画を含む資料には、voyage-multimodal-3.5がテキスト・画像・動画の交互入力に対応しています。
Voyage AIのモデルは、MongoDB Atlasの「AI Model APIs」で作ったモデルAPIキーで呼びます。キーを環境変数に入れれば、Pythonクライアントが自動で読みます。
pip install -U voyageai
export VOYAGE_API_KEY="<your-voyage-api-key>"埋め込みの取得で外せないのがinput_typeです。検索対象の文書には"document"、ユーザーの質問には"query"を渡します。
import numpy as np
import voyageai
vo = voyageai.Client()
# 検索対象のドキュメントを埋め込む
doc_embeddings = vo.embed(
documents, model="voyage-4", input_type="document"
).embeddings
# ユーザーの質問を埋め込む
query_embedding = vo.embed(
[query], model="voyage-4", input_type="query"
).embeddings[0]
# 正規化済みなので内積がそのままコサイン類似度になる
scores = np.dot(doc_embeddings, query_embedding)
top_ids = np.argsort(scores)[::-1][:20]Anthropicのドキュメントは、検索用途ではinput_typeを省略したりNoneにしたりしないよう明記しています。理由は、指定した値に応じて入力テキストの先頭へ固定の文が足されるためです。クエリには「Represent the query for retrieving supporting documents:」、文書には「Represent the document for retrieval:」が付きます。この区別を間違えたときの挙動はVoyage embeddingのinput_typeでquery/documentを分ける理由で扱っています。
生成されるベクトルは長さ1に正規化されています。そのため、コサイン類似度と内積は同じ値になり、内積のほうが速く計算できます。ユークリッド距離で並べても順位は同じです。コード例のnp.dotで済ませてよいのはこのためです。
大量のチャンクを抱えるなら、ベクトルの保存コストにも手が打てます。output_dtypeにint8を指定すると保存量が4分の1に、binaryにすると32分の1になると説明されています。次元を減らすなら、先頭から必要な次元数だけ残して切り詰める方法があり、公式の例は切り詰めたあとにベクトルを正規化し直しています。どちらも検索精度とのトレードオフなので、自分のデータで検索の当たり具合を確かめてから採用します。格納先のDBは何でもかまいません。MongoDB Atlasを使うなら、クラスタの作成やスケーリングを自然言語で頼めるMongoDB AtlasのMCPクラスタ管理も併せて使えます。
Voyage以外でも後段の設計は変わりません。ブログではGeminiとVoyageの埋め込みが特に効果的だったとされています。
チャンクが「何の話か」を失うとき、Contextual Retrievalで文脈を戻す
文書を数百トークンのチャンクに割ると、チャンク単体では「どの会社の、いつの話か」が消えます。ブログの例では「The company's revenue grew by 3% over the previous quarter.」というチャンクが出てきます。このままでは、ACME社の2023年第2四半期の売上成長率を尋ねる質問と結びつきません。
Contextual Retrievalは、各チャンクの先頭にそのチャンクの位置づけを説明する短文を足してから、埋め込みとBM25の索引を作る前処理です。ブログの例では「SEC提出書類のACME社の2023年第2四半期の業績に関するチャンクで、前四半期の売上は3億1,400万ドル」という趣旨の文が前置きされます。文脈の生成はClaude 3 Haikuに文書全体とチャンクを渡して頼み、出力は通常50〜100トークンです。
コストを抑えるのがPrompt Cachingです。文書全体を一度キャッシュに載せれば、チャンクごとに文書を送り直す必要がなくなります。800トークンのチャンク、8,000トークンの文書、50トークンの指示、100トークンの文脈という条件で、一度きりの生成費用は文書100万トークンあたり1.02ドルと試算されています。
効果は、上位20チャンクを取得したときの取りこぼし率で測られています。
| 構成 | 取りこぼし率 | 改善幅 |
|---|---|---|
| 従来の埋め込み検索(BM25なし) | 取りこぼし率5.7% | 改善幅基準 |
| (参考)埋め込み+BM25 | 取りこぼし率5.0% | 改善幅—(BM25を足した従来構成) |
| Contextual Embeddings | 取りこぼし率3.7% | 改善幅35%減 |
| Contextual Embeddings+Contextual BM25 | 取りこぼし率2.9% | 改善幅49%減 |
| 上記+リランキング | 取りこぼし率1.9% | 改善幅67%減 |
この数字は、埋め込みにGemini Text 004を使い、コードベース・小説・ArXiv論文・科学論文を平均した結果です。日本語の社内文書で同じ幅が出る保証はありません。ブログ自身も、自分のデータで評価を回すよう勧めています。リランキングの検証ではまず上位150チャンクを取り、リランカーで上位20に絞っています。また、モデルに渡すチャンク数は5・10・20のうち20が最も良い結果でした。リランカーはCohereのものが使われており、Voyage AIにも最高精度のrerank-3と、遅延とコストを優先するrerank-3-liteがあります。
前処理のパイプラインを自分で持ちたくないなら、voyage-context-3とvoyage-context-4というモデルがあります。これらはembed()ではなくcontextualized_embed()で呼び、文書全体(コンテキスト長120,000トークン)を渡すと、チャンク単位のベクトルに文書全体の文脈が反映されます。手動のメタデータ付与は不要になります。前処理の中身を細かく調整したいならContextual Retrieval、運用の手間を減らしたいならcontextualized embeddingが向きます。Contextual Retrievalの仕組みと内訳はContextual Retrievalの解説記事で詳しく扱っています。
検索結果に出典を付けて回答させる
検索で候補チャンクが揃ったら、Claudeに渡す番です。Anthropicが勧める入れ物はsearch_resultコンテンツブロックで、Claude Haiku 3を除く現行モデルが対応します。ベータヘッダーは不要で、標準のMessages APIに含まれます。
ブロックはsource(URLやkb://article-1234のような内部ID)、title、content(テキストブロックの配列)で構成します。citationsに{"enabled": true}を付けると、Claudeが根拠にした箇所に出典が付きます。引用を出すために特別なプロンプトは要りません。質問を投げるだけで、該当するテキストブロックに引用が付きます。
import anthropic
from anthropic.types import (
MessageParam,
SearchResultBlockParam,
TextBlockParam,
)
search_result = SearchResultBlockParam(
type="search_result",
source="kb://product-guide",
title="Product Configuration Guide",
content=[
TextBlockParam(
type="text",
text="タイムアウトの既定値は30秒で、10〜120秒の範囲で調整できます。",
)
],
citations={"enabled": True},
)
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[
MessageParam(
role="user",
content=[
search_result,
TextBlockParam(type="text", text="タイムアウトはどこまで伸ばせますか"),
],
)
],
)応答はtextブロックの配列で、根拠のある箇所にはcitations配列が付きます。各引用はsourceとtitleに加え、content配列のどのブロックを使ったかを示すstart_block_indexとend_block_indexを持ちます。根拠テキストそのものであるcited_textも含まれ、出力トークンとして数えられません。引用を有効にすると、システムプロンプトの追加とチャンク化のぶん入力トークンはわずかに増えますが、出力側の負担は小さいと説明されています。
設計で効くのは引用の粒度です。テキストブロックが引用できる最小の単位なので、Claudeはブロックの一部を切り出さず、ブロックごと引用します。長い1ブロックを渡すと、引用が広すぎる範囲を指します。論理的なまとまりごとにブロックを分けて渡せば、引用の指し先が絞れます。具体的な分け方はsearch_resultの引用粒度を細かくする方法にあります。
出典URLは、後から変わらないものをsourceに入れます。タイトルは内容を正確に表すものにします。公式のベストプラクティスにも、恒久的なURLと内容に合ったタイトルを使うことが挙がっています。同じ検索結果を繰り返し渡すなら、ブロックにcache_controlを付けるとキャッシュできます。
動的な検索をツール呼び出しにつなげる
検索対象が更新され続けるなら、search_resultをカスタムツールの戻り値として返す構成にします。ユーザーメッセージへ直接並べる方法と、ツールの戻り値として返す方法の両方があり、混在させたときのインデックス番号の振られ方はsearch_result_indexの番号仕様で確認できます。
ツール経由のRAGで往復する4段階
- 1
質問を受けてClaudeがツールを呼ぶ
search_knowledge_baseのようなツール定義があれば、Claudeが検索クエリ付きのtool_useブロックを返します。 - 2
アプリ側で検索する
ベクトル検索に加えて、必要ならBM25とリランキングを重ね、上位のチャンクを選びます。
- 3
結果を`tool_result`で返す
選んだチャンクを
search_resultブロックの配列にして返します。 - 4
引用付きの応答を受け取る
Claudeが検索結果を根拠に答え、引用付きの
textブロックが返ります。
応答を処理するコードで見落としやすいのは、tool_useブロックが応答の先頭に来るとは限らない点です。ツールを強制しない呼び出しでは、前置きのテキストが先に返ることがあります。公式のサンプルコードも、content配列を走査してtool_useを探す書き方にしています。content[0]と決め打ちにしないでください。
検索が空振りしたときの返し方も決めておきます。公式は、エラーを投げずに「No results found.」のような説明を含むプレーンなテキストブロックを返すよう勧めています。Claudeが空の結果をユーザーに説明し、会話が続きます。件数は関連の高い上位に絞るのが無難です。ブログも、情報が多すぎるとモデルの注意が散ると述べています。
ツール呼び出し→検索→結果返却→最終応答のループを自前で書くのは手間です。Claude Agent SDKはこのループを内蔵しているので、検索関数をカスタムツールとして1つ登録すれば済みます。Messages APIの構造やBatch処理など周辺機能の全体像はAnthropic API完全ガイドにまとめています。
search_resultとdocumentブロックを使い分ける
Claudeに文書を読ませて出典付きで答えさせる入口は、search_resultのほかにもあります。documentブロックにcitationsを付ける方法で、チャンク化のしかたと引用の指し方が違います。
| 入れ物 | チャンク化 | 引用の指し方 |
|---|---|---|
search_result | チャンク化渡したテキストブロックのまま | 引用の指し方ブロック番号 |
document(プレーンテキスト) | チャンク化文単位に自動分割 | 引用の指し方文字位置(0始まり) |
document(PDF) | チャンク化文単位に自動分割 | 引用の指し方ページ番号(1始まり) |
document(カスタムコンテンツ) | チャンク化追加の分割なし | 引用の指し方ブロック番号(0始まり) |
検索パイプライン側ですでにチャンク化とランキングを済ませているなら、search_resultが素直です。出典URLや内部IDをそのまま引用に載せられます。一方、渡す文書が1〜数ファイルで全文を読ませたいなら、documentブロックのほうが簡単です。同じファイルを複数の会話で繰り返し参照するなら、Files APIで上げたfile_idを指定します。
RAGのチャンクの中の文を引用させたい場合は、公式のTipが具体的です。各チャンクをプレーンテキストのdocumentに入れると、文単位で引用できます。追加の分割をさせたくない、または自前で分割したいなら、カスタムコンテンツのdocumentに入れます。箇条書きや文字起こしのように構造のある資料は、後者が向いています。
documentブロックが直接扱えない形式もあります。.docxや.xlsxはプレーンテキストに変換してからメッセージに入れます。.csvや.mdのような平文のファイルは、text/plainを明示してアップロードできます。
よくあるつまずき
input_typeを省略する — 省略すると、検索精度が落ちると公式に注意されています。文書側とクエリ側の両方の呼び出しで明示します。- citationsのON/OFFを混在させる — 1回のリクエストに含まれる
search_resultは、全部で引用を有効にするか全部で無効にするかのどちらかです。混ぜるとエラーになります。documentブロックも同様に、有効は全部か皆無かです。 - Webサーチツールと併用して引用を切る — 同じリクエストでWebサーチツールを有効にしている場合は、すべての
search_resultでcitationsを有効にする必要があります。 - tool_result内で
search_resultと他のブロックを混ぜる — 1つでもsearch_resultがあるなら、同じtool_resultのブロックはすべてsearch_resultでなければバリデーションエラーです。補足テキストは、いずれかのsearch_resultのcontentにテキストブロックとして入れます。引用の対象にもなります。 - assistantメッセージに
search_resultを入れる — ユーザーメッセージ(ツール結果を含む)専用で、assistant側に入れると拒否されます。 - 構造化出力とcitationsを併用する —
output_config.formatと、documentやsearch_resultのcitations有効化を同じリクエストに置くと400エラーです。引用の挿入と厳密なJSONスキーマ制約は両立しないためです。構造化出力が必要なら、引用なしで運用するか、引用情報を別リクエストで取ります。 - 検索結果に画像を入れる —
search_resultのcontentはテキストのみで、画像などは入れられません。 - 小規模なのに検索基盤を組む — 前の節の判断どおり、知識ベースが小さければ全文キャッシュのほうが実装も不具合調査も楽です。
まとめ
ClaudeのAPIで担うのは出典付きの生成で、埋め込みと検索は外に置く設計です。まず全文キャッシュで動かし、知識ベースが収まらなくなってから、Voyageの埋め込みとContextual Retrievalを足す順番なら実装コストを抑えられます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。