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

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

ツール経由の動的なsearch_resultと、ユーザーメッセージ直接指定のsearch_resultは同一リクエストで混在できます。search_result_indexの通し番号の振られ方を扱います。

search_resultには2つの供給経路があり、同一会話で混在できる

Claudeのsearch_resultコンテンツブロックには、ツールが実行時に返す動的な経路(Method 1)と、ユーザーメッセージへ直接埋め込む事前取得済みの経路(Method 2)の2種類があります。この2つは同一の会話、同一のリクエストの中で混在させられます。前半の質問はユーザーメッセージに直接置いたsearch_resultで答え、後半の質問はツール呼び出しで取得したsearch_resultで答える、という組み方が公式に想定されています。

事前にキャッシュ済みの検索結果を先に渡しつつ、足りない情報だけをツールに探しに行かせる構成は、RAGアプリケーションで実際によく出るパターンです。混在自体は許可されていますが、実装で迷いやすいのは「どちらの経路の結果か」を引用側でどう区別するかという点です。

問題は、混在させたときにsearch_result_indexがどちらの経路のものかをどう区別しているかです。答えは単純で、経路を区別しません。全リクエストの中でsearch_resultブロックが出現した順に、0から通し番号を振るだけです。各方式単体の基本実装(コード例・citations.enabledの設定)はClaude RAGの構築ガイドのステップ3・4で扱っており、本記事は混在させたときの番号の振られ方だけに絞ります。

search_result_indexは出現順の通し番号 — 供給経路は無視される

公式ドキュメントの定義はこうです。search_result_indexは「リクエスト中に含まれる全search_resultブロックのうち、引用元になったブロックの0始まりのインデックス。全メッセージ・全tool_resultを横断した出現順」。Method 1由来かMethod 2由来かという区別は、この番号に一切現れません。

つまり、番号を振る対象は「search_resultという型のブロック」であって「どうやって取得したか」ではありません。ユーザーメッセージに直接書いたsearch_resultも、ツールが返したtool_result内のsearch_resultも、会話の中でメッセージが並ぶ順に等しくカウントされます。

この仕様が破綻なく成立する理由は、search_resultブロックの置き場所にあります。search_resultはユーザーロールのメッセージにしか置けません。ツール経由の結果もtool_resultとしてユーザーロールのターンに乗るため、直接指定の結果と同じロールの中に並びます。assistantロールのメッセージにsearch_resultを含めるとAPIはリクエストを拒否します。両方式が最終的に同じ「ユーザーロールのターン」という置き場所に集約されるからこそ、供給経路を無視した単純な出現順カウントが破綻せずに成立します。

実際に混在させると番号がどう振られるか

具体例で確認します。最初のユーザーメッセージに、事前取得済みの製品概要ページをsearch_resultとして直接埋め込みます。Claudeはこれを読んだうえで、価格情報を調べるためツール呼び出しを行い、tool_resultとして2つ目のsearch_resultを受け取ります。

// 1つ目: ユーザーメッセージに直接埋め込んだsearch_result
{ "role": "user", "content": [
  { "type": "search_result", "source": "https://docs.company.com/overview", "title": "Product Overview", "content": [...] },
  { "type": "text", "text": "製品概要と料金プランを教えて" }
]}
// 2つ目: ツール呼び出しのtool_result内にあるsearch_result
{ "role": "user", "content": [
  { "type": "tool_result", "tool_use_id": "toolu_01...", "content": [
    { "type": "search_result", "source": "https://docs.company.com/pricing", "title": "Pricing Plans", "content": [...] }
  ]}
]}

Claudeの回答では、製品概要への引用がsearch_result_index: 0、価格情報への引用がsearch_result_index: 1になります。リクエストに含まれるsearch_resultブロックが出現した順そのままです。ユーザーメッセージ直接指定が先に来たから0、ツール経由のtool_resultが後に来たから1、という単純な計算です。

実装側で引用元をUIに表示する場合、この番号を「送信したsearch_resultの配列インデックス」として扱えます。事前取得済みのものとツール取得のものを別々の配列で管理していても、Claudeへ送るリクエストの時点で両方を1本の出現順として扱っておけば、レスポンスのsearch_result_indexをそのまま該当ブロックの参照に使えます。逆に、事前取得済みの配列とツール取得の配列を送信後も別々にインデックス管理していると、search_result_indexと実装側の配列番号がずれる原因になります。

start_block_indexと混同しない — 2つのインデックスは階層が違う

search_result_indexと紛らわしいのがstart_block_index / end_block_indexです。この2つは指しているものが違います。

search_result_indexは「どのsearch_resultブロックが引用元か」を指します。一方start_block_indexend_block_indexは、そのsearch_resultcontent配列の中で「どのtextブロックが引用範囲か」を指します。1つのsearch_resultに複数のtextブロックを入れて分割している場合、後者の粒度制御が効いてきます。分割の設計そのものはsearch_resultの引用粒度を細かくする方法で扱っています。本記事が扱うsearch_result_indexは、あくまで「どの供給元か」を特定する外側の番号です。

tool_result内でsearch_resultと他の型は混在できない

供給経路をまたぐ混在は許容される一方、1つのtool_resultcontent配列の内側での混在は禁止です。あるtool_resultの1つのブロックでもsearch_result型なら、そのtool_result内の全ブロックがsearch_result型である必要があります。「検索結果はsearch_resultで返しつつ、補足コメントは普通のtextブロックで添える」という組み方は、同じtool_resultの中では成立しません。

テキストの補足を添えたい場合は、tool_resultに別のtextブロックを混ぜるのではなく、いずれかのsearch_resultcontent配列内にtextブロックとして入れます。そうすればそのテキストも引用対象になります。この制約は、混在パターン全体で唯一「ユーザーロール側の1メッセージ内」ではなく「1つのtool_resultの中」という、より狭い単位で効くルールです。範囲の広さが違う2種類の制約が重なっている、と整理しておくと迷いません。

番号はリクエストのたびに数え直される

search_result_indexは、会話全体を通じて固定された識別子ではありません。その1回のAPIリクエストに含まれるメッセージ配列を、毎回あらためて数え直した結果です。会話が進んで新しいsearch_resultが増えれば番号は増え続けますし、逆に古いメッセージが取り除かれれば、残ったsearch_resultだけで番号が振り直されます。

これは実務上、2つの注意点につながります。1つは、過去のレスポンスに含まれていたsearch_result_indexを、次のリクエスト後もそのまま参照先の特定に使い続けてはいけないという点。もう1つは、Context editingとmemory toolを併用する長時間エージェント設計で扱ったように、古いtool_resultを会話履歴から自動的に消す仕組みと組み合わせる場合、消えたsearch_resultの分だけ以降の番号が詰まるという点です。番号は各レスポンスの中で完結させて読むのが安全です。

混在させるときはcitations.enabledを全ブロックで揃える

供給経路の混在で唯一そろえる必要があるのが、引用設定です。citations.enabledはオール・オア・ナッシングです。リクエストに含まれる全search_resultブロックで有効にするか、全ブロックで無効にするかのどちらかしか許されません。ツール経由の1件だけ引用を有効にして、ユーザーメッセージ直接指定の1件を無効のままにする、といった部分的な設定は組めず、リクエストごとエラーになります。

この制約は、web検索ツールを同じリクエストで併用するときにも及びます。web検索が有効なリクエストでは、独自に用意したsearch_resultブロック側のcitations.enabledもすべて有効にしておく必要があります。異なる方式・異なる出所のsearch_resultを1つのリクエストに混ぜるほど、引用設定を後から個別に変える余地は狭くなる、と捉えておくと構成のミスに気づきやすくなります。

一方、cache_controlcitationsとは独立した設定で、オール・オア・ナッシングの制約を受けません。search_resultブロックごとに個別のキャッシュブレークポイントを付けるかどうかを選べます。混在構成を組むときは、引用設定は全ブロックで統一し、キャッシュ設定はブロックごとに個別判断する、という区別を意識しておくと設計で迷いません。

よくある質問

同じsearch_resultから複数回引用されたら番号はどうなるか

同じ番号が繰り返し現れます。search_result_indexは引用が発生した回数を数えるものではなく、その引用がどのsearch_resultブロックを指しているかを表す値です。同じ情報源を段落ごとに何度引用しても、指しているsearch_resultが同じであれば値も同じままです。

ツール呼び出しが複数回に渡ってさらにsearch_resultを返したらどうなるか

出現順に番号は増え続けます。1回目のツール呼び出しでsearch_result_index: 1まで使われていれば、2回目のツール呼び出しが返す新しいsearch_resultは2から始まります。供給経路が何度切り替わっても、リクエスト内の累計出現数がそのまま次の番号になります。ユーザーメッセージ直接指定→ツール呼び出し→再度ツール呼び出し、という順で3系統が混在しても、数え方の規則は変わりません。

まとめ

search_resultはツール経由の動的取得(Method 1)とユーザーメッセージ直接指定(Method 2)を同一リクエストで混在させられ、search_result_indexはその供給経路を区別せず、リクエスト中の出現順に通し番号を振ります。番号は会話を通じた固定IDではなく、そのリクエストのメッセージ配列を毎回数え直した結果である点、ブロック内の引用範囲を指すstart_block_indexとは階層が違う点、そしてcitations.enabledだけは全search_resultブロックで揃える必要がある点の3つを押さえておけば、混在構成での引用の追跡で迷わなくなります。番号そのものの計算は単純ですが、その単純さの裏には「search_resultはユーザーロールにしか置けない」という仕様が前提として存在しています。

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