GASでClaude APIのTool useを使い
AIにGAS関数を実行させる方法

「AIに要約させる」の次は「AIに調べさせて、動かす」です。Tool use(AIが自分で使う関数を選ぶ仕組み)を使い、Claudeにスプレッドシート検索やカレンダー登録といったGAS側の関数を呼ばせる方法を、動くコードで解説します。

|対象: GAS / Claude API / Tool use

Table of Contents

Tool useとは何か・何が変わるのか

通常のAI活用は「文章を渡して、文章を受け取る」だけです。そのため、AIは自分が知らないこと(自社の顧客マスタの中身など)には答えられません。 Tool use(ツール呼び出し。関数呼び出し/Function callingとも呼ばれます)は、この壁を越える仕組みです。

やることはシンプルで、APIに「使える関数の一覧」を一緒に渡しておくだけ。するとAIは、必要だと判断したときに 「search_customer{company: "山田商事"}で呼びたい」と返してきます。実際に関数を動かすのはAIではなく、あなたのGASコードです。結果を返してあげると、AIはそれを踏まえて回答を作ります。

社内データに答えられる

顧客マスタや在庫シートを検索させ、根拠のある回答を返せる

既存関数が資産になる

すでにあるGASの関数を、そのままAIの道具として登録できる

引数の抽出が不要

「山田商事の担当は?」から会社名を切り出す処理をAIが担う

実行権限は渡さない

AIは要求するだけ。動かすかどうかはGAS側が判断できる

AIに渡す「道具」を定義する

ツールの定義はname(関数名)、description(説明)、input_schema(引数の形をJSON Schemaで書いたもの)の3点セットです。

// AIに使わせる「道具」を定義する
// name / description / input_schema の3点セット
const TOOLS = [
  {
    name: "search_customer",
    description:
      "顧客マスタシートから会社名で顧客を検索し、担当者名と電話番号を返す。" +
      "顧客の連絡先を聞かれたときに使う。",
    input_schema: {
      type: "object",
      properties: {
        company: {
          type: "string",
          description: "検索する会社名(部分一致)",
        },
      },
      required: ["company"],
    },
  },
  {
    name: "create_event",
    description:
      "Googleカレンダーに予定を登録する。日時が確定した打ち合わせを登録したいときに使う。",
    input_schema: {
      type: "object",
      properties: {
        title: { type: "string", description: "予定のタイトル" },
        startAt: {
          type: "string",
          description: "開始日時(例: 2026-07-20T14:00:00+09:00)",
        },
        minutes: { type: "number", description: "所要時間(分)" },
      },
      required: ["title", "startAt", "minutes"],
    },
  },
];

精度を左右するのはdescriptionです。AIはこの説明文だけを頼りに「今このツールを使うべきか」を判断します。 何をする関数かだけでなく、どんなときに使うのかまで書くのがコツです。 各引数にも説明を付け、日時のように形式が決まっているものは例を添えておくと誤りが減ります。

ツールの実体をGASで書く

定義したツールを実際に動かす関数を用意します。ここはいつものGASのコードで、特別なことは何もありません。 ツール名で振り分ける入口の関数を1つ作っておくと、後の処理がすっきりします。

// ツールの実体。呼ぶのはAIではなく、このGASコード
function runTool(name, input) {
  switch (name) {
    case "search_customer":
      return searchCustomer(input.company);
    case "create_event":
      return createEvent(input.title, input.startAt, input.minutes);
    default:
      throw new Error("未知のツール: " + name);
  }
}

function searchCustomer(company) {
  const sheet = SpreadsheetApp.getActive().getSheetByName("顧客マスタ");
  const rows = sheet.getDataRange().getValues();
  const hits = rows
    .slice(1) // 見出し行を除く
    .filter((row) => String(row[0]).indexOf(company) !== -1)
    .map((row) => ({ company: row[0], person: row[1], tel: row[2] }));

  if (hits.length === 0) {
    return "該当する顧客は見つかりませんでした。";
  }
  return JSON.stringify(hits);
}

function createEvent(title, startAt, minutes) {
  const start = new Date(startAt);
  if (isNaN(start.getTime())) {
    throw new Error("startAt の日時形式が不正です: " + startAt);
  }
  const end = new Date(start.getTime() + minutes * 60 * 1000);
  const event = CalendarApp.getDefaultCalendar().createEvent(title, start, end);
  return "登録しました。イベントID: " + event.getId();
}

返り値は文字列にします。表形式のデータはJSON文字列にして返すとAIが読み取りやすくなります。 「見つかりませんでした」といった結果も、エラーにせず文章で返すのがポイントです。AIはそれを読んで、別のキーワードで検索し直したり、ユーザーに確認を返したりできます。

toolsを付けてClaude APIを呼ぶ

Tool use専用のエンドポイントはありません。いつもの/v1/messagestoolsを1つ足すだけです。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: 2048,
    system:
      "あなたは社内アシスタントです。顧客情報や予定登録が必要なときは、" +
      "推測で答えず必ず用意されたツールを使ってください。",
    tools: TOOLS,
    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が手元の知識だけでそれらしく答えてしまうことがあります。muteHttpExceptions: trueは、エラー時に例外を投げさせず、ステータスコードと本文を自分で確認するための指定です。

レスポンスの形とstop_reasonの見方

AIがツールを使いたいときは、stop_reason(AIが話し終えた理由)が"tool_use"になります。これが処理を分岐させる合図です。

// tools を渡したときのレスポンス例(抜粋)
{
  "id": "msg_01...",
  "stop_reason": "tool_use",        // ← ツールを呼びたいという合図
  "content": [
    { "type": "text", "text": "顧客マスタを確認します。" },
    {
      "type": "tool_use",
      "id": "toolu_01AbCd...",       // ← tool_result で返すときに使うID
      "name": "search_customer",
      "input": { "company": "山田商事" }
    }
  ]
}

contentは配列で、テキストとツール要求が混ざって入ってきます。必要なのはtypetool_useのブロックで、nameが呼びたい関数名、inputが引数です。idは結果を返すときに必要になるので、必ず控えておきます。 なお、1回のレスポンスに複数の tool_use ブロックが入ることもあります(並列ツール呼び出し)。

tool_resultを返す往復ループ

ここが実装の中心です。「呼ぶ → ツール要求が来たら実行して結果を返す → また呼ぶ」を、ツール要求が来なくなるまで繰り返します。 守るべきルールは2つ。AIの発言(content)をそのまま履歴に積むことと、呼ばれたツールの結果を1つのuserメッセージにまとめて返すことです。

// 往復ループ本体。stop_reason が "tool_use" の間だけ回す
const MAX_TURNS = 5; // GASの6分制限を意識して必ず上限を決める

function askAssistant(userText) {
  const messages = [{ role: "user", content: userText }];

  for (let turn = 0; turn < MAX_TURNS; turn++) {
    const res = callClaude(messages);

    // ツール要求がなければ、それが最終回答
    if (res.stop_reason !== "tool_use") {
      return res.content
        .filter((block) => block.type === "text")
        .map((block) => block.text)
        .join("");
    }

    // AIの発言をそのまま履歴に積む(tool_useブロックごと必要)
    messages.push({ role: "assistant", content: res.content });

    // 呼ばれたツールをすべて実行し、結果を1つのuserメッセージにまとめる
    const results = res.content
      .filter((block) => block.type === "tool_use")
      .map((block) => {
        try {
          return {
            type: "tool_result",
            tool_use_id: block.id,
            content: String(runTool(block.name, block.input)),
          };
        } catch (e) {
          return {
            type: "tool_result",
            tool_use_id: block.id,
            content: "エラー: " + e.message,
            is_error: true,
          };
        }
      });

    messages.push({ role: "user", content: results });
  }

  return "ツールの往復が上限に達しました。質問を分けて試してください。";
}

tool_use_idには、AIが返してきたtool_useブロックのidをそのまま入れます。ツールが失敗したときはis_error: trueを付けて理由を返します。呼ばれたツールの結果を1つでも返し忘れるとリクエストが不正になるため、失敗しても必ず全件返してください。 往復回数の上限(MAX_TURNS)も必須です。GASには6分の実行時間制限があるため、上限なしのループは危険です。

カスタムメニューから使う

あとは入口を用意するだけです。スプレッドシートのカスタムメニューから質問できるようにすると、社内の誰でも使えるようになります。

// 使い方:カスタムメニューから呼ぶ例
function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu("AIアシスタント")
    .addItem("質問する", "showPrompt")
    .addToUi();
}

function showPrompt() {
  const ui = SpreadsheetApp.getUi();
  const res = ui.prompt("AIに質問", "例: 山田商事の担当者は?", ui.ButtonSet.OK_CANCEL);
  if (res.getSelectedButton() !== ui.Button.OK) return;

  const answer = askAssistant(res.getResponseText());
  ui.alert(answer);
}

// 実行例
// 質問: 「山田商事の担当者を教えて」
//   → AIが search_customer({company:"山田商事"}) を要求
//   → GASがシートを検索して結果を返す
//   → AIが「担当は佐藤様、電話は03-xxxx-xxxxです」と回答

実務での注意点

1. 取り返しのつかない処理はGAS側で止める

削除・送信・登録など元に戻せない処理は、AIの判断をそのまま実行しないでください。 実行の主導権はGAS側にあります。条件チェックを入れる、承認シートに書き出して人が確認してから動かす、といった防御を必ず挟みます。

// 取り返しのつかない処理は、実行前にGAS側で止める
function runTool(name, input) {
  switch (name) {
    case "search_customer":
      return searchCustomer(input.company);

    case "create_event": {
      // AIの判断を鵜呑みにせず、GAS側でルールを課す
      const start = new Date(input.startAt);
      if (start.getTime() < Date.now()) {
        return "過去の日時には登録できません。日時を確認してください。";
      }
      if (input.minutes > 480) {
        return "8時間を超える予定は登録できません。";
      }
      return createEvent(input.title, input.startAt, input.minutes);
    }

    default:
      throw new Error("未知のツール: " + name);
  }
}

2. ツールは少なく、説明は具体的に

ツールを増やすほど、AIはどれを使うか迷い、精度が落ちます。用途が近いツールは1つにまとめ、5〜10個程度に抑えるのが現実的です。 意図した場面で呼ばれないときは、まずdescriptionに「どんなときに使うか」を書き足してください。

3. 往復するぶんコストと時間が増える

Tool useは最低でも2回、ツールを2つ使えば3回とAPIを呼びます。会話履歴も往復ごとに長くなるため、入力トークンが積み上がります。 往復回数の上限を決めること、ツール定義とsystemを短く保つこと、そしてレート制限(429エラー)への自動リトライを用意しておくことが安定運用の鍵です。

まとめ

Tool useは、通常のMessages APIにtoolsを足し、stop_reasontool_useのときに関数を実行してtool_resultを返すだけの、素直な仕組みです。GASなら既存の関数がそのままAIの道具になり、「社内データを調べて動くAI」が現実的なコード量で作れます。 大事なのは、実行の主導権を必ずGAS側に残しておくこと。この一点を守れば、安全に業務へ組み込めます。

よくある質問

Claude APIに「使える関数の一覧」を渡しておくと、AIが必要だと判断したときに「この関数をこの引数で呼びたい」とリクエストを返してくる仕組みです。実際に関数を動かすのはAIではなく自分のコード(GAS側)です。GASなら、スプレッドシートの検索やカレンダー登録といった既存の関数をそのままAIに使わせられます。

使えます。Tool useは専用のエンドポイントではなく、通常のMessages API(/v1/messages)にtoolsパラメータを追加するだけです。GASからUrlFetchApp.fetchでJSONを送る書き方は変わりません。追加のライブラリも不要です。

ありません。APIが返すのは「この関数を呼びたい」という要求(tool_useブロック)だけで、実行するかどうかはGAS側のコードが決めます。削除や送信など取り返しのつかない処理は、実行前に条件チェックを入れる、あるいは確認シートに書き出して人が承認してから動かす設計にしてください。

AIの返答(assistantロール)をそのまま会話履歴に積んだうえで、userロールのメッセージとしてtool_resultブロックを送ります。tool_use_idにはAIが返してきたtool_useブロックのidをそのまま入れます。複数のツールが同時に呼ばれた場合は、1つのuserメッセージにtool_resultをまとめて入れるのが正しい形です。

tool_resultにis_error: trueを付け、contentにエラー内容を文章で入れて返します。AIはそれを読んで別の引数で呼び直したり、ユーザーに確認を求めたりします。失敗したツールのtool_resultを返さずに省略すると、リクエストが不正になりエラーになります。

Tool useはAPIとの往復が複数回発生するため、1回の実行が長くなりがちです。往復回数の上限(例: 5回)を必ず決め、ツール側の処理も軽く保ってください。長い処理が必要なら、ツールは「キューに積むだけ」にして、実処理は別トリガーに逃がす設計が安全です。

AI×GASの業務自動化を
相談する。

社内データを調べて動くAIアシスタントの構築から、既存のGAS資産へのAI組み込みまで、実務で使える形でご相談いただけます。

AI×GAS業務自動化を見る