0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

情シス向けAI APIコスト管理:GASで部門別予算枠を実装する

0
Posted at

複数のAI API(Gemini、Claude、OpenAIなど)を部門横断で利用する企業が増えています。この記事では、GWS(Google Workspace)環境を軸に、GAS(Google Apps Script)とスプレッドシートを組み合わせた部門別予算枠管理の設計パターンを解説します。

この記事を読んだほうが良い人

  • 社内でGemini / Claude / OpenAI APIを複数部門が利用しており、月次のコスト超過が繰り返されている情シス担当者
  • APIキーの発行・管理は始めたが、部門ごとの利用状況の把握まで手が届いていない方
  • GASやGoogle Sheetsを使った自動化の経験があり、コスト管理の仕組みを自社で作りたいと考えている方
  • APIコストの事前管理体制を整えたい情シス担当者

AI APIコスト管理で「後手対応」が起きるしくみ

開発部門がOpenAI APIを使い、営業部門がClaude APIで提案書自動生成を動かし、マーケ部門がGemini APIで画像生成を試している——という構造では、APIキーすら一元管理されていないケースが実態として多い。コスト超過が発覚するのは「月末にプロバイダから請求が来たとき」になります。

典型的な事故パターンは次の3つです。

  • バッチ処理の暴走: 開発者がGPT-4系モデルで大量ドキュメントを処理するスクリプトを動かし、数日でクレジットを使い切る。入力トークン数の見積もりが甘い場合に特に起きやすい
  • 複数部門の同時急増: 新機能の評価期間が重なり、複数部門が同時に高コストなモデルを試す。部門横断で予算総額が2〜3倍に膨らんでも誰も気づかない
  • APIキーの共用による帰属不明: 開発チームが1つのAPIキーを部門内外で使い回し、誰のどの処理がコストを出したか特定できなくなる

この後手構造を生む根本は次の2点です。

  • 計測点が分散している: 各プロバイダの管理コンソールにログインしないと使用量が見えない
  • 部門ごとの予算配分がない: 「使った分だけ払う」構造は理解できるが、誰がどれだけ使ってよいかが決まっていない

この2点を解消する設計フレームワークが本記事の核心です。

プロバイダ側制限とGAS側管理の役割分担

AI APIコスト管理を設計する前に、プロバイダ側でできることとGAS側でやることの境界を整理します。

プロバイダ側でできること

各プロバイダは、APIキー単位またはプロジェクト単位での上限設定やアラートを持っています。

  • Google Cloud(Vertex AI / Gemini API): Cloud Billingの予算アラート機能で、プロジェクト単位の支出が閾値を超えたときにメール通知を出せます。閾値は金額またはパーセンテージで設定でき、複数の通知ポイントを設けることも可能です
  • OpenAI: 組織設定から月次の利用予算とアラートしきい値を設定できます。予算に達した場合はメール通知とダッシュボードアラートが届きますが、APIへのアクセス自体は継続します。OpenAI はかつてのハードリミット(自動停止)機能を廃止しており、上限到達後も請求は継続することに注意が必要です
  • Anthropic(Claude): 組織アカウントで使用量のモニタリングが可能です。APIキー単位の消費量も管理画面で確認できます。アラート通知は Admin API を通じた実装も可能です

いずれもプロバイダの管理コンソールから設定でき、予算超過時に通知を受け取れます。ここは必ず最初に設定すべき「下限ガード」です。この設定なしにGAS側の管理だけ作っても、GASが止まったときに歯止めがなくなります。

GAS側で補う部分

プロバイダ側の制限だけでは、「部門ごとの予算割り当て」と「GWS連携(Slack通知・Sheets集計・Google Chat通知)」はカバーできません。以下の表で整理します。

管理の観点 プロバイダ側 GAS + Sheets 側
総使用量の上限 △(通知のみ)※ △(代替実装)
部門別の予算枠 ✕(プロバイダに概念なし) ◎(Sheetsで自由設計)
Slack / Chat 連携 △(メールのみが多い) ◎(Webhook連携)
複数プロバイダの横断集計 ✕(各社サイロ) ◎(集約ダッシュボード)

※ OpenAI は 2024 年頃にハードリミット(API 自動停止)を廃止しており、現在は通知のみの対応となっています。

プロバイダ側のネイティブ機能でリスクの下限を抑え、GAS側で部門別の予算管理と通知をかぶせる二層構造が現実的な設計です。どちらか一方だけでは穴が生まれます。

部門別予算枠の設計の考え方

「どう分けるか」の前に「何に基づいて分けるか」を決める必要があります。

予算配分の3パターン

実務でよく採用されるパターンは次の3種類です。

  1. 均等配分: 月次予算総額を部門数で割る。シンプルで説明しやすいが、利用量に差がある場合に不公平感が出る
  2. 利用実績ベース: 直近3ヶ月の実績コストを参考に配分する。実態に合いやすいが、新部門の扱いに困る
  3. プロジェクトベース: AIを使う業務プロジェクトごとに予算を割り当てる。粒度が細かく管理しやすいが、メンテナンスコストが高い

100名規模企業では、初期は「均等配分+利用実績の3ヶ月後見直し」という組み合わせが運用しやすいです。最初から完璧な配分を目指すより、まず動かして実績を見てから調整するサイクルを作ることが大切です。

予算金額の目安は、利用するモデルと処理量によって大きく異なります。GPT-4o系やClaude 3.5 Sonnet系は軽量モデルの10〜30倍のコストがかかるケースもあります。最初の2〜3ヶ月は「記録だけして予算設定はしない」試走期間を設けることを推奨します。予算感覚のないまま枠を決めると、厳しすぎる制限で業務が止まるか、緩すぎて管理の意味がなくなります。

APIキーの払い出し設計

部門別管理を機能させるには、APIキーと部門の対応関係を確立する必要があります。最も確実な方法は、部門ごとにAPIキーを分けて払い出すことです。

1つのキーを複数部門が共有するとコストの帰属が分からなくなります。管理台帳(Sheetsで十分)にキーID・払い出し部門・担当者・用途・作成日・有効期限を記録し、情シスが一元管理します。

APIキー管理台帳のシート列の例です。

列名 内容
key_id APIキーの識別子(末尾4文字など)
provider OpenAI / Anthropic / Google Cloud など
department 払い出し先部門名
owner 管理担当者のメールアドレス
purpose 用途(例:提案書生成Bot)
created_at 発行日
expires_at 有効期限(設定している場合)
status active / suspended / revoked

有効期限は6ヶ月〜1年を設定し、期限が来たら情シスと部門担当者が利用状況を確認した上で延長か廃止を判断します。使われていないキーをそのままにすると、退職した社員の端末に残ったキーが悪用されるリスクが残ります。キーの棚卸しは、APIコスト管理と同時にセキュリティ管理にも直結します。

GASで実装する監視・通知の骨格

ここでは「APIキーが部門ごとに分かれている」前提で、GASによる監視の実装パターンを示します。

設計の骨格は3ステップです。

  1. 各部門のAPI使用量をGoogle Sheetsに集約する
  2. 集約データを予算マスタと比較する
  3. 閾値を超えた部門にSlack通知を送る

Sheets構成

2つのシートを用意します。

  • usageシート: datedepartmentproviderapi_key_idestimated_cost_jpy の列構成
  • budgetシート: departmentmonthly_budget_jpyalert_threshold_pct の列構成

estimated_cost_jpy はプロバイダのAPIレスポンスに含まれるトークン使用量とレートから計算した推定円換算コストを記録します。あくまで「傾向を把握するための近似値」として扱い、実績確認はプロバイダの請求書ベースで月次に行います。

GASスクリプト(骨格)

「usageシートの今月分を集計し、budgetシートの閾値と比較してSlack通知を送る」GASコードの骨格です。自社の予算金額・Slack Webhook URL・通知文言を変えるだけで動かせるように設計しています。

// Slack Incoming Webhook URLはスクリプトプロパティで管理する
const SLACK_WEBHOOK_URL = PropertiesService.getScriptProperties()
  .getProperty('SLACK_WEBHOOK_URL');

// メイン関数:時間ベーストリガーから定期実行する(例:毎朝9時)
function checkBudgetAndNotify() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const usageSheet = ss.getSheetByName('usage');
  const budgetSheet = ss.getSheetByName('budget');

  // 今月の使用額を部門ごとに集計
  const monthlyUsage = aggregateMonthlyUsage(usageSheet);

  // 予算マスタを読み込み
  const budgets = loadBudgets(budgetSheet);

  // 各部門の使用率を計算してアラート判定
  for (const dept in monthlyUsage) {
    if (!budgets[dept]) continue;

    const usage = monthlyUsage[dept];
    const budget = budgets[dept].monthly_budget_jpy;
    const threshold = budgets[dept].alert_threshold_pct / 100;
    const ratio = usage / budget;

    if (ratio >= threshold) {
      const msg = buildAlertMessage(dept, usage, budget, ratio);
      sendSlackNotification(msg);
    }
  }
}

// 今月分の使用額を部門ごとに集計して返す
function aggregateMonthlyUsage(sheet) {
  const now = new Date();
  const thisMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;

  const data = sheet.getDataRange().getValues();
  const headers = data[0];
  const dateIdx = headers.indexOf('date');
  const deptIdx = headers.indexOf('department');
  const costIdx = headers.indexOf('estimated_cost_jpy');

  const result = {};
  for (let i = 1; i < data.length; i++) {
    const row = data[i];
    const rowMonth = row[dateIdx].toString().slice(0, 7);
    if (rowMonth !== thisMonth) continue;

    const dept = row[deptIdx];
    if (!result[dept]) result[dept] = 0;
    result[dept] += Number(row[costIdx]);
  }
  return result;
}

// 予算マスタをオブジェクト形式で返す
function loadBudgets(sheet) {
  const data = sheet.getDataRange().getValues();
  const headers = data[0];
  const deptIdx = headers.indexOf('department');
  const budgetIdx = headers.indexOf('monthly_budget_jpy');
  const thresholdIdx = headers.indexOf('alert_threshold_pct');

  const result = {};
  for (let i = 1; i < data.length; i++) {
    const dept = data[i][deptIdx];
    result[dept] = {
      monthly_budget_jpy: Number(data[i][budgetIdx]),
      alert_threshold_pct: Number(data[i][thresholdIdx])
    };
  }
  return result;
}

// Slackに送るメッセージを組み立てる
function buildAlertMessage(dept, usage, budget, ratio) {
  const pct = Math.round(ratio * 100);
  return `【AI API予算アラート】${dept} が今月予算の ${pct}% に達しました。\n使用額:¥${usage.toLocaleString()} / 予算:¥${budget.toLocaleString()}`;
}

// Slack Incoming Webhookにメッセージを送信する
function sendSlackNotification(message) {
  UrlFetchApp.fetch(SLACK_WEBHOOK_URL, {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify({ text: message })
  });
}

コードのカスタマイズポイント

このコードは骨格なので、次の3点を自社環境に合わせて調整します。

  • SLACK_WEBHOOK_URL の管理: GASのスクリプトプロパティに設定します。コード内にベタ書きすると、スクリプトを別のメンバーと共有したときにシークレットが漏れます。スクリプトプロパティはGASエディタの「プロジェクトの設定」から追加できます
  • buildAlertMessage の文言: 運用に合わせて変更します。部門ごとに異なるSlackチャンネルに送りたい場合は、budgetシートにSlack Channel IDの列を追加して sendSlackNotification に渡します
  • アラートしきい値の段階化: 80%でWarning、100%でCriticalのように2段階にすると、部門が自分で気づいて使い方を調整しやすくなります。buildAlertMessage の戻り値を {text, severity} のオブジェクトにして色分けするのも効果的です

トリガーの設定

checkBudgetAndNotify は時間ベーストリガーで定期実行します。GASエディタのトリガー設定から、時間主導型・時間ベースのタイマー・毎日・特定の時間帯(例:午前9時〜10時)を設定します。毎日1回の実行で十分な場合がほとんどです。コストが急増しやすい月初やプロジェクト評価期間中は、1日2回(午前・午後)に変更することも検討してください。

usageシートへのデータ投入はどうするか

このスクリプトはSheetsを読み取るだけです。Sheetsへの書き込みは別途設計が必要で、主な選択肢は次の3つです。

  1. 手動入力(最初の1〜2ヶ月): 利用部門の担当者が週次でAPIプロバイダの管理画面を確認してSheetsに記入します。自動化の前に「どの数字を見ればよいか」を全員が把握する効果があります
  2. APIプロキシ経由: GASで自作のAPIプロキシを用意し、AI APIへのリクエストをすべてここ経由にします。プロキシがリクエストごとにコスト見積もりをSheetsに書き込みます。精度は高いですが実装コストがかかります
  3. プロバイダAPIから定期取得: 各プロバイダが提供する使用量取得エンドポイントを定期的に叩き、Sheetsに書き込みます。OpenAI・Anthropicともにこのエンドポイントを提供しており、GASのURLFetchAppから呼び出せます。なお、Anthropicの場合はAdmin API Key(sk-ant-admin...形式)が必要です。通常のAPIキーとは別に、管理コンソールの Settings > Admin Keys から発行し、別管理にしてください

100名規模では、まず手動入力から始め、慣れてきたらプロバイダAPIからの定期取得に切り替える段階的アプローチが現実的です。仕組みが運用として定着してから自動化する順序のほうが、作ったまま使われないダッシュボードになるリスクを減らせます。

注意点:コスト計算の精度

GAS側でトークン数からコストを推定する場合、為替レートの変動とプロバイダの価格改定が計算にズレを生みます。このGASはあくまで「アラートのためのリアルタイム近似値」として位置づけることが重要です。実績確認は月次でプロバイダの請求書と突き合わせ、乖離が大きい場合はレート計算ロジックを見直します。「会計数値の代替」ではないことを関係部門と最初に合意しておかないと、後で「GASの数字と請求書が違う」というクレームになります。

導入前チェックリスト

実装を始める前に、以下を確認します。

情報整理(情シス側)

  • 社内で利用されているAI APIプロバイダと、使用中のAPIキーの一覧が手元にある
  • 各APIキーが誰の権限で発行され、誰が管理しているか把握できている
  • 月次の許容コスト総額が経営側と合意できている
  • 部門ごとの予算枠に関して、部門長の合意を得るプロセスが設計されている
  • 試走期間(記録のみ)として最低2ヶ月分の実績データ収集の計画がある

技術前提(GAS実装)

  • GASプロジェクトを作成できるGWSアカウントがある
  • Slack Incoming Webhookの発行権限がある、またはシステム管理者に依頼できる
  • GASの時間ベーストリガーを設定できる
  • Google Sheetsへの読み書き権限が確保されている
  • GASのスクリプトプロパティにWebhook URLなどのシークレットを格納する方法を理解している

運用ルール設計

  • usageシートへのデータ投入方法(手動 or 自動)が決まっている
  • アラートが飛んだときの対応フロー(誰がどう動くか)が文書化されている
  • 予算を超過した場合のAPIキー無効化手順が決まっている
  • GASの集計値とプロバイダ請求書の月次突き合わせ担当者が決まっている

まとめ:月末サプライズを設計で防ぐ

AI APIのコスト管理は、仕組みを作らない限り「月末に数字を見て驚く」サイクルから抜け出せません。

技術的に難しい取り組みではありません。GASとGoogle Sheetsの組み合わせで、部門別の予算枠設定・使用量の集計・Slackへのアラート通知を実装できます。プロバイダ側の上限設定とGAS側の部門別管理を二層で組み合わせる構造が、費用対効果の高い設計です。

今週中にできる最初の3ステップを挙げます。

  1. 各プロバイダの管理コンソールで予算アラートを設定する: OpenAI・Anthropic・Google Cloudのいずれも管理コンソールからアラート通知を設定できます。OpenAI はハードリミット(API 自動停止)を廃止しているため、通知が届いた後に迅速に対応できる体制も合わせて設計してください
  2. 社内で使われているAPIキーをすべてリストアップする: 把握できていないキーがある段階では部門別管理は始まりません。情シスが主体的に各部門に確認し、新規発行は情シス経由に一元化するルールを設けます
  3. Google Sheetsで月次の利用実績を手動記録し始める: 自動化は後回しでよいです。まず2〜3ヶ月の実績を手元に持つことで、部門ごとの予算配分の根拠が生まれます

社内にGASを書ける人材がいれば、このチェックリストを片手に棚卸しから着手することで、3ヶ月後には「月末サプライズ」を構造的に防ぐ体制が整います。

コーポレートITのご相談はお気軽に

この記事で書いたような業務改善・自動化の設計から実装まで、DRASENASではコーポレートITの現場に寄り添った支援を行っています。
「まず相談だけ」でも大歓迎です。DRASENAS 公式サイトからお気軽にどうぞ。


※ この記事は DRASENAS Blog の転載です。オリジナルではコードや設定手順が随時アップデートされています。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?