業務自動化

GAS×OpenAI APIで問い合わせ・アンケートを自動要約 月5時間の集計を15分に

問い合わせフォーム・顧客アンケート・社内サーベイを運用する中小企業/個人事業主 の業務自動化レシピ
業種問い合わせフォーム・顧客アンケート・社内サーベイを運用する中小企業/個人事業主
ツールGoogle Apps Script
難易度★★☆ 中
設定時間約 50 分

問い合わせフォームやアンケートの自由記述欄は、集めた後に誰も読み返さないまま溜まりがちです。本レシピは Google Apps Script (以下 GAS) から OpenAI API を呼び出し、スプレッドシートの回答を 1 行ずつ「60 字の要約」と「6 分類のタグ」に変換する仕組みを、そのまま動くコード付きで構築します。API キーはコードに書かず安全に保管します。

読了目安 9 分 / 設定目安 50 分 / API 費用の目安 月 ¥10〜¥250

編集長の見解: 顧客の声を集めること自体は、Google フォームを使えば無料で誰でもできます。実際に詰まるのは 集めた後の「読む・分類する・要点をまとめる」工程 です。ここは判断を伴う作業に見えて、実は「短くする」「箱に振り分ける」という定型処理の塊です。だからこそ AI に任せる価値があります。しかも要約という用途なら最上位モデルは不要で、安価な小型モデルで十分に実用になります。初期構築 50 分と月数十円の費用で、放置されていた回答データが翌日から使える資産に変わります。

自由記述が「読まれないデータ」になる理由

問い合わせフォームや顧客アンケートを運用している事業者に共通する詰まりを整理します。

  1. 量が読み切れない: 1 件あたり数百字の自由記述が数百件溜まり、通読する時間が確保できない
  2. 分類が属人化する: 「これは不具合か要望か」の判断が担当者ごとにブレる
  3. 集計が手作業: スプレッドシートを目で追いながら、別シートに要約を打ち直している
  4. 鮮度が落ちる: 月次でまとめて処理するため、対応が必要な声に気づくのが 3 週間後になる
  5. 共有されない: 生の回答は長すぎて、経営会議の資料に載せられない

これらは「読んで、短くして、箱に入れる」という定型処理です。次のセクションで、完成する仕組みの全体像を示します。

完成形 (フロー図)

フォーム回答 → OpenAI で要約・分類 → 台帳に自動追記
📝
01
回答が届く
フォーム回答が1行追加
02
時刻トリガー
1時間ごとに自動起動
🤖
03
OpenAI に送信
未処理行だけを渡す
🏷
04
要約と分類
60字要約とタグを取得
05
シートに追記
処理済み行は再送しない

1 時間ごとに Google Apps Script が起動し、未処理の回答行だけをまとめて処理する

要点は 5 番目の「処理済み行は再送しない」 です。要約欄が空の行だけを対象にすることで、トリガーが回るたびに同じ回答へ課金される事故を防ぎます。次に、必要なものと費用を確認します。

必要なものと費用

必要なもの費用役割
Google アカウント¥0スプレッドシート・フォームの利用
Google Apps Script¥0自動化を動かす仕組み本体
OpenAI API のアカウント従量課金 (前払いクレジット制)要約・分類を担当する AI
Google フォーム (任意)¥0回答を集める入口

追加の月額固定費はありません。発生するのは OpenAI API の従量課金だけで、使った分だけの後払いではなく、事前にクレジットを購入して残高から消費する前払い方式 です[出典]。残高が尽きると処理が止まるため、上限が読めない請求に怯える必要はありません。

すでに OpenAI API の全体像や他の使い道を知りたい場合は、OpenAI API 中小企業の自社AI開発 月¥10,000規模のコスト試算と実装手順 を先にご覧ください。

要約に最上位モデルは要りません

「短くする」「決められた箱に振り分ける」という処理は、大規模言語モデル (LLM) にとって最も得意な部類の作業です。編集部が推奨するのは小型モデルで、フラッグシップと比べて 1/10 以下の費用で運用できます。まず小型モデルで試し、要約の質に不満が出た場合だけ上位モデルへ切り替える順序が経済的です。

では、どのモデルを選ぶと月いくらになるのか。次のセクションで公式単価をもとに試算します。

モデル選びと費用の試算

OpenAI API はモデルごとに 100 万トークン (AI が文章を処理する単位。日本語ではおおむね 1 文字 = 1 トークンが目安) あたりの単価が決まっています。本レシピに関係する選択肢は以下のとおりです[出典]

モデル入力 100 万トークン出力 100 万トークン編集部の評価
gpt-5.4-nano$0.20 (約 ¥30)$1.25 (約 ¥188)★★★★★ 本レシピの推奨。要約精度と価格の均衡が良い
gpt-5-nano$0.05 (約 ¥8)$0.40 (約 ¥60)★★★★ 最安。件数が数千件を超えるならこちら
gpt-4o-mini$0.15 (約 ¥23)$0.60 (約 ¥90)★★★★ 実績の長い定番。既存資産があるなら
GPT-5.4 (フラッグシップ)$2.50 (約 ¥375)$15.00 (約 ¥2,250)★★ 要約用途には過剰

円換算は $1 = ¥150 で編集部が算出した目安です。為替により変動します。

推奨モデル gpt-5.4-nano を使い、1 件あたり入力 400 トークン (指示文 200 + 回答本文 200)、出力 60 トークン と置いた場合の月額試算が以下です。

月間の回答件数入力トークン出力トークンAPI 費用の月額概算
100 件4 万0.6 万約 $0.016 (約 ¥2)
500 件20 万3 万約 $0.08 (約 ¥12)
2,000 件80 万12 万約 $0.31 (約 ¥47)
10,000 件400 万60 万約 $1.55 (約 ¥233)

上記は 編集部による試算 です。実際の費用は回答の長さと指示文の分量で変動します。

月 1 万件を処理しても API 費用は ¥250 前後です。判断作業を人手で回した場合の人件費と比べれば、費用は事実上の誤差になります。次のセクションから構築手順に入ります。

設定手順 (50 分)

Google Apps Script × OpenAI API 構築の 6 ステップ
Step 1
回答シートの列を整える (10 分)

Google フォームの回答シート、または既存の問い合わせ台帳を開きます。要約結果を書き込む列を右端に 2 列足してください。

  • 既存列: タイムスタンプ / 氏名 / 自由記述 など
  • 追加列 1: 「要約」 (AI が 60 字にまとめた文が入る)
  • 追加列 2: 「分類」 (不具合・機能要望などのタグが入る)
  • 自由記述が何列目か、追加列が何列目かを控えます (A 列 = 1)

フォーム連携シートは回答が増えると列が右にずれないため、追加列は安全に使えます。

Step 2
OpenAI の API キーを発行する (10 分)

OpenAI のプラットフォームにログインし、API キーを新規発行します。合わせて請求設定でクレジットを購入し、残高を持たせてください。

  • 発行したキーは画面を閉じると二度と表示されません。その場で控えます
  • 用途ごとにキーを分けると、後から停止・差し替えが容易です
  • 使いすぎ防止のため、利用上限額 (usage limit) を必ず設定します

キーは他人に渡さず、メールやチャットに貼らないでください。

Step 3
Apps Script プロジェクトを作成 (5 分)

回答シートを開き、メニューの「拡張機能」 から「Apps Script」 を選びます。エディタが別タブで開き、空のプロジェクトが作られます。

プロジェクト名は「回答の自動要約」 など分かりやすいものに変更してください。シートから作ることで、対象データとの紐づけが明確になります。

Step 4
API キーをスクリプトプロパティに登録 (5 分)

API キーはコードに直接書かず、Apps Script 側の保管領域に入れます。共有時にキーが漏れる事故を防ぐためです。

  • エディタ左の歯車アイコン (プロジェクトの設定) を開く
  • 最下部の「スクリプト プロパティ」 で「スクリプト プロパティを追加」
  • プロパティ名に OPENAI_API_KEY、値に Step 2 のキーを入力して保存

コード側はこの名前でキーを読み出します。名前を変えた場合はコードも合わせてください。

Step 5
コードを貼り付け、設定 4 行を書き換える (10 分)

次セクションのコード全文をエディタに貼り付け、冒頭の設定部分だけを自分の環境に合わせます。

  • SHEET_NAME: 回答が入っているシート名
  • TEXT_COLUMN: 自由記述が入っている列番号
  • SUMMARY_COL / CATEGORY_COL: Step 1 で足した列番号
  • CATEGORIES: 自社の分類名に書き換え可能

貼り付けたら保存し、まず 2〜3 行だけのテストデータで動かします。

Step 6
動作確認してトリガーを設定 (10 分)

関数選択欄で summarizeResponses を選んで実行します。初回は外部通信とシート編集の許可を求める画面が出ます。

  • 「詳細」 から自分のプロジェクトを選び、許可してください
  • 要約列と分類列に文字が入れば成功です
  • 問題なければ createTrigger を 1 回実行し、1 時間ごとの自動起動を登録します

導入後 1 週間は実行ログを開き、エラーが出ていないか確認してください。

以上で構築は完了です。次に、実際に貼り付けるコードの全文を掲載します。

コード全文 (そのまま動きます)

以下は編集部が動作を確認した実物です。冒頭の設定部分だけを書き換えてください。API キーはコードに含まれていません。Step 4 で登録したスクリプトプロパティから読み出します。

// ===== 設定: ここだけ書き換える =====
const SHEET_NAME   = 'フォームの回答 1';  // 回答が入っているシート名
const TEXT_COLUMN  = 3;   // 自由記述が入っている列番号 (A=1)
const SUMMARY_COL  = 6;   // 要約を書き込む列番号
const CATEGORY_COL = 7;   // 分類を書き込む列番号
const MODEL        = 'gpt-5.4-nano';
const MAX_ROWS     = 30;  // 1 回の実行で処理する上限 (6 分制限への保険)
const MAX_CHARS    = 4000;// 1 件あたりに送る本文の最大文字数
const CATEGORIES   = ['不具合', '機能要望', '料金', '使い方', '称賛', 'その他'];
// ====================================

const OPENAI_URL = 'https://api.openai.com/v1/chat/completions';

/**
 * 回答シートの未処理行を OpenAI で要約・分類し、シートへ書き戻す。
 * トリガーから 1 時間ごとに呼ばれる想定。
 */
function summarizeResponses() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
  if (!sheet) throw new Error('シートが見つかりません: ' + SHEET_NAME);

  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return;   // 見出し行のみ

  const width  = Math.max(TEXT_COLUMN, SUMMARY_COL, CATEGORY_COL);
  const values = sheet.getRange(2, 1, lastRow - 1, width).getValues();
  let processed = 0;

  for (let i = 0; i < values.length; i++) {
    if (processed >= MAX_ROWS) break;

    const text = String(values[i][TEXT_COLUMN - 1] || '').trim();
    const done = String(values[i][SUMMARY_COL - 1] || '').trim();
    if (!text || done) continue;   // 空欄・処理済みは飛ばす (二重課金の防止)

    const result = askOpenAi_(text);
    const rowNo  = i + 2;
    sheet.getRange(rowNo, SUMMARY_COL).setValue(result.summary);
    sheet.getRange(rowNo, CATEGORY_COL).setValue(result.category);

    processed++;
    Utilities.sleep(1000);   // 連続呼び出しの間隔を空ける
  }
  SpreadsheetApp.flush();
}

/** OpenAI に 1 件分の本文を送り、要約と分類を受け取る。 */
function askOpenAi_(text) {
  const apiKey = PropertiesService.getScriptProperties()
                                  .getProperty('OPENAI_API_KEY');
  if (!apiKey) throw new Error('OPENAI_API_KEY がスクリプトプロパティに未設定です');

  const systemPrompt =
    'あなたは中小企業のカスタマーサポート担当者です。' +
    '与えられた問い合わせまたはアンケート回答を、日本語で 60 字以内に要約してください。' +
    '続けて、内容を次のいずれか 1 つに分類してください: ' + CATEGORIES.join(' / ') + '。' +
    '本文に書かれていない事実を補ってはいけません。' +
    '出力は summary と category の 2 つのキーを持つ JSON のみとし、前後に説明を付けないこと。';

  const payload = {
    model: MODEL,
    messages: [
      { role: 'system', content: systemPrompt },
      { role: 'user',   content: text.slice(0, MAX_CHARS) }
    ],
    temperature: 0.2
  };

  const response = UrlFetchApp.fetch(OPENAI_URL, {
    method: 'post',
    contentType: 'application/json',
    headers: { Authorization: 'Bearer ' + apiKey },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true   // エラーでも例外にせず自分で処理する
  });

  const code = response.getResponseCode();
  if (code !== 200) {
    // 課金停止・レート超過などはシート上に残し、後で見つけられるようにする
    return { summary: 'API エラー (HTTP ' + code + ')', category: '要確認' };
  }

  const body = JSON.parse(response.getContentText());
  return parseResult_(body.choices[0].message.content);
}

/** モデルの応答文字列を安全に JSON へ変換する。 */
function parseResult_(raw) {
  const cleaned = String(raw).replace(/```json/gi, '').replace(/```/g, '').trim();
  try {
    const parsed = JSON.parse(cleaned);
    return {
      summary : parsed.summary  || cleaned,
      category: parsed.category || 'その他'
    };
  } catch (e) {
    // JSON として読めなかった場合も本文は捨てず、目視確認に回す
    return { summary: cleaned, category: '要確認' };
  }
}

初回だけ実行する見出し行の作成と、トリガー登録の関数も併せて貼り付けておくと便利です。

/** 初回に 1 回だけ実行: 追加した 2 列に見出しを付ける */
function setupHeaders() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
  sheet.getRange(1, SUMMARY_COL).setValue('要約').setFontWeight('bold');
  sheet.getRange(1, CATEGORY_COL).setValue('分類').setFontWeight('bold');
  sheet.setFrozenRows(1);
}

/** 初回に 1 回だけ実行: 1 時間ごとのトリガーを登録する */
function createTrigger() {
  ScriptApp.getProjectTriggers().forEach(function (t) {
    if (t.getHandlerFunction() === 'summarizeResponses') {
      ScriptApp.deleteTrigger(t);   // 重複登録を防ぐ
    }
  });
  ScriptApp.newTrigger('summarizeResponses')
           .timeBased()
           .everyHours(1)
           .create();
}

分類の内訳を毎朝メールで受け取りたい場合は、以下も追加してください。日次トリガーで動かします。

/** 前日分の分類件数を集計し、自分宛にメールで送る */
function sendDailyDigest() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return;

  const width = Math.max(TEXT_COLUMN, SUMMARY_COL, CATEGORY_COL);
  const rows  = sheet.getRange(2, 1, lastRow - 1, width).getValues();

  const since = new Date(Date.now() - 24 * 60 * 60 * 1000);
  const count = {};
  const lines = [];

  rows.forEach(function (row) {
    const stamp = row[0];                       // A 列 = タイムスタンプ想定
    if (!(stamp instanceof Date) || stamp < since) return;
    const category = String(row[CATEGORY_COL - 1] || '未分類');
    count[category] = (count[category] || 0) + 1;
    lines.push('[' + category + '] ' + String(row[SUMMARY_COL - 1] || ''));
  });

  if (lines.length === 0) return;   // 新着なしの日はメールを送らない

  const head = Object.keys(count)
    .map(function (k) { return k + ': ' + count[k] + ' 件'; })
    .join(' / ');

  MailApp.sendEmail(
    Session.getActiveUser().getEmail(),
    '【日次】新着の回答 ' + lines.length + ' 件',
    head + '\n\n' + lines.join('\n')
  );
}

なぜ MAX_ROWS で上限を掛けるのか

Google Apps Script には「1 回の実行は 6 分まで」 という制限があります。未処理の回答が数百件溜まった状態で初回実行すると、途中で強制終了し、どこまで処理したか分からなくなります。1 回 30 件に絞り、トリガーで繰り返し消化させるほうが安全です。溜まっている場合は手動で数回実行してください。処理済み行は自動で飛ばされるため、何度実行しても二重課金にはなりません。

コードを動かす前に、Google 側の利用上限を確認します。

Google Apps Script の利用上限 (クォータ)

Google Apps Script は無料ですが無制限ではなく、アカウント種別ごとに 1 日あたりの上限が定められています[出典]。本レシピで効いてくる項目は以下です。

制限項目無料アカウント (gmail.com)Google Workspace
1 回の実行時間6 分6 分
トリガーの合計実行時間90 分 / 日6 時間 / 日
外部への HTTP 通信 (UrlFetch)20,000 回 / 日100,000 回 / 日
1 回の通信で受け取れる容量50 MB50 MB
メール送信先100 件 / 日1,500 件 / 日

外部通信は 1 日 20,000 回まで使えるため、回答 1 件 = 1 通信の本レシピでは実質的に問題になりません。先に詰まるのはトリガーの合計実行時間 90 分 / 日 のほうです。

無料アカウントで最も詰まりやすい制限: トリガー合計 90 分 / 日

1 件の要約には、通信待ちを含めて 2〜4 秒かかります。30 件で 1〜2 分、これを 1 時間ごと (1 日 24 回) 回すと最大で 1 日 48 分前後。90 分の枠内には収まりますが、余裕は大きくありません。MAX_ROWS を 100 に上げたり、トリガーを 15 分ごとに縮めたりすると上限に接触します。件数が多い事業者は、1 時間ごとに 30 件のまま運用し、初期の溜まり分だけ手動実行で消化してください。

トリガー間隔の選び方を、編集部の判断としてまとめます。

トリガー間隔1 日の実行回数無料アカウントでの可否向いているケース
15 分ごと96 回⚠️ 上限に接触しやすい即時対応が必要な問い合わせ
1 時間ごと24 回✅ 推奨通常の問い合わせ・アンケート
1 日 1 回 (深夜)1 回✅ 余裕あり月次アンケートの一括処理

上限を踏まえたうえで、削減できる工数を試算します。

Mira のシミュレーション (期待効果の試算)

以下は 編集部による試算 です。実際の効果は回答の長さと運用体制により変動します。

前提: 1 件あたり平均 300 字の自由記述を、担当者が読んで要約し分類するのに 25 秒かかるものとします。自動化後は、AI が付けた分類の妥当性を目視確認する時間のみが残ると想定します。

月 500 件の自由記述を処理する月間工数 (編集部の試算)
Before (手作業)
  • 読んで要約: 月 3.5 時間 (500件×25秒)
  • 分類の振り分け: 月 1.4 時間 (500件×10秒)
  • 対応までの遅れ: 最大 3 週間 (月次処理)
  • 月額費用: ¥0 (人件費のみ)
  • 分類のブレ: 担当者ごとに発生
  • 合計: 月 4.9 時間
After (GAS × OpenAI API)
  • 読んで要約: 0 時間 (全自動)
  • 分類の振り分け: 0 時間 (全自動)
  • 確認・修正: 月 0.25 時間 (15 分)
  • 対応までの遅れ: 最大 1 時間
  • 月額費用: 約 ¥12 (API 費用のみ)
  • 合計: 月 0.25 時間

編集部の試算: 月 4.6 時間の削減。API 費用は月 ¥12 前後に収まる

工数削減以上に大きいのは、対応までの遅れが 3 週間から 1 時間になる 点です。不具合の報告が当日中に把握できれば、同じ不満を抱えた顧客の離脱を止められます。次に、導入時に実際に起きる失敗を挙げます。

編集部の警告 ⚠️ よくある 3 つの失敗

失敗パターン 1: API キーをコードに直接書いてしまう

キーをコード内の変数に書くと、スプレッドシートを社内で共有した瞬間に、閲覧者全員がキーを読める状態になります。キーは必ずスクリプトプロパティに保管し、コードには名前だけを書いてください。本記事のコードはその作りになっています。万一貼り付けてしまった場合は、OpenAI の管理画面でそのキーを失効させ、新しいキーを発行し直すのが唯一の対処です。あわせて、利用上限額の設定も必ず行ってください。

失敗パターン 2: 個人情報をそのまま送ってしまう

問い合わせフォームの自由記述には、氏名・電話番号・取引内容が書き込まれていることがあります。外部の AI サービスへ送る以上、何をどこへ送るかを事前に社内で決め、プライバシーポリシーの記載と整合させてください。本記事のコードは自由記述の列だけを送り、氏名・メールアドレスの列は送りません。この設計を崩さないことが実務上の要点です。判断に迷う情報が含まれる業種では、送信前に該当箇所を伏せ字化する処理を追加してください。

失敗パターン 3: AI の分類を検算せずに使う

要約は概ね正確ですが、皮肉や曖昧な表現を含む回答では分類を取り違えます。導入後 1 か月は、分類列を目視で確認する運用を必ず残してください。ズレが目立つ場合は、コード内の指示文に自社固有の判断基準を 1〜2 行足すと精度が上がります。AI が JSON として読めない応答を返した場合、分類欄には「要確認」 が入る作りにしてあるため、フィルタで抽出すれば見落としを防げます。

失敗の多くはコードではなく運用設計に起因します。まずは 1 つのフォームに絞って試してください。

補助金の活用 (編集部の提案)

本レシピ単体では補助金の対象になりにくい

Google Apps Script は無料、OpenAI API は月数十円のため、補助対象経費 (補助金がもらえる費目) と呼べる支出がほとんど発生しません。一方で、顧客管理システムや問い合わせ管理ツールの導入と組み合わせる形なら、業務改善計画の一部として説明材料になります。IT 導入補助金 2026 の詳細 で対象ツールの条件をご確認ください。

申請書に書ける「顧客の声の活用実績」 として使う

本レシピで蓄積した分類データは、小規模事業者持続化補助金 の申請で「顧客ニーズを定量的に把握している根拠」 として提示できます。採択 (申請が通ること) を狙ううえで、自社の課題を数字で語れる事業者は説得力が違います。

補助金を待たずに今日から始められるのが、無料の仕組みで完結する本レシピの利点です。

関連レシピ

よくある質問

Q. プログラミングの経験がなくても設定できますか?

編集部の答え: 書き換える箇所は冒頭の 4 行に限定してあります。列番号の指定とシート名の一致だけ守れば、コードの中身を理解していなくても動きます。ただし想定外の動きをした際に自力で直すには多少の読解力が必要です。不安な場合は、まず 3 行程度のテストデータで試してください。

Q. Make.com や Zapier を使う方法と、どちらが良いですか?

編集部の答え: 連携先が Google スプレッドシートと OpenAI だけで閉じているなら、月額固定費のかからない本レシピが有利です。一方、Slack や Notion など他のサービスへ結果を流したい場合は、Make.com を使ったレシピ のほうが設定が簡単です。無理に一方へ寄せる必要はなく、用途で使い分けて構いません。

Q. 要約の文字数や分類の項目を変えられますか?

編集部の答え: 変えられます。分類の項目はコード冒頭の CATEGORIES を自社の言葉に書き換えるだけです。文字数は指示文の「60 字以内」 の部分を書き換えてください。ただし要約を長くすると出力トークンが増え、費用も比例して増えます。

Q. 過去に溜まった数千件の回答も処理できますか?

編集部の答え: できます。要約列が空の行だけを対象にする作りなので、手動実行を繰り返せば 30 件ずつ消化されます。ただし 1 回の実行が 6 分の制限に触れないよう、MAX_ROWS は 30 前後のままにしてください。数千件を一気に処理する場合は、トリガーの合計実行時間 90 分 / 日 に達して翌日へ持ち越される可能性があります。


出典・参考情報


本記事の数値のうち、「編集部の試算」 と明示された箇所は編集部によるシミュレーションです。実際の効果は事業特性により変動します。料金・利用上限は各社公式情報源を確認のうえご判断ください。記事中の API キーはすべて架空のプレースホルダであり、実在するものではありません。

編集長 Mira / AI経営ラボ 本記事は 2026-07-29 時点の情報です。OpenAI API の料金および Google Apps Script の利用上限は予告なく変更される場合があります。