Claude Media
search_resultを混在させたときのsearch_result_indexの番号仕様

search_resultを混在させたときのsearch_result_indexの番号仕様

ツール経由とユーザーメッセージ直接指定のsearch_resultは同一リクエストで混在できます。search_result_indexの通し番号の振られ方を、数え方の関数と出力つきで示します。

2つの経路のsearch_resultを1つのリクエストに混ぜられる

search_resultコンテンツブロックには供給経路が2つあります。ツールが実行時に返す経路(Method 1)と、ユーザーメッセージに直接置く事前取得済みの経路(Method 2)です。この2つは同じ会話の中で混在させられ、Claudeはどちらの結果からも引用します。

混ぜたときの問題は、引用に付くsearch_result_indexがどちらの経路のものかをどう区別するかです。答えは、経路を区別しません。リクエストに含まれるsearch_resultブロックを出現順に数え、0から通し番号を振るだけです。

各方式を単体で組む実装は、Claude RAGの構築ガイドの2節にあります。コード例とcitations.enabledの設定は、「検索結果に出典を付けて回答させる」「動的な検索をツール呼び出しにつなげる」で見られます。ここでは、混在させたときの番号の振られ方と、実装で踏みやすい制約を扱います。

search_result_indexは「リクエスト内の出現順」で決まる

引用に含まれるsearch_result_indexは、リクエスト中の全search_resultブロックのうち、引用元になったブロックの0始まりの位置です。数える範囲は全メッセージと全tool_resultにまたがり、順序はブロックが現れた順です。ブロックの種類はsearch_resultだけが数えられます。引用のtypeはsearch_result_locationで、source・title・cited_textと3つのインデックスが並びます。

具体的には、次のように数えます。最初のユーザーメッセージに事前取得済みの製品概要を置き、Claudeがツールを呼んで価格ページを取得する流れを例にします。

手順

混在した会話での番号の振られ方

  1. 1

    ユーザーメッセージに直接置いたsearch_result

    会話の先頭にあるので0になります。同じメッセージ内のtextブロックは数えません。

  2. 2

    assistantのtool_use

    search_resultではないので番号は進みません。

  3. 3

    tool_resultの中のsearch_result

    先頭のものが1になります。同じtool_resultに2つ返れば、続きの2まで使われます。

search_resultが置ける場所はユーザーロールのメッセージだけです。ツール経由の結果も、tool_resultとしてユーザーロールのターンに入ります。assistantロールのメッセージにsearch_resultを含めたリクエストは拒否されます。

引用番号から元のブロックを引く関数

番号の数え方を実装に落とすと、リクエストを先頭から1回走査してsearch_resultを配列に積むだけです。次の関数は、公式の定義をそのままコードにしたものです。ユーザーメッセージ直下のブロックと、tool_resultの内側のブロックを同じ配列に入れます。

def search_results(messages):
    """messages を先頭から走査し、search_result の (source, title) を出現順に返す"""
    found = []
    for m in messages:
        content = m["content"]
        if isinstance(content, str):
            continue
        for block in content:
            if block["type"] == "search_result":
                found.append((block["source"], block["title"]))
            elif block["type"] == "tool_result" and isinstance(block["content"], list):
                for inner in block["content"]:
                    if inner["type"] == "search_result":
                        found.append((inner["source"], inner["title"]))
    return found

この関数に、事前取得済みの1件とツールが返した2件を含む3メッセージの会話を渡して実行すると、次の出力になります。

python3 idx.py
0 https://docs.company.com/overview
1 https://docs.company.com/pricing
2 https://docs.company.com/limits

レスポンスのsearch_result_index: 2は、この配列の3番目にあたるlimitsのブロックを指します。事前取得済みの配列とツール取得の配列を別々に持っていても、送信時点で1本の出現順に並べておけば、引用の番号をそのまま参照に使えます。送信後も2つの配列を別々に管理すると、番号と配列の位置がずれます。

同じURLを2つのsearch_resultとして渡した場合も、ブロックごとに別の番号になります。番号が指すのはURLではなくブロックの位置です。

引用のsourceとtitleには、元のブロックに書いた値がそのまま入ります。公式の説明では、sourceはURLでもkb://article-1234のような社内の識別子でもよく、安定した文字列であれば何でも使えます。事前取得分にはcache://...、ツール取得分にはtool://...のように経路が分かる識別子を付けておくと、番号が経路を区別しない仕様を引用側の表示で補えます。これは仕様ではなく、sourceの自由度を使った設計上の選択肢です。

番号はリクエストごとに数え直される

公式の定義は「リクエスト中の全search_resultブロックのうち」の位置です。会話全体で固定されたIDではなく、その1回のリクエストのメッセージ配列から導かれる値になります。

上の関数で確かめます。同じ会話から最初のユーザーメッセージ(事前取得済みの1件)を履歴から外して数え直すと、番号の振られ方が変わります。

python3 idx.py
-- 最初のユーザーメッセージを履歴から外したあと --
0 https://docs.company.com/pricing
1 https://docs.company.com/limits

pricingは1から0に、limitsは2から1に変わりました。古いメッセージを自動で消す構成では、これと同じことが起こります。Context editingとmemory toolを併用する長時間エージェント設計のように古いtool_resultを履歴から落とす仕組みと組み合わせるときも同じです。消えたブロックの分だけ、以降の番号が詰まります。

過去のレスポンスに含まれていたsearch_result_indexを、次のリクエスト後の参照先の特定に使い回すことはできません。番号は、それを返したレスポンスと、その元になったリクエストの組で読みます。

search_result_indexとstart_block_indexは階層が違う

紛らわしい2つのインデックスは、指す階層が異なります。

くらべる

引用に付く2種類のインデックス

外側の番号

search_result_index

リクエスト中のsearch_resultブロックのうち、どのブロックを引用したかを指します。供給経路をまたいで通し番号になります。

内側の範囲

start_block_index / end_block_index

引用先のsearch_resultのcontent配列の中で、どのtextブロックを引用したかを指します。end_block_indexは含まない終端で、start_block_indexより必ず大きくなります。

cited_textはcontent[start_block_index:end_block_index]を連結した全文で、出力トークンには数えられません。Claudeが引用の単位にするのはtextブロックで、ブロックの中の一部の文字列だけを引くことはありません。細かい粒度で引用させたいときは、1つのsearch_resultのcontentを小さなtextブロックに分けます。その設計はsearch_resultの引用粒度を細かくする方法で扱っています。

経路ごとの役割分担と、番号の検証のしかた

公式のベストプラクティスは、2つの経路に別の役割を割り当てています。ツール経由(Method 1)は、実行時に検索する動的なRAG向けです。返す結果は最も関連の強いものに絞り、コンテキストがあふれないようにします。直接指定(Method 2)は、すでに手元にある結果や、まとめて処理したい複数の結果向けです。

混在構成では、会話の先頭に直接指定した結果が若い番号を取り、ツールが返した結果が後ろに続きます。ツールの返す件数が変わっても、先頭の直接指定分の番号は動きません。

Method 2は、中身が分かっている内容で引用の動きを確かめる用途にも向くと書かれています。番号の対応を検証するときは、固定した2〜3件のsearch_resultと質問を直接指定で送ります。返ってきたsearch_result_indexと、上の関数が返す配列の位置が一致するかを見れば、数え方の理解が合っているかを手元で確かめられます。

混在構成で守る制約

供給経路をまたぐ混在は自由ですが、ブロックの組み合わせには次の3つの制約があります。

制約

混在で効く3つの制約

  • tool_resultの内側

    search_resultが1つでも入っているtool_resultは、中身をすべてsearch_resultにします。textなど別の型を混ぜると検証エラーです。

  • citationsの設定

    リクエスト内のsearch_resultは、すべて有効かすべて無効のどちらかです。設定が異なるとエラーになります。

  • web検索ツールとの併用

    同じリクエストでweb検索ツールを有効にしているときは、すべてのsearch_resultでcitationsを有効にします。

ユーザーメッセージ直下では、search_resultはtextブロックのほか、画像やドキュメントのブロックと並べられます。厳しいのはtool_resultの内側だけです。

ツールの結果に補足のテキストを添えたいときは、いずれかのsearch_resultのcontent配列にtextブロックとして入れます。そのテキストも引用の対象になります。検索が失敗した、または0件だったときは、search_resultを返しません。返すのは{"type": "text", "text": "No results found."}のようなtextブロックだけです。このtool_resultにはsearch_resultが含まれないので、上の制約には当たりません。公式のベストプラクティスにある書き方で、Claudeは空の結果をユーザーに説明して会話が続きます。

citationsの既定は無効で、公式の例はすべて"enabled": trueを明示しています。既定が無効で、設定が異なるとエラーになるという2つの記述を並べると、設定を書き忘れたブロックが1つ混ざるだけで、有効にした他のブロックと食い違う扱いになります。ツール側と事前取得側で別々のコードがsearch_resultを組み立てているなら、両方の組み立て関数でcitationsを明示してください。

cache_controlは、公式の書式ではsearch_resultブロックのcitationsと並ぶ別のキーです。all-or-nothingが書かれているのはcitationsだけなので、キャッシュのブレークポイントはブロックごとに付けるかどうかを選べます。事前取得した大きなブロックにだけcache_controlを付け、ツール経由の結果には付けない、という構成も書式の上では成立します。キャッシュできる最小の長さはPrompt cachingの要件に従います。

使えるモデルと提供先

search_resultは標準のMessages APIの機能で、ベータヘッダーは要りません。Claude Haiku 3を除くすべての現行モデルが、引用つきのsearch_resultに対応しています。提供先はClaude API、Amazon Bedrock、Google Cloudです。

content配列に入れられるのはテキストだけで、画像などは入りません。ゼロデータ保持(ZDR)の扱いは、公式の「API and data retention」のページに書かれています。

まとめ

search_result_indexは経路を問わずリクエスト内の出現順で決まるため、送信する直前の配列を1本の出現順で持っておけば、引用の番号はそのまま参照に使えます。混在構成でつまずくのは番号よりも、tool_resultの内側の型とcitationsの設定のほうです。

この記事を共有:XはてブLinkedIn