GASでClaude APIのWeb検索ツールで
最新情報を回答させる方法
AIは学習した時点より新しいことを知りません。web_search(AI自身にWebを検索させるサーバー側ツール)を使えば、toolsを1つ足すだけで、最新情報を出典付きで答えさせられます。GASからの実装を動くコードで解説します。
Table of Contents
サーバー側ツールとは何か・Tool useとの違い
AIは学習した時点までの知識しか持ちません。ですから「先週の発表内容」を聞いても答えられませんし、悪いことに、それらしい嘘を返すこともあります。 これを解決するのがWeb検索ツールです。
ポイントは検索を実行するのがAnthropicのサーバーだという点です。これをサーバー側ツールと呼びます。 自作のツールを使わせるTool useでは、AIは「この関数を呼びたい」と要求するだけで、実際に動かすのは自分のGASコードでした。Web検索ツールにその往復はありません。 GAS側は結果を受け取るだけで、検索処理を書く必要も、検索用のAPIを別途契約する必要もありません。
実装は tools に1つ足すだけ
検索処理を書かない。専用エンドポイントもベータヘッダーも不要
出典が付いてくる
どのページを根拠にしたかURLで確認できる。社内利用でも説明できる
クエリはAIが考える
質問から検索語を組み立て、必要なら複数回検索して裏を取る
検索APIの契約が不要
検索サービスとの個別契約やキー管理が要らない
Web検索ツールを定義する
自作ツールと違い、input_schema(引数の形)を書く必要はありません。使うツールの種類を指定するだけです。
// Web検索ツールの定義。これだけでAIがWebを検索できるようになる
const WEB_SEARCH_TOOL = {
type: "web_search_20260209", // ツールのバージョン。対応モデルと対で決まる
name: "web_search",
max_uses: 5, // 1リクエストあたりの検索回数の上限(必須級)
};typeの末尾の数字はツールのバージョンです。ここが重要で、バージョンごとに対応モデルが決まっています。web_search_20260209は Claude Opus 4.8 / 4.7 / 4.6、Claude Sonnet 5 / 4.6 で使えます。古いモデルを使う場合はツールのバージョンも古いもの(web_search_20250305)になるため、モデルとツールはセットで確認してください。
max_usesは1リクエストあたりの検索回数の上限です。Web検索には検索実行分の料金が別途かかるため、必ず指定してください。
GASから呼ぶ最小コード
呼び出し方はいつものClaude APIと同じです。/v1/messagesにtoolsを足すだけで、UrlFetchAppの書き方は変わりません。APIキーはコードに直接書かず、PropertiesService(GASに値を安全に保存する機能)に入れておきます。
// Claude APIを1回呼ぶ。tools に検索ツールを入れるだけ
const API_URL = "https://api.anthropic.com/v1/messages";
const MODEL = "claude-opus-4-8";
function callClaude(messages) {
const apiKey = PropertiesService.getScriptProperties()
.getProperty("ANTHROPIC_API_KEY");
const payload = {
model: MODEL,
max_tokens: 4096,
system:
"あなたは調査アシスタントです。最新の情報が必要な質問には、" +
"記憶で答えず必ずWeb検索してから回答してください。" +
"分からないことは分からないと答えてください。",
tools: [WEB_SEARCH_TOOL],
messages: messages,
};
const res = UrlFetchApp.fetch(API_URL, {
method: "post",
contentType: "application/json",
headers: {
"x-api-key": apiKey,
"anthropic-version": "2023-06-01",
},
payload: JSON.stringify(payload),
muteHttpExceptions: true, // エラーを例外にせず、自分でコードと本文を確認する
});
const code = res.getResponseCode();
if (code !== 200) {
throw new Error("Claude API エラー(" + code + "): " + res.getContentText());
}
return JSON.parse(res.getContentText());
}systemに「記憶で答えず必ず検索する」と明記するのがコツです。検索ツールを渡しても、AIが「自分は知っている」と判断すると検索せずに答えてしまうことがあります。 最新情報が命の用途では、検索を促す一文を必ず入れてください。
レスポンスの形と出典の在りか
自作ツールのときと違い、GAS側が何かを実行して返す必要はありません。検索は済んだ状態で、結果ごと1回のレスポンスに入ってきます。
// Web検索が走ったときのレスポンス例(抜粋)
{
"stop_reason": "end_turn",
"content": [
{ "type": "text", "text": "調べます。" },
{
"type": "server_tool_use", // ← AIが実行した検索(クエリが見える)
"id": "srvtoolu_01...",
"name": "web_search",
"input": { "query": "Claude API 最新モデル 2026" }
},
{
"type": "web_search_tool_result", // ← 検索結果。ここに出典が入る
"tool_use_id": "srvtoolu_01...",
"content": [
{
"type": "web_search_result",
"url": "https://example.com/blog/...",
"title": "○○を発表しました"
}
]
},
{ "type": "text", "text": "検索結果によると..." } // ← 最終回答
]
}見るべきブロックは3種類です。server_tool_useにはAIが実際に投げた検索クエリが入っています。狙い通りの検索語かを確認できるので、デバッグに役立ちます。web_search_tool_resultが検索結果で、ここに出典のURLとタイトルが並びます。そしてtextが最終的な回答です。
回答テキストと出典URLを取り出す
content配列を種類ごとに拾い分けます。ここで必ず入れてほしいのが、検索エラーの判定です。
// 回答テキストと出典URLを取り出す
function extractAnswer(res) {
const text = res.content
.filter((block) => block.type === "text")
.map((block) => block.text)
.join("");
const sources = [];
res.content
.filter((block) => block.type === "web_search_tool_result")
.forEach((block) => {
// 成功時は配列、エラー時は error_code を持つオブジェクトが入る
if (!Array.isArray(block.content)) {
Logger.log("検索エラー: " + block.content.error_code);
return;
}
block.content.forEach((result) => {
sources.push({ title: result.title, url: result.url });
});
});
return { text: text, sources: sources };
}検索が失敗しても、HTTPステータスは200のまま返ってきます。例外は飛びません。エラーのときはcontentが検索結果の配列ではなく、error_codeを持つオブジェクトに変わります(検索回数の上限に達した場合のmax_uses_exceededなど)。 上のコードのように配列かどうかで分岐しないと、エラーに気づかないまま出典ゼロの回答を正常扱いしてしまいます。
pause_turnで中断されたら再開する
サーバー側ツールを使うと、stop_reasonが"pause_turn"で返ることがあります。エラーではありません。サーバー側で「検索する→考える」を繰り返す回数が上限に達し、いったん中断した合図です。
// pause_turn に対応した呼び出しループ
const MAX_CONTINUATIONS = 3; // GASの6分制限を意識して上限を決める
function research(question) {
const messages = [{ role: "user", content: question }];
for (let i = 0; i <= MAX_CONTINUATIONS; i++) {
const res = callClaude(messages);
if (res.stop_reason !== "pause_turn") {
return extractAnswer(res); // 完了
}
// 中断された場合は、AIの発言をそのまま履歴に積んで再送するだけ。
// 「続けて」などのメッセージを足してはいけない。
messages.push({ role: "assistant", content: res.content });
}
throw new Error("再開の上限に達しました。質問を分けてください。");
}再開はとても簡単で、AIの発言(content)をそのまま履歴に積み、もう一度APIを呼ぶだけです。このとき「続けてください」といったユーザーメッセージを足してはいけません。サーバーは末尾のserver_tool_useを見て自動的に続きから再開します。余計な一文は、かえって話の流れを乱します。 再開回数の上限は必ず決めてください。GASには6分の実行時間制限があるため、上限なしのループは危険です。
ドメイン制限と回数制限で暴走を防ぐ
業務で使うなら、情報源は絞るべきです。信頼できるドメインだけを検索対象にすれば、まとめサイトや古いブログを根拠にした回答を防げます。
// 情報源を絞り、暴走を防ぐ設定
const WEB_SEARCH_TOOL = {
type: "web_search_20260209",
name: "web_search",
max_uses: 3,
// 信頼できるドメインだけに絞る(blocked_domains とは併用できない)
allowed_domains: [
"anthropic.com",
"developers.google.com",
],
// 地域を伝えると、ローカル情報の精度が上がる
user_location: {
type: "approximate",
country: "JP",
timezone: "Asia/Tokyo",
},
};allowed_domainsで許可するドメインを指定し、逆に除外したいときはblocked_domainsを使います。この2つは同時に指定できないため、どちらかを選んでください。user_locationで地域を伝えておくと、地域性のある調べもので精度が上がります。
スプレッドシートで一括リサーチする
ここまで来れば、あとはGASの得意分野です。調べたいことをシートに並べて、回答と出典を書き戻す形にすれば、競合の動向調査や取引先のニュースチェックをそのまま自動化できます。
// スプレッドシートの調査リストを一括で処理する
// A列: 調査したいこと / B列: 回答 / C列: 出典
function researchSheet() {
const sheet = SpreadsheetApp.getActive().getSheetByName("調査");
const rows = sheet.getDataRange().getValues();
const start = Date.now();
for (let i = 1; i < rows.length; i++) {
if (!rows[i][0]) continue; // 質問が空の行は飛ばす
if (rows[i][1]) continue; // 調査済みの行は飛ばす(再実行に強くする)
// 6分制限の手前で安全に打ち切る。残りは次回のトリガーに任せる
if (Date.now() - start > 4 * 60 * 1000) {
Logger.log("時間切れのため中断しました。次回の実行で続きから処理します。");
return;
}
try {
const result = research(rows[i][0]);
sheet.getRange(i + 1, 2).setValue(result.text);
sheet.getRange(i + 1, 3).setValue(
result.sources.map((s) => s.url).join("\n")
);
} catch (e) {
sheet.getRange(i + 1, 2).setValue("エラー: " + e.message);
}
SpreadsheetApp.flush(); // 途中で止まっても、ここまでの結果を確実に残す
}
}1件あたり検索と推論で時間がかかるため、6分の実行時間制限は現実的な脅威です。 経過時間を見て4分で自ら打ち切り、処理済みの行を飛ばす作りにしておけば、時間主導トリガーで毎朝動かすだけで、続きから安全に処理が進みます。
実務での注意点
1. 出典は必ず残す
検索したからといって、回答が正しいとは限りません。検索結果の読み違いは起こりますし、そもそも情報源が間違っていることもあります。 回答だけを書き戻す設計にすると、誰も裏を取れなくなります。出典URLを必ず隣の列に残し、重要な判断に使う前に人が確認できる状態にしてください。
2. コストは検索回数ぶん増える
Web検索には、通常のトークン料金に加えて検索実行分の料金がかかります。さらに検索結果の本文が入力トークンとして積み上がるため、 入力側の料金も普段より膨らみます。max_usesを必ず指定し、最新の料金はAnthropicの公式料金ページで確認してください。
3. 社外に質問文が出ることを意識する
検索クエリはAIが質問文から組み立てます。つまり、質問に含めた情報が検索語として外部の検索に渡る可能性があります。 未公開の商品名や個人情報を質問文に入れるのは避け、社内情報とWeb検索を組み合わせたい場合は、社内データの参照は自作のTool useに任せる、といった役割分担を検討してください。
まとめ
Web検索ツールは、toolsに1つ足すだけで「AIが知らないこと」に答えられるようにする仕組みです。検索を実行するのはAnthropic側なので、GASに書くのは結果を取り出す処理と、中断(pause_turn)からの再開だけ。 自作ツールのような往復ループは要りません。
実務で効くのは3点です。max_usesでコストを抑えること、allowed_domainsで情報源を絞ること、そして出典URLを必ず残すこと。この3つを守れば、毎朝の情報収集をGASに任せられます。
よくある質問
実行する人が違います。Tool use(自作のツール呼び出し)は、AIが「この関数を呼びたい」と要求し、実際に動かすのは自分のGASコードです。一方Web検索ツールはサーバー側ツールと呼ばれ、検索を実行するのはAnthropicのサーバーです。GAS側は結果を受け取るだけで、検索処理を書く必要も、検索APIを別途契約する必要もありません。
不要です。いつものMessages API(/v1/messages)にtoolsパラメータを1つ足すだけで、UrlFetchAppでの呼び出し方は変わりません。ベータヘッダーも追加のライブラリも要りません。ただしツールのバージョン文字列(web_search_20260209)は対応モデルが決まっているため、モデルとセットで確認してください。
できます。レスポンスのcontent配列にweb_search_tool_resultブロックが入り、その中に検索でヒットしたページのURLとタイトルが並びます。GAS側でこのブロックを拾ってシートに書き出せば、「AIの回答」と「根拠にしたページ」を並べて残せます。社内で使うなら出典は必ず残す設計をおすすめします。
ツール定義にmax_usesを指定すれば、1リクエストあたりの検索回数に上限をかけられます。さらにallowed_domainsで検索対象を信頼できるドメインだけに絞ることもできます。Web検索には検索実行分の料金が別途かかるため、max_usesは必ず指定してください。最新の料金はAnthropicの公式料金ページで確認してください。
エラーではありません。サーバー側で検索と推論を繰り返す回数が上限に達し、いったん中断した合図です。直前のやり取り(userメッセージとAIのcontent)をそのまま履歴に積んでもう一度APIを呼べば、続きから再開されます。このとき「続けて」といった追加のメッセージを足す必要はありません。
HTTPステータスは200のまま返ってくる点に注意が必要です。web_search_tool_resultブロックのcontentが、通常は検索結果の配列なのに対し、エラー時はerror_codeを持つオブジェクトになります。GAS側では例外に頼らず、contentが配列かどうかで分岐してエラーを検知してください。
AI×GASの情報収集自動化を
相談する。
競合の動向調査や業界ニュースの毎朝チェックなど、AIにWebを調べさせてシートに溜める仕組みを、実務で使える形でご相談いただけます。