タイトル回収
OpenAIから$6,000の請求が来た!!!!!!!!!!
8/13~8/24の間でこんなに!?
66,000リクエスト!?
約十日だから、1日7,000弱のリクエスト数??
なんじゃこりゃ!
原因は、GAS
でした
こんにちは。
大阪のITベンチャー Sky Gird株式会社で
AIエンジニアとして働く青栁と申します
まさか人生初のQiitaの投稿が
社内での失敗になるとは全く予想していなかった....💦
皆さんに伝えたいこと
タイトルに釣られて見に来てくれた人はある程度察しがついてると思いますが
伝えたいことはこれ
- SaaS系のAPIを利用するときはちゃんと挙動確認をせよ!!!!!
- GASの関数を使うときは更新頻度に気をつけろ!!!!!!!!
- 課金が発生するサービスに対して、異変に気づける環境を作ろう!!!!!!!!
です。
この先何が起こっていたかを
コード付きで解説していくので是非ともお付き合いください🙇
しょうもな!って思った方はブラウザバック推奨です
何が起こったのか
背景
GASを使うことになったきっかけ
- 弊社の営業チームが使用するデータはスプレを使っていた
- 名刺の情報を溜め込んでおり、HPのリンクもあった
- はじめはスプレにある
IMPORTXML
関数を使ってサイトのタイトルを取得していた - しかし
- 取得した情報が役に立たない or そもそもエラーになる
というような感じで
なんかいい感じに一覧で会社情報まとめたい!という要望がありました。
当時の会話
営業さん
なんかいい感じに一覧で会社情報まとめたいねんけどどしたらいい?
青栁
そんなんめちゃ簡単ですやん!!AIにやらせたらいいですやん!
営業さん
そうなん??ほな任せたわ!
... 1日後 ...
青栁
できやした!サイトの要約なんかもしちゃって!名刺にない情報ものってるのでご活用くださいまし!(得意げ)
営業さん:
お!ありがとう!はやいやん!
青栁
それほどでも!!
(実際はGPT使って30分ほどしかコーディングしてないけど!AIサイコー!)
とまぁ当時はちょっと浮かれてました。
やったこと
step.1 コーディング
まずは助けてChatGPT。
要件は決まっていたので
その内容をGPTに投げつけて下記のコードをスクリプトに貼り付けました。
// javascript
/**
* @customfunction
*/
function scrapeHTML(url) {
// URLからHTMLを取得
var response = UrlFetchApp.fetch(url);
var html = response.getContentText();
// Step 2: OpenAI APIのエンドポイントとAPIキーの設定
var apiKey = "<実際はキーを入力>"
var apiUrl = 'https://api.openai.com/v1/chat/completions';
var prompt = "次に与える情報を要約してください。営業資料として使用するためわかりやすく簡潔に箇条書きでお願いします。日本語で必ずお願いします!: "
// Step 3: APIリクエストの作成
var payload = {
"model": "gpt-4o", // 適切なモデルを選択
"messages": [
{"role": "user", "content": prompt + html,}
],
"max_tokens": 300, // 要約の長さを制御
"temperature": 0.1 // 出力の多様性を制御
};
var options = {
'method': 'post',
'contentType': 'application/json',
'headers': {
'Authorization': 'Bearer ' + apiKey
},
'payload': JSON.stringify(payload)
};
// Step 4: APIリクエストの送信
var response = UrlFetchApp.fetch(apiUrl, options);
var jsonResponse = JSON.parse(response.getContentText());
// Step 5: 要約結果の取得と返却
var summary = jsonResponse.choices[0].message.content;
return summary;
}
これをAppScript
に突っ込むだけ
この記事では詳しいスクリプトの設定方法は割愛します
step.2 スプレッドシートで関数を呼び出し
GASで命名したscrapeHTML
をセルで呼びだし。
これだけ!
これによってサイトの要約
にスクレイプしたサイト情報が自動で表現できるようになりました
これくらいであれば15分くらいで構築できるので、GAS+ OpenAI API自体はおすすめです❤️
高額請求になった原因
スプレの仕様、関数の更新頻度を確認していなかったことが原因だと考えています
今回の件は、ログからしっかりデバックできず、半分推測になってます
推測の根拠は下記です
Geminiからの出力を抜粋
この場合の更新頻度は、手動更新が基本となります。つまり、スプレッドシート上で scrapeHTML 関数を含むセルを再計算させる操作を行わない限り、要約結果は更新されません。
ただし、このコードにはUrlFetchApp.fetch(url)
が含まれています。これは外部の URL からデータを取得する処理であり、この部分が揮発性関数のように振る舞う可能性があります。つまり、スプレッドシートの再計算時にUrlFetchApp.fetch(url)
が実行され、新しい HTML データを取得して要約を更新する可能性があります。
しかし、UrlFetchApp.fetch(url)
が毎回新しい HTML データを取得するとは限りません。キャッシュなどの影響で、前回取得した HTML データが再利用される場合もあります。
Qiitaでも注意を促す記事を発見しました
Geminiの回答と、記事を読み合わせて
状況証拠を鑑みると
関数の更新頻度を制御していなかったことが原因になりそうです
対策
1. 常用するSaaSサービスの料金監視
弊社 SkyGridはSlackを使用しています。
そこで、Mailをフックに料金アラートを任意のSlackチャンネルへ送信されるように設定をしました。
チャンネルには開発者が複数人所属しているためあとは、メールが届くタイミングを適切に設定する必要があります。
2. コードレビュー、設計レビューをしてもらう
今回は僕と、営業さんとの間だけでスピード感重視で
設計したため誰からもフィードバックをもらうことなくローンチしました。
思い返すとここがだいぶ不味かったなと思います。
GASだと侮っていた自分がいたなと反省しています。
ただ毎度レビューしてもらうってのは工数もかかってしまうので
自分用のレビューボットをローカルにおいても面白そうだなと思いました
まとめ
今回は失敗談をまとめました。
GASとOpenAIのAPIの組み合わせで高額請求になりましたが
結果的にはエンジニアとして非常に大切な経験をできたと思います。
皆さんはぜひお気をつけを。