Claude APIのsearch_resultブロックでRAGに出典を付ける

Claude API エンジニアリング 7分で読めます

社内ドキュメントを検索して答えさせる RAG(検索拡張生成)では、「その答えはどの文書のどこに書いてあったのか」を利用者に示せるかどうかが信頼性を分けます。Claude API の 検索結果コンテンツブロック(search_resultは、あなたが渡した自前のコンテンツに対して、Web検索ツールの結果と同じ形式の引用を自動で付けさせる仕組みです。本記事では公式ドキュメントに基づき、ブロックの構造・2通りの渡し方・返ってくる引用の読み方・制限を順に整理します。

search_resultブロックとは

search_result は、あなたが用意した検索結果を Claude に渡すための専用のコンテンツブロックです。ブロックには本文だけでなく出典(source)とタイトル(titleを一緒に持たせます。Claude はその内容を根拠に答えたとき、Web検索ツールの結果を引用するときと同じ形で、出典とタイトル付きの引用を自動的に付けてくれます。社内ナレッジベースやベクトル検索の結果に対して、公式の引用機構をそのまま使えるのがこの機能の要点です。

公式ドキュメントによれば、アクティブなモデルはすべてこの機能に対応しており、例外は Claude Haiku 3 のみです。ベータヘッダーは不要で、標準の Messages API の一部として扱われます。

検索結果の渡し方は2通りあります。

  1. ツール呼び出しの結果として返す: 自作のツールが検索結果を返す。実行時に検索する動的な RAG 向け。
  2. ユーザーメッセージの中に直接置く: 取得済み・キャッシュ済みの結果をそのまま渡す。

どちらの場合も、引用を有効にしていれば Claude は自動的に引用を付けます。「必ず出典を示して」といった特別なプロンプトは不要で、質問するだけで、該当するテキストブロックに引用が付いて返ってきます。

ブロックの構造と必須フィールド

検索結果ブロックの基本形は次のとおりです。

{
  "type": "search_result",
  "source": "https://example.com/article",
  "title": "Article Title",
  "content": [
    { "type": "text", "text": "The actual content of the search result..." }
  ],
  "citations": { "enabled": true }
}

必須フィールドは4つです。

任意のフィールドは2つです。

content 配列の各要素は、type"text"text が空でない文字列であるテキストブロックでなければなりません。検索結果に入れられるのはテキストだけで、画像やその他のメディアは content 配列の中に置けません。

content は複数のテキストブロックに分割できます。これは引用の粒度に直結します。

{
  "type": "search_result",
  "source": "https://docs.company.com/api-guide",
  "title": "API Documentation",
  "content": [
    { "type": "text", "text": "Authentication: All API requests require an API key." },
    { "type": "text", "text": "Rate Limits: The API allows 1000 requests per hour per key." },
    { "type": "text", "text": "Error Handling: The API returns standard HTTP status codes." }
  ],
  "citations": { "enabled": true }
}

引用できる最小単位はテキストブロックです。Claude はブロック単位で引用し、ブロック内の一部分だけを切り出すことはしません。長い本文を1つのブロックにまとめると、どこを引用しても全文が引用テキストとして返ります。逆に意味のまとまりごとに細かく分けておけば、その分だけ引用の範囲が絞られます。

方法1: ツール結果として返す

自作の検索ツールが search_result ブロックの配列を返すようにすると、実行時に検索する動的な RAG が組めます。ツール使用の通常の流れと同じで、違うのは tool_result の中身が検索結果ブロックである点だけです。

公式のナレッジベース検索の例を Python SDK で示します。tool_choice でツール呼び出しを強制しているため、検索の工程が毎回必ず走ります。

from anthropic.types import (
    MessageParam, TextBlockParam, SearchResultBlockParam, ToolResultBlockParam,
)

client = Anthropic()

knowledge_base_tool = {
    "name": "search_knowledge_base",
    "description": "Search the company knowledge base for information",
    "input_schema": {
        "type": "object",
        "properties": {"query": {"type": "string", "description": "The search query"}},
        "required": ["query"],
    },
}

def search_knowledge_base(query):
    # ここに自前の検索ロジックを書く
    return [
        SearchResultBlockParam(
            type="search_result",
            source="https://docs.company.com/product-guide",
            title="Product Configuration Guide",
            content=[TextBlockParam(type="text", text="To configure the product, navigate to Settings > Configuration. ...")],
            citations={"enabled": True},
        ),
    ]

messages = [MessageParam(role="user", content="How do I configure the timeout settings?")]

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    tools=[knowledge_base_tool],
    tool_choice={"type": "tool", "name": "search_knowledge_base"},
    messages=messages,
)

tool_use = next((block for block in response.content if block.type == "tool_use"), None)
if tool_use is not None:
    tool_result = search_knowledge_base(tool_use.input["query"])
    messages.append(MessageParam(role="assistant", content=response.content))
    messages.append(
        MessageParam(
            role="user",
            content=[ToolResultBlockParam(type="tool_result", tool_use_id=tool_use.id, content=tool_result)],
        )
    )
    final_response = client.messages.create(model="claude-opus-5", max_tokens=1024, messages=messages)
    print(final_response)

公式のサンプルは、レスポンスから tool_use ブロックを取り出す際に先頭決め打ちではなく走査しています。tool_use が必ず先頭に来るとは限らないためで、実装でも同じようにしておくのが安全です。

ツール結果には1つ制約があります。tool_resultcontent 配列に search_result ブロックが1つでも含まれるなら、その配列は全部が search_result でなければなりません。他の種類のブロックを混ぜるとバリデーションエラーになります。検索結果に補足の説明文を添えたい場合は、いずれかの検索結果の content 配列の中にテキストブロックとして入れます。そうすればその説明文も引用の対象になります。

方法2: ユーザーメッセージに直接置く

すでに検索結果が手元にある場合は、ツールを介さずユーザーメッセージの contentsearch_result ブロックを並べるだけで済みます。公式は、自社の検索基盤で取得済みのコンテンツ、過去のクエリのキャッシュ、外部検索サービスの結果、そしてテストや開発の場面をこの方式の用途として挙げています。

curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-opus-5",
    "max_tokens": 1024,
    "messages": [
      {
        "role": "user",
        "content": [
          {
            "type": "search_result",
            "source": "https://docs.company.com/api-reference",
            "title": "API Reference - Authentication",
            "content": [
              { "type": "text", "text": "All API requests must include an API key in the Authorization header. ..." }
            ],
            "citations": { "enabled": true }
          },
          {
            "type": "text",
            "text": "Based on these search results, how do I authenticate API requests and what are the rate limits?"
          }
        ]
      }
    ]
  }'

ユーザーメッセージの中では、search_result ブロックは他の種類のコンテンツブロックと自由に並べられます。上の例のように質問のテキストブロックと並べるのが基本形で、画像やドキュメントのブロックを同じように加えることもできます。ツール結果の場合と違い、こちらには混在の制約はありません。

返ってくる引用の読み方

どちらの渡し方でも、Claude が検索結果の情報を使うと、テキストブロックに citations 配列が付いて返ります。

{
  "type": "text",
  "text": "All API requests must include an API key in the Authorization header.",
  "citations": [
    {
      "type": "search_result_location",
      "cited_text": "All API requests must include an API key in the Authorization header. ...",
      "source": "https://docs.company.com/api-reference",
      "title": "API Reference - Authentication",
      "search_result_index": 0,
      "start_block_index": 0,
      "end_block_index": 1
    }
  ]
}

各フィールドの意味は次のとおりです。

つまり引用は「何番目の検索結果の、content 配列のどのブロックからどのブロックまでか」を指しており、cited_text はその範囲の全文です。UI に出典リンクを出すなら sourcetitle を、原文のハイライトを出すなら cited_text かブロックのインデックスを使います。この仕組みは Citations(引用)機能のカスタムコンテンツドキュメントと同じ考え方です。

制限と実装のコツ

公式が挙げている制限は次のとおりです。

実装上のコツとして、公式は次の点を勧めています。出典 URL は安定した永続的なものにし、タイトルは中身を正しく表すものにし、長い本文は論理的なまとまりごとにテキストブロックへ分けて引用の境界を細かくすること。出典の書式はアプリケーション全体で統一すること。ツール経由の検索(方法1)では、コンテキストがあふれないよう関連性の高い結果だけに絞ること。

エラー処理にも指針があります。検索が失敗したり結果が0件だったりしたときは、例外を投げるのではなく {"type": "text", "text": "No results found."} のようなプレーンなテキストブロックを返します。そうすれば Claude が結果が空であることを利用者に説明し、会話をそのまま続けられます。