GASからChatGPT APIを呼ぶのに要る部品は3つだけだ。APIキーの保管場所、UrlFetchAppでのPOST、返ってきたJSONから本文を取り出す処理。ここまでは数十行で動く。詰まるのはその先で、カスタム関数のまま数百行へ引き伸ばすと必ず壊れる。
- APIキーはコードに直接書かず、スクリプトプロパティに置く(ただし「安全になる」わけではない。理由は本文で書く)
- 最小構成は「1セル1回」。カスタム関数にする前に、テスト実行と実行ログで通す
- 数百行を処理するなら、カスタム関数ではなくメニューから範囲を回す作りに変える
GASとChatGPT APIをつなぐのに必要な部品は3つだけ
商品説明文を300SKUぶん書き換える、という仕事がある。多くの現場では、A列の商品名をコピーしてブラウザのChatGPTに貼り、出てきた文章を選択してシートのB列へ戻す、という往復で処理されている。1行あたり30秒として300行で2時間半かかる計算になる(実測ではなく、あくまで仮定の試算だ)。しかも途中で1行ずれても気づかない。この往復を、シートの中で完結させたい。そのための最小構成の話をする。
やることを部品に分解すると、必要なのは3つしかない。
| 部品 | 何をするか | どちら側の話か |
|---|---|---|
| APIキーの保管場所 | 「このリクエストは自分の課金アカウントのものだ」と証明する文字列を、シートを共有しても一緒には渡らない場所に置く | OpenAI側(キーの発行)+ Google側(保管) |
UrlFetchApp でのPOST |
指示文をJSONに詰めて、OpenAIのエンドポイントへ送る | Google側(GASの標準機能) |
| 返ってきたJSONの取り出し | レスポンスの中から、生成された本文だけを抜き出してセルに書く | Google側(コードで書く) |
この3つ以外は全部おまけである。「どのモデルを使うか」も「何行まとめて回すか」も、この3つが通ってから考えればいい話であって、最初に悩む場所ではない。
どこまでがGoogleの話で、どこからがOpenAIの話か
詰まる人の大半は、この境界が見えていない。先に切り分けておく。
- Google側: スプレッドシート、Apps Scriptエディタ、スクリプトプロパティ、
UrlFetchApp、実行ログ、実行時間の上限。ここで起きる問題は「権限承認で止まる」「時間切れ」「セルにエラーが出る」という形で現れる。 - OpenAI側: APIキー、支払い方法の登録、モデル、利用状況と請求、レート制限。ここで起きる問題は
401や429といった3桁のHTTPステータスコードで返ってくる。
症状を見て「これはどちら側の問題か」を先に決められると、切り分けが一気に速くなる。メッセージに3桁の数字が入っていればOpenAI側、セルに#ERROR!やLoading...が出ているならGoogle側。この粗い当たりの付け方で、実務ではだいたい足りる。
そもそもGASでやるべきなのか
先に言っておくと、この構成が向いているのは「1列を読んで、1列に書き戻す」形の作業だけだ。商品名から説明文、問い合わせ本文からカテゴリ、レビューから要約。入力と出力が1対1で、シート上に並んでいる。この形なら強い。
逆に、複数のシステムをまたぐ処理——RMSから受注CSVを落として、在庫を引き当てて、WMSへ出荷指示を投げる——をGASで組もうとすると、途中でエラー処理と再実行の設計に押しつぶされる。そこはOMSやiPaaSの領域で、自作の損益分岐点を超えている。何を自作して何を買うかの判断軸はEC業務の自動化ツールをどう選ぶかで整理した。受発注と在庫まわりの自動化全体の中でGASがどこに座るのかは受注・在庫業務の自動化のほうが見通しがいい。
ここから先は「GASでやると決めた人」向けの手順である。
手順1: APIキーを取得してスクリプトプロパティに保管する
APIキーはOpenAIの管理画面で発行する。発行ページの名称もメニューの位置も改定されるので、ここに画面名を書いても半年後には嘘になる。現行の管理画面でAPIキーの発行ページを探し、そこで作る、とだけ言っておく。支払い方法の登録が先に要るかどうかも変わりうるので、自分のアカウントで確認してほしい。
発行したキーは、その場で1度しか全文が表示されない作りになっていることが多い。コピーし損ねたら作り直せばいいだけだが、コピーしたキーを「とりあえずメモ帳」や「シートのZ列」に置くのは、この記事でいちばんやってほしくない置き方である。
コードに直書きしてはいけない理由は「危ないから」ではない
const key = 'sk-xxxxx'; と書けば動く。動くから、多くの人はそう書く。問題は、シートを人に渡した瞬間に起きる。
スプレッドシートを外注ライターやアルバイトに共有したとする。渡したつもりなのは「シートの中身」だが、実際に渡っているのはそのシートに紐づいたスクリプトでもある。編集権限がある相手は、拡張機能からApps Scriptエディタを開ける。開ければコードが読める。コードにキーが書いてあれば、キーが読める。読めたキーがその人のプロンプト実験に使われても、結果はこちらの請求書にしか出てこない。
つまりこれは倫理の話ではなく、「シートの閲覧権限」と「スクリプトの中身が見える範囲」が別物だという仕様の話だ。共有ボタンを押すとき、後者は視界に入らない。だから事故になる。
スクリプトプロパティへの入れ方
手順はこれだけだ。
- スプレッドシートで 拡張機能 > Apps Script を開く
- エディタ左側の歯車アイコン(プロジェクトの設定)をクリックする
- 下のほうにある「スクリプト プロパティ」で「スクリプト プロパティを追加」を押す
- プロパティ名に
OPENAI_API_KEY、値に発行したキーを貼って保存する
コード側からは1行で取り出せる。
const key = PropertiesService.getScriptProperties().getProperty('OPENAI_API_KEY');
これでコード本体からキーの文字列が消える。誰かに画面を見せても、スクリーンショットを撮っても、キーは写らない。
ただし「スクリプトプロパティに入れたから安全」ではない
ここは正確に書く。Apps Scriptの公式ドキュメントでは、スクリプトプロパティは「スクリプトのすべてのユーザーで共有される値」と定義されている。暗号化された金庫ではない。
実務上の意味はこうだ。シートの編集権限を持つ人は、Apps Scriptエディタを開いてプロジェクトの設定からキーの値を読める。直書きより「コピペやスクショで漏れにくい」だけであって、共有相手から隠せてはいない。「スクリプトプロパティに入れれば安全です」と書いてある記事は、この一点を飛ばしている。
だから運用としてはこう考える。
- キーを置いたシートは編集権限を絞る。外注先に渡すなら閲覧権限にするか、シート自体を分ける
- 不特定多数に配るテンプレートに、自分のキーを入れたまま複製させない
- キーは用途ごとに分けて発行し、漏れたと思ったらその1本だけ失効させる。1本を全用途で使い回すと、失効させた瞬間に全部止まる
- 誰にいつシートを共有したかを、シート内に「共有先」タブとして書き残しておく。棚卸しのときに効く
手順2: UrlFetchAppで1回だけ叩く最小のコード
ここが本題である。ただしその前に、シートを1枚だけ足しておく。
先に「設定」シートを作る
新しいシートを1枚追加して、名前を 設定 にする。A列にキー名、B列に値を入れるだけの表だ。
| A列 | B列 | |
|---|---|---|
| 1行目 | キー | 値 |
| 2行目 | model | (使うモデル名を書く) |
| 3行目 | system | あなたはECサイトの商品説明を書く担当者です |
モデル名をここに置く理由は後述するが、ひとことで言えばモデル名はいちばん先に古くなる情報だからだ。コードに埋め込むと、モデルが入れ替わるたびにエディタを開いて書き換えることになる。シートに置けば、シートを直せば済む。
コード全文
拡張機能 > Apps Script でエディタを開き、コード.gs の中身を全部消して、これを貼る。
// 「設定」シート A列=キー / B列=値 から値を1つ読む
function getSetting(key) {
const sheet = SpreadsheetApp.getActive().getSheetByName('設定');
if (!sheet) throw new Error('「設定」という名前のシートがありません');
const rows = sheet.getDataRange().getValues();
const hit = rows.find(function (r) { return r[0] === key; });
return hit ? String(hit[1]) : '';
}
// AIに1回だけ聞いて、本文の文字列だけを返す
function askAI(prompt) {
const key = PropertiesService.getScriptProperties().getProperty('OPENAI_API_KEY');
if (!key) throw new Error('スクリプトプロパティ OPENAI_API_KEY が未設定です');
const res = UrlFetchApp.fetch('https://api.openai.com/v1/chat/completions', {
method: 'post',
contentType: 'application/json',
headers: { Authorization: 'Bearer ' + key }, // キーはヘッダ。payloadに入れても通らない
payload: JSON.stringify({
model: getSetting('model'), // モデル名はシートから読む
messages: [
{ role: 'system', content: getSetting('system') },
{ role: 'user', content: prompt }
]
}),
muteHttpExceptions: true // これが無いとエラー本文が読めない(後述)
});
const code = res.getResponseCode();
const body = JSON.parse(res.getContentText());
if (code !== 200) {
throw new Error(code + ' ' + (body.error && body.error.message));
}
return body.choices[0].message.content.trim(); // 本文の取り出し口はここ一点
}
// まずこれを実行ボタンで1回だけ動かす
function testCall() {
Logger.log(askAI('次の商品名を30字の説明文にして: 綿100% 半袖Tシャツ 白 M'));
}
覚えておくべきは3行だけ
長く見えるが、実質的に効いているのは次の3行である。
headers: { Authorization: 'Bearer ' + key }—— キーはヘッダに入れる。payloadの中にapi_keyのような形で書いても認証されない。Bearerの後ろの半角スペースを落とすと、それだけで弾かれる。muteHttpExceptions: true—— 既定はfalseで、その場合HTTPエラーが返ると例外で止まり、レスポンス本文を読めないまま終わる。「エラーが出たけど何が悪いのか分からない」の最大の原因がこれである。trueにすると例外を投げずレスポンスを返してくるので、ステータスコードとエラーメッセージを自分で読める。body.choices[0].message.content—— 生成された本文の取り出し口。choicesの0番目、そのmessageのcontent。JSONの構造で迷ったら、ここだけ覚えておけばいい。
エンドポイントは https://api.openai.com/v1/chat/completions へのPOSTで、必須のパラメータは model と messages の2つだけである。temperature も max_tokens も、最初は要らない。
カスタム関数にする前に、必ずここで実行する
コードを貼ったら、エディタ上部の関数選択プルダウンで testCall を選び、実行ボタンを押す。この順序を飛ばして先に =GPT() を作ると、うまくいかなかったときに「コードが悪いのか、シート側が悪いのか」が切り分けられなくなる。
初回は権限承認のダイアログが出る。ここで手が止まる人が必ず出るので、先に書いておく。
- 「承認が必要です」→ 権限を確認
- Googleアカウントを選ぶ
- 「このアプリはGoogleで確認されていません」という警告が出る
- 左下の詳細をクリック → 「(プロジェクト名)に移動(安全ではないページ)」をクリック
- アクセス内容を確認して許可
この警告は「自分で書いたスクリプトが審査を受けていない」というだけの意味で、他人のアプリを承認しているわけではない。ただし、他人から共有されたシートで同じ画面が出たときは話が別だ。そのスクリプトが何をするか読んでから承認する。
実行が終わったら、エディタ下部に開く実行ログを見る(閉じてしまったら、左サイドバーの実行数から該当の回を選ぶ)。Logger.log の中身がここに出る。説明文らしき日本語が1行出ていれば、3つの部品は全部つながっている。
手順3: カスタム関数にする ── ただし向き不向きがある
ログで通ったら、セルから呼べるようにする。追加するのは9行だけだ。
/**
* セルの文字列をAIに投げて、返ってきた本文を返す
* @param {string} text 入力セル(A2 のようにセル参照で渡す)
* @param {string} instruction 指示文
* @return {string} 生成された本文
* @customfunction
*/
function GPT(text, instruction) {
if (!text) return '';
return askAI(instruction + '\n\n' + text);
}
これで、シート側からこう書ける。
=GPT(A2, "この商品名から、80字以内の商品説明文を作って。誇張表現は使わない")
@customfunction の行が入っていると、セルで =GPT と打ったときに補完候補として出てくる。無くても動くが、他の人に使わせるなら書いておいたほうがいい。
カスタム関数には、通常のスクリプトと違う制約がある
ここが、多くの記事が書かずに終わるところだ。カスタム関数は「セルから呼べる関数」ではあるが、通常のスクリプトと同じことができるわけではない。2026-08-11時点でApps Scriptの公式ドキュメントに明記されている制約のうち、EC実務で効いてくるものを挙げる。
| 制約 | 実務上どう出るか |
|---|---|
| 実行時間の上限が通常のスクリプトより短い(公式は30秒と記載) | 長い指示文や長文の入力を投げると、返ってくる前に打ち切られて#ERROR!になる |
| ユーザー認可が必要なサービスを呼べない | Gmail送信やカレンダー操作をカスタム関数の中に混ぜると、そこで落ちる |
| Spreadsheetサービスは読み取り専用 | カスタム関数の中から別のセルへsetValueできない。「処理済みフラグを立てる」がそもそも書けない |
| 再計算は引数の値が変わったときだけ | 後述。これがいちばん厄介 |
上限の秒数そのものは改定されうるので、数字を暗記する意味はない。覚えるべきは「上限がある前提で作る」という設計の側だ。上限が何秒かを知っていても、上限に当たる作りをしていれば結局止まる。
再計算されない、という罠
カスタム関数は引数として渡したセルの値が変わったときにだけ再計算される、と公式に書かれている。裏を返すと、こうなる。
=GPT("固定の文言")のようにセル参照を渡していない式は、二度と再計算されない。プロンプトを直しても結果が変わらず、「反映されない」と悩むことになる- 逆に、セル参照を渡している式は参照先を触るたびに再計算され、そのたびにAPIが呼ばれる。誤字を1文字直しただけでも呼ばれる
- 生成AIは同じ入力でも毎回同じ文章を返すとは限らない。再計算のたびに、承認済みだったはずの商品説明が別の文章に書き変わる
最後の1点はEC実務では致命的になりうる。上長が確認して「これでOK」と言ったB列の文言が、翌朝シートを開いたら別の文になっている。誰も書き換えていないのに変わる。原因が分からないまま、そのままモール側へアップロードされる、という事故の形が想像できるだろうか。
結論: カスタム関数は「試し打ち」用と割り切る
使い分けはこうなる。
| やりたいこと | カスタム関数 | メニューからの一括処理 |
|---|---|---|
| プロンプトを数行で試して精度を見る | 向く | 大げさ |
| 10行前後を1回だけ処理する | 使える | どちらでもよい |
| 数百行を処理する | 向かない | こちら |
| 結果を固定して残す | できない(再計算で変わる) | できる(値として書き込む) |
| 途中で止まっても続きから再開する | できない | できる |
プロンプトの精度をセル数行で確かめる。それが決まったら、一括処理に移す。カスタム関数の役目はそこまでだ。
数百行を処理すると壊れる。一括処理は別の作り方をする
ここが本記事の核である。=GPT(A2, ...) が1行で動いたとき、人は必ずそのセルの右下をつかんで300行下までドラッグする。そして画面がこうなる。
- 上から数十行に
Loading...が並んだまま、いつまでも回っている - ところどころ
#ERROR!が混ざる。しかも連続ではなく、まだら - いくつかのセルには文章が入っているが、明らかに途中で切れている
- シートを開き直すと、埋まっていたはずのセルがまた
Loading...に戻る
これは「うまく動かない」のではなく、その作り方の当然の帰結である。300個の関数が同時に走り、それぞれが外部APIを呼ぼうとし、順番待ちが発生し、上限に当たったものから落ちていく。まだらになるのは、落ちるタイミングがセルごとに違うからだ。
いちばん怖いのは、エラーではなく請求のほう
#ERROR! は目に見える。目に見えないのは呼び出し回数だ。カスタム関数は参照セルが変わるたびに再計算される。つまり、
- A列の商品名を並べ替えた → 300行ぶん再実行
- A列の誤字を5件直した → その5行が再実行
- 同じファイル内でシート(タブ)を複製して別案を作った → 複製先でも300行ぶん実行
「1回のつもりが何度も課金されている」という状態が、静かに積み上がる。しかも失敗した呼び出しであっても、リクエストとして送った入力ぶんは消費され得る。カスタム関数を従量課金のAPIに接続することの本質的な危険は、上限エラーではなくここにある。
作り直す: メニューから選択範囲を回す
方針を3つに変える。①実行するタイミングを人間が決める ②結果は値としてセルに書き込む ③途中で止まっても続きから再開できる。この3つを満たすと、コードはこうなる。前章までのコードに追記する形で貼れる。
// シートを開いたときにメニューを作る
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('AI処理')
.addItem('選択範囲を処理', 'runSelection')
.addToUi();
}
function runSelection() {
const sh = SpreadsheetApp.getActiveSheet();
const rng = sh.getActiveRange(); // 入力列(A列)を選択してから実行する
const rows = rng.getValues();
const top = rng.getRow();
const OUT = 3; // C列 = 出力
const FLAG = 4; // D列 = 処理済みフラグ
const t0 = Date.now();
let done = 0;
for (let i = 0; i < rows.length; i++) {
const src = String(rows[i][0]).trim();
if (!src) continue;
if (sh.getRange(top + i, FLAG).getValue() === 'done') continue; // 再開
// 上限に当たる手前で自分から止まる
if (Date.now() - t0 > 4 * 60 * 1000) {
SpreadsheetApp.getActive().toast(
(top + i) + '行目で中断しました。もう一度実行すると続きから再開します');
return;
}
try {
const out = askAI('この商品名から80字以内の商品説明文を作って: ' + src);
sh.getRange(top + i, OUT).setValue(out);
sh.getRange(top + i, FLAG).setValue('done');
done++;
} catch (e) {
sh.getRange(top + i, OUT).setValue('ERR: ' + e.message); // 1行落ちても全体は止めない
}
Utilities.sleep(500); // 連射しない
}
SpreadsheetApp.getActive().toast(done + '行を処理しました');
}
貼ったらシートを一度リロードする。上部のメニューバーに「AI処理」が増えるので、A列の処理したい範囲を選択してから AI処理 > 選択範囲を処理 を押す。
この作りの、どこが効いているか
| 書いた行 | それが防いでいること |
|---|---|
D列の done フラグを見て continue |
再実行しても処理済みの行を二度叩かない。中断からの再開が「もう一度押すだけ」になる |
4分で return して toast を出す |
実行時間の上限に当たって強制終了されるのを避ける。上限に当たると「何行目まで終わったか」が分からないまま落ちる |
try / catch で ERR: をセルに書く |
1行のエラーで全体が止まらない。あとで ERR でフィルタすれば、失敗行だけ選択して再実行できる |
Utilities.sleep(500) |
短時間に叩きすぎてレート制限に当たるのを緩和する。数値は環境次第なので、詰まるなら伸ばす |
| 出力をC列、フラグをD列に分ける | 入力(A列)を書き換えても出力が消えない。人がC列を手直ししても、フラグが立っている限り上書きされない |
速度と再開性は、どちらかしか取れない
このコードは1行ずつ setValue している。処理としては遅い。まとめて配列にため、最後に setValues で一括書き込みしたほうが速いのは事実だ。
ただし一括書き込みは、途中で落ちたときに、それまでの結果が丸ごと消える。250行ぶんAPIを呼んで課金だけ発生し、シートには1文字も残らない、という終わり方をする。
だからこう決める。試作段階と数百行規模では、遅くても行ごとに書く。速度が問題になるほど回数が増えたなら、そのときは行ごと書き込みのまま実行を分割する(100行ずつ選択して回す)ほうが、一括書き込みに変えるより安全だ。EC実務で失って困るのは数分ではなく、やり直しの判断コストのほうである。
なお、この「選択範囲を回して、フラグを立てて、失敗行だけ拾い直す」という骨格は、AIを呼ぶ処理に限った話ではない。受注メールの本文から住所や商品名を機械的に抜き出す処理でも同じ形になる。実例は受注メールから必要なデータだけを取り出すにまとめてある。
動かないときの切り分け手順
症状から原因へ最短で行くための対応表を置く。順番に上から見ていけばいい。
| シート上の見え方 | 起きていること | どこを見るか |
|---|---|---|
セルが Loading... のまま止まる |
カスタム関数が大量に同時実行され、順番待ちになっている | 行数を減らして再試行。根本的にはメニュー実行へ切り替える |
#ERROR!。セルのメモに Exceeded maximum execution time (line 0). |
カスタム関数の実行時間上限を超えた | 該当セルにカーソルを当ててメモを読む |
#ERROR! で You do not have permission to call ... |
ユーザー認可が必要なサービスをカスタム関数から呼んだ | 同じくセルのメモ |
| 一部の行だけ空欄/古い値のまま | 引数が変わっていないため再計算されていない | 式がセル参照を引数に渡しているか |
メッセージに 401 |
キー違い・削除済み・Authorizationヘッダが送られていない |
実行ログ。スクリプトプロパティの値も確認 |
メッセージに 429 |
レート超過、または残高切れ・支出上限到達 | 実行ログ + OpenAI側の利用状況ページ |
| メニュー実行が途中で落ちる | スクリプト実行の時間上限に到達した | エディタ左の「実行数」画面でステータスと時刻を確認 |
429 は2種類ある。ここを間違えると無限に待つ
実務でいちばん時間を溶かすのがこれだ。429 という同じコードで、意味の違う2つが返ってくる。
- レート超過: 短時間に叩きすぎた。少し待って再実行すれば通る。
Utilities.sleepを伸ばすか、選択範囲を分ける - 残高切れ・支出上限到達: 待っても直らない。リトライを何度書いても永久に通らない。OpenAI側の請求・利用状況を見て、支払い方法か上限設定を直すしかない
切り分けは、エラーメッセージの本文を読むことでしかできない。だから muteHttpExceptions: true が要る。これが無いと本文が読めず、「なんとなく429っぽい」から先へ進めない。throw new Error(code + ' ' + body.error.message) と書いておくと、この文字列がそのままセルの ERR: や実行ログに残る。
ログの読み方
見る場所は2つある。
- 実行ログ:
Logger.logで出した内容。手でtestCallを実行したときの確認に使う - 実行数(Executions): エディタ左のメニューにある一覧。いつ・どの関数が・何秒動いて・成功したか失敗したかが並ぶ。メニュー実行が途中で落ちたときは、まずここで「開始時刻と終了時刻の差」を見る。上限秒数に近い値で失敗しているなら時間切れ、一瞬で失敗しているならキーか設定シートの問題
切り分けの順番はいつも同じでいい。①実行数で成否と所要時間を見る → ②失敗していたら実行ログでメッセージを読む → ③3桁の数字があればOpenAI側、無ければGoogle側。この3ステップで、原因の当たりはほぼ付く。
費用の考え方と、陳腐化に強い作り
ここまでで動くものはできた。次に効いてくるのは、半年後に開き直したときに自分で直せる状態になっているか、である。この種の仕組みが止まる原因は、たいていバグではない。モデルの世代が変わった、料金の体系が変わった、上限の挙動が変わった——外側が動いたのに、こちら側がその値をコードに書き込んでいた、という止まり方をする。
コードに書いてよい値と、書いてはいけない値
線の引き方は単純でいい。自分の意思と関係なく変わる値は、コードに書かない。シート側に設定欄を作って、そこから読む。
| 設定シートに出す項目 | コードから出す理由 |
|---|---|
| モデル名 | 世代が変わるたびにスクリプトを開いて書き換えることになる。ここが最も頻繁に古くなる |
| プロンプトの本文 | 文言の改善は、コードを読めない現場の担当者のほうが速い。編集のたびに開発側を待たせない |
| 1回に処理する件数 | 上限に当たったとき、まず下げて様子を見る値。コード修正なしで調整できるようにする |
| 入力列・出力列・フラグ列 | 別の業務へ転用するときに、触るのがシートだけで済む |
| 料金を確認した日付 | 金額ではなく日付を残す。「いつ時点の前提で見積もったか」が分かれば、次に見直す判断ができる |
逆に、コード側に固定してよいのは手順そのものだ。キーをヘッダに載せる、返ってきたJSONから本文を取り出す、失敗行にフラグを立てる。ここは外側が変わっても形が変わらない。
費用は調べるものではなく、自分の使い方で測るもの
単価はここには書かない。書いた時点で古くなるうえ、読んだ人の使い方と一致しないからだ。代わりに、自分の数字を出す手順を置く。
- 現在の単価を公式の料金ページで確認する。モデルごとに、入力と出力で別々に設定されているのが通常である。ページはブックマークしておく
- 本番のうち10件だけ流す。APIの応答には使用量(入力・出力それぞれの消費量)が含まれるので、これを実行ログに書き出すか、シートの末尾列に残す
- 10件の実測を、処理したい件数に外挿する。300SKUなら30倍する
これで「300件でいくらか」が、推測ではなく自分のデータになる。商品説明文のように入力が短く出力も短い仕事と、問い合わせ全文を渡して要約させる仕事とでは、1件あたりの重さが桁で違う。他人の見積もりが当てにならないのはそのためだ。
実行時間や呼び出し回数の上限も同じ扱いでいい。秒数や回数を覚えても、変われば覚え直しになる。上限があるという前提だけを設計に織り込む——途中で止まり、もう一度押せば続きから走る。この形にしておけば、上限値が動いても作りは生き残る。
モデル名も料金も、半年後には変わっている。変わらないのは「どこを設定値として外に出しておくか」を判断できるほうで、この判断力がEC実務者にとってどう効くかはEC担当者がAI時代に学ぶべきスキルにまとめてある。
この構成でやってはいけないこと
最後に、避けるべきものを3つ挙げる。どれも「動くかどうか」ではなく、動いた後で困るかどうかの話である。
顧客の氏名・住所をそのまま渡さない
商品説明文の生成なら顧客情報は登場しない。問題は、同じ仕組みが便利だと分かった次の段階で、受注データや問い合わせ本文にそのまま向けたときだ。A列を丸ごと渡す作りになっているので、渡す必要のない列まで一緒に外へ出る。
対処は難しくない。外部へ送る前に、送らなくても仕事が成立する列を落とす。氏名を「顧客A」に置き換える、住所は都道府県だけ残す、といった処理をシート側で挟んでから渡す。具体的な手順は氏名と住所を渡さずに受注処理を速くする手順で書いた。
出てきた文章を、そのまま商品ページに載せない
生成された説明文は、事実確認を通っていない。素材・産地・寸法のような仕様は、元データに無ければ埋められてしまうことがある。効果や効能に関する表現も、書き手が意図しないまま強い言い方になることがある。表示に責任を負うのは出品者側であり、生成元ではない。
作りとしては、C列に生成結果、D列に人が直した確定版、と分けておく。出品に使うのはD列だけと決めておけば、確認を飛ばした行が混ざらない。
動いたシートを、編集権限のまま共有しない
スクリプトプロパティはスクリプトプロジェクトに紐づく。ファイルのコピーを配ってもキーは渡らないので、コピー先はキー未設定のまま止まる。ここは意外に安全側だ。
危ないのは逆で、同じファイルを編集権限のまま共有することである。編集できる相手はスクリプトの設定画面を開けるので、そこに入っているキーを読める。使われた分の請求は発行した側に来るし、誰がどれだけ使ったかは分からない。閲覧権限で足りる相手に編集権限を渡さない。人に渡すのはコードと手順書だけにして、キーは各自で発行してもらう。
よくある質問
- 無料で使えるか: GAS側はGoogleアカウントがあれば追加費用なしで使える。API側は従量課金で、無料枠の有無や条件は時期によって変わる。着手前に料金ページで現況を確認する
- Excelでも同じことができるか: 考え方は同じで、外部を呼ぶ口がOfficeスクリプトやVBAに置き換わるだけである。ただし実行環境と共有の前提が違うので、同じコードは動かない。ここで作った「設定はシート、手順はコード」の分け方はそのまま持ち込める
- エラーが出たまま放置するとどうなるか: セルに
#ERROR!が残っている間は実害がない。危ないのは空欄のまま気づかず次工程へ回ることだ。出力列とは別に生成日時の列を作り、空の行を数式で数えておくと、目視に頼らずに済む
ここまでが最小構成の全体である。部品は3つ、壊れるのは数百行に伸ばした瞬間、切り分けは実行数とログの順。まずは10行で1回通してみるところまでを、今日の範囲にするといい。どの列を渡してよくて、どの列は渡してはいけないか——その判断は、コードを書ける人ではなく、その業務を回している人にしかできない。


コメント