MENU

GAS×ChatGPTでFAQボットを作る|社内問い合わせ自動化の完全ガイド

GAS×ChatGPTでFAQボットを作る|社内問い合わせ自動化の完全ガイド

社内の問い合わせ対応は、同じ質問に繰り返し答える時間と、担当者ごとに回答がばらつく品質の2つが課題になりやすい。

GASとChatGPT APIを組み合わせれば、スプレッドシートベースのFAQボットを構築できる。一度作れば24時間365日、一貫した品質で自動回答が返る。コストは月数百円程度。コードはすべてコピペで動くので、プログラミング未経験でも問題ない。


目次

FAQボットの仕組みと導入メリット

FAQボットの仕組み

FAQボットは、あらかじめ登録したQ&Aデータベースを参照して、ユーザーからの質問に自動回答するシステムだ。

今回作成するボットの処理フローは以下のとおり。


【処理の流れ】
1. ユーザーが質問を入力
2. スプレッドシートのFAQデータを検索
3. ChatGPTが最適な回答を選択・整形
4. 自然な文章で回答を返す

ChatGPTを挟むことで、質問の表現が多少違っても適切なFAQを見つけて回答を生成する。「パスワード変えたい」「PWリセット」「ログインできない」といった言い回しの違いにも対応可能だ。

導入メリット

メリット 詳細
1. 24時間対応 深夜・休日でも即座に回答
2. 回答品質の統一 誰が聞いても同じ回答
3. 対応コスト削減 1件あたり0.1〜0.5円程度
4. スケーラビリティ 同時100件でも対応可能
5. ナレッジの蓄積 FAQがそのまま知識資産に

導入による変化の目安:

  • 定型質問の対応時間:1件5分 → 0分(自動化)
  • 担当者の対応負荷:70%削減
  • 回答までの待ち時間:平均2時間 → 即座

必要な準備

1. OpenAI APIキーの取得

ChatGPT APIを利用するにはOpenAIのAPIキーが必要になる。

取得手順:

  • OpenAI Platform にアクセス
  • アカウント作成(またはGoogleアカウントでログイン)
  • 右上のアイコン →「View API Keys」
  • 「Create new secret key」をクリック
  • 生成されたキー(sk-で始まる文字列)をコピーして保存

注意: APIキーは一度しか表示されない。必ず安全な場所に保存しておくこと。

料金の目安(2026年現在):

モデル 入力 出力 1回あたり目安
gpt-3.5-turbo $0.0005/1K tokens $0.0015/1K tokens 約0.1〜0.3円
gpt-4o-mini $0.00015/1K tokens $0.0006/1K tokens 約0.05〜0.1円
gpt-4o $0.0025/1K tokens $0.01/1K tokens 約1〜3円

FAQ対応にはgpt-4o-miniが最もコストパフォーマンスに優れている。

2. FAQスプレッドシートの準備

FAQデータを管理するスプレッドシートを作成する。

推奨するシート構成:

A列 B列 C列 D列 E列 F列
ID カテゴリ 質問 回答 キーワード 優先度
1 アカウント パスワードを変更したい 1. 設定画面を開く… パスワード,PW,変更
2 請求 領収書を発行してほしい マイページ→請求履歴から… 領収書,請求書
3 機能 データをエクスポートしたい 画面右上のエクスポート… エクスポート,出力

各列の役割:

  • ID: 管理用の通し番号
  • カテゴリ: 質問の分類(アカウント、請求、機能など)
  • 質問: よくある質問の内容
  • 回答: 質問への回答(具体的なステップで記載)
  • キーワード: 類似表現や略語(カンマ区切り)
  • 優先度: 高/中/低(複数該当時の優先順位)

シート名は「FAQ」にしておく。コードから参照するため。


実装手順:ステップバイステップ

ステップ1: スプレッドシートを作成

  • Google スプレッドシート で新規作成
  • シート名を「FAQ」に変更
  • 上記の構成でヘッダー行を作成
  • テスト用に3〜5件のFAQを登録

ステップ2: Apps Scriptを開く

  • スプレッドシートのメニュー「拡張機能」→「Apps Script」
  • 新しいエディタが開く
  • デフォルトのコードを削除

ステップ3: メインコードを貼り付け

以下のコードをエディタに貼り付ける。


/**
 * ====================================
 * GAS×ChatGPT FAQボット
 * ====================================
 * スプレッドシートのFAQデータを参照して
 * ChatGPTで自然な回答を生成する
 */

// ============ 設定 ============
const CONFIG = {
  // OpenAI APIキー(必ず変更してください)
  API_KEY: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',

  // FAQデータのシート名
  FAQ_SHEET_NAME: 'FAQ',

  // 使用するモデル(gpt-4o-mini推奨)
  MODEL: 'gpt-4o-mini',

  // 回答の最大トークン数
  MAX_TOKENS: 500,

  // ログを記録するか
  ENABLE_LOG: true,

  // ログシート名
  LOG_SHEET_NAME: 'ログ'
};

/**
 * メイン関数:質問に対してFAQから回答を生成
 * @param {string} userQuestion - ユーザーからの質問
 * @return {string} - 生成された回答
 */
function answerQuestion(userQuestion) {
  try {
    // 入力チェック
    if (!userQuestion || userQuestion.trim() === '') {
      return 'ご質問内容を入力してください。';
    }

    // FAQデータを取得
    const faqData = getFAQData();

    if (faqData.length === 0) {
      return 'FAQデータが登録されていません。管理者にお問い合わせください。';
    }

    // ChatGPTで回答を生成
    const answer = generateAnswer(userQuestion, faqData);

    // ログを記録
    if (CONFIG.ENABLE_LOG) {
      logQuery(userQuestion, answer);
    }

    return answer;

  } catch (error) {
    console.error('エラー発生:', error);
    return 'システムエラーが発生しました。しばらく経ってから再度お試しください。';
  }
}

/**
 * スプレッドシートからFAQデータを取得
 * @return {Array} FAQデータの配列
 */
function getFAQData() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName(CONFIG.FAQ_SHEET_NAME);

  if (!sheet) {
    throw new Error('FAQシートが見つかりません: ' + CONFIG.FAQ_SHEET_NAME);
  }

  const data = sheet.getDataRange().getValues();
  const faqList = [];

  // ヘッダー行(1行目)をスキップ
  for (let i = 1; i < data.length; i++) {
    const row = data[i];
    if (row[2]) { // 質問列が空でなければ
      faqList.push({
        id: row[0],
        category: row[1],
        question: row[2],
        answer: row[3],
        keywords: row[4] || '',
        priority: row[5] || '中'
      });
    }
  }

  return faqList;
}

/**
 * ChatGPT APIで回答を生成
 * @param {string} userQuestion - ユーザーの質問
 * @param {Array} faqData - FAQデータ
 * @return {string} - 生成された回答
 */
function generateAnswer(userQuestion, faqData) {
  // FAQデータをプロンプト用に整形
  const faqText = faqData.map(faq =>
    `[ID:${faq.id}][${faq.category}][優先度:${faq.priority}]\n質問: ${faq.question}\nキーワード: ${faq.keywords}\n回答: ${faq.answer}`
  ).join('\n---\n');

  // プロンプトを作成
  const prompt = `あなたは丁寧で親切な社内ヘルプデスクのFAQボットです。
以下のFAQデータベースを参照して、ユーザーの質問に回答してください。

【回答ルール】
1. FAQに該当する内容があれば、その回答をベースに丁寧に回答する
2. 複数のFAQが該当する場合は、優先度が高いものを優先する
3. 該当するFAQがない場合は「申し訳ございません。この件についてはFAQに情報がございません。担当部署にお問い合わせください。」と回答する
4. 回答は敬語を使い、簡潔かつ分かりやすく
5. 手順がある場合は番号付きリストで示す
6. 回答の最後に「他にご不明点があればお気軽にお聞きください。」と添える

【FAQデータベース】
${faqText}

【ユーザーの質問】
${userQuestion}

【回答】`;

  return callChatGPT(prompt);
}

/**
 * ChatGPT APIを呼び出す
 * @param {string} prompt - プロンプト
 * @return {string} - ChatGPTの回答
 */
function callChatGPT(prompt) {
  const url = 'https://api.openai.com/v1/chat/completions';

  const payload = {
    model: CONFIG.MODEL,
    messages: [
      {
        role: 'system',
        content: 'あなたは社内ヘルプデスクのFAQボットです。丁寧で正確な回答を心がけてください。'
      },
      {
        role: 'user',
        content: prompt
      }
    ],
    max_tokens: CONFIG.MAX_TOKENS,
    temperature: 0.3  // 回答の一貫性を重視
  };

  const options = {
    method: 'post',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + CONFIG.API_KEY
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };

  const response = UrlFetchApp.fetch(url, options);
  const statusCode = response.getResponseCode();

  if (statusCode !== 200) {
    const errorBody = response.getContentText();
    console.error('API Error:', statusCode, errorBody);
    throw new Error('API呼び出しに失敗しました: ' + statusCode);
  }

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

/**
 * 問い合わせログを記録
 * @param {string} question - 質問
 * @param {string} answer - 回答
 */
function logQuery(question, answer) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let logSheet = ss.getSheetByName(CONFIG.LOG_SHEET_NAME);

  // ログシートがなければ作成
  if (!logSheet) {
    logSheet = ss.insertSheet(CONFIG.LOG_SHEET_NAME);
    logSheet.appendRow(['タイムスタンプ', '質問', '回答', '文字数']);
  }

  // ログを追記
  logSheet.appendRow([
    new Date(),
    question,
    answer,
    answer.length
  ]);
}

/**
 * テスト用関数
 */
function testFAQBot() {
  const testQuestions = [
    'パスワードを変更したいのですが',
    '領収書を発行してほしい',
    '休暇の申請方法は?'
  ];

  testQuestions.forEach(q => {
    console.log('━━━━━━━━━━━━━━━━━━━━');
    console.log('質問: ' + q);
    console.log('回答: ' + answerQuestion(q));
  });
}

ステップ4: APIキーを設定

コード冒頭の CONFIG.API_KEY を、取得したOpenAI APIキーに置き換える。


API_KEY: 'sk-あなたのAPIキーをここに貼り付け',

ステップ5: 保存してテスト実行

  • Ctrl+S(Macは Cmd+S)で保存
  • 関数選択ドロップダウンで「testFAQBot」を選択
  • 「実行」をクリック
  • 初回は権限承認が必要(「権限を確認」→「許可」)
  • 実行ログに回答が表示されれば成功

Googleフォーム連携:問い合わせフォームを作成

FAQボットをGoogleフォームと連携し、問い合わせフォームとして機能させる。

フォーム連携の全体像


【処理の流れ】
1. ユーザーがGoogleフォームで質問を送信
2. GASがフォーム送信を検知
3. FAQボットが回答を生成
4. 回答をメールで自動返信

実装手順

  • Googleフォームを作成

以下の項目を設定する。

  • メールアドレス(必須)
  • お問い合わせ内容(必須・段落テキスト)
  • カテゴリ(任意・プルダウン)
  • フォームとスプレッドシートを連携

フォームの「回答」タブ →「スプレッドシートにリンク」→ 既存のスプレッドシート(FAQボットのシート)を選択する。

  • 以下のコードを追加

/**
 * ====================================
 * Googleフォーム連携
 * ====================================
 */

/**
 * フォーム送信時に自動実行される関数
 * @param {Object} e - フォーム送信イベント
 */
function onFormSubmit(e) {
  try {
    // フォームの回答を取得
    const responses = e.values;
    const timestamp = responses[0];
    const email = responses[1];      // メールアドレス
    const question = responses[2];   // お問い合わせ内容
    const category = responses[3] || '未分類';  // カテゴリ(あれば)

    // FAQボットで回答を生成
    const answer = answerQuestion(question);

    // メールで回答を送信
    sendAutoReply(email, question, answer);

    // 回答をスプレッドシートに記録(任意)
    recordAnswer(e.range.getRow(), answer);

    console.log('自動回答送信完了: ' + email);

  } catch (error) {
    console.error('フォーム処理エラー:', error);
    // エラー時は管理者に通知
    notifyAdmin(error.message);
  }
}

/**
 * 自動返信メールを送信
 * @param {string} email - 送信先メールアドレス
 * @param {string} question - 元の質問
 * @param {string} answer - 生成された回答
 */
function sendAutoReply(email, question, answer) {
  const subject = '【自動回答】お問い合わせありがとうございます';

  const body = `お問い合わせいただきありがとうございます。

いただいたご質問に対して、FAQデータベースから回答いたします。

━━━━━━━━━━━━━━━━━━━━━━━━━━
■ ご質問内容
━━━━━━━━━━━━━━━━━━━━━━━━━━
${question}

━━━━━━━━━━━━━━━━━━━━━━━━━━
■ 回答
━━━━━━━━━━━━━━━━━━━━━━━━━━
${answer}

━━━━━━━━━━━━━━━━━━━━━━━━━━

この回答で解決しない場合や、追加のご質問がある場合は、
このメールに返信いただくか、担当部署までお問い合わせください。

※このメールはFAQボットによる自動送信です。
`;

  GmailApp.sendEmail(email, subject, body);
}

/**
 * スプレッドシートに回答を記録
 * @param {number} row - 対象行
 * @param {string} answer - 回答
 */
function recordAnswer(row, answer) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const formSheet = ss.getSheets()[0]; // フォームの回答シート

  // 回答列(E列など)を想定
  const answerColumn = 5;
  formSheet.getRange(row, answerColumn).setValue(answer);
  formSheet.getRange(row, answerColumn + 1).setValue(new Date());
}

/**
 * エラー時に管理者に通知
 * @param {string} errorMessage - エラーメッセージ
 */
function notifyAdmin(errorMessage) {
  const adminEmail = 'admin@example.com'; // 管理者メールアドレス
  GmailApp.sendEmail(
    adminEmail,
    '【警告】FAQボットでエラーが発生しました',
    'エラー内容:\n' + errorMessage + '\n\n発生日時: ' + new Date()
  );
}
  • トリガーを設定
  • Apps Script エディタで「トリガー」(時計アイコン)をクリック
  • 「トリガーを追加」
  • 以下を設定:

– 実行する関数: onFormSubmit

– イベントのソース: スプレッドシートから

– イベントの種類: フォーム送信時

  • 「保存」

これでフォーム送信時に自動で回答メールが送信される。


Slack連携:チャットで質問を受け付ける

SlackのスラッシュコマンドからFAQに問い合わせる連携も可能だ。

Slack連携の概要


【処理の流れ】
1. ユーザーがSlackでスラッシュコマンド実行(例: /faq パスワード変更)
2. SlackからGAS(Webアプリ)にリクエスト
3. FAQボットが回答を生成
4. Slackに回答を返す

実装手順(概要)

  • GASをWebアプリとしてデプロイ

/**
 * ====================================
 * Slack連携用エンドポイント
 * ====================================
 */

/**
 * Webアプリのエンドポイント(POST)
 * @param {Object} e - リクエストオブジェクト
 * @return {TextOutput} - レスポンス
 */
function doPost(e) {
  try {
    // Slackからのリクエストを解析
    const params = e.parameter;
    const question = params.text; // スラッシュコマンドの引数
    const responseUrl = params.response_url;

    // 即座に受付メッセージを返す(3秒以内に応答が必要)
    const immediateResponse = {
      response_type: 'ephemeral',
      text: '🔍 FAQを検索中です...'
    };

    // 非同期で回答を生成して送信
    generateAndSendSlackResponse(question, responseUrl);

    return ContentService
      .createTextOutput(JSON.stringify(immediateResponse))
      .setMimeType(ContentService.MimeType.JSON);

  } catch (error) {
    return ContentService
      .createTextOutput(JSON.stringify({
        response_type: 'ephemeral',
        text: 'エラーが発生しました: ' + error.message
      }))
      .setMimeType(ContentService.MimeType.JSON);
  }
}

/**
 * 回答を生成してSlackに送信
 * @param {string} question - 質問
 * @param {string} responseUrl - Slackのレスポンス用URL
 */
function generateAndSendSlackResponse(question, responseUrl) {
  const answer = answerQuestion(question);

  const payload = {
    response_type: 'in_channel', // チャンネル全体に表示
    blocks: [
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `*📋 ご質問:*\n${question}`
        }
      },
      {
        type: 'divider'
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: `*💡 回答:*\n${answer}`
        }
      }
    ]
  };

  UrlFetchApp.fetch(responseUrl, {
    method: 'post',
    headers: { 'Content-Type': 'application/json' },
    payload: JSON.stringify(payload)
  });
}
  • Webアプリとしてデプロイ
  • Apps Script エディタで「デプロイ」→「新しいデプロイ」
  • 種類: ウェブアプリ
  • 次のユーザーとして実行: 自分
  • アクセスできるユーザー: 全員
  • 「デプロイ」→ URLをコピー
  • Slack Appを設定
  • Slack API で新しいAppを作成
  • 「Slash Commands」で新しいコマンドを追加

– Command: /faq

– Request URL: GASのWebアプリURL

  • ワークスペースにインストール

運用のコツ:FAQの改善サイクル

FAQボットは作って終わりではなく、継続的に育てていくものだ。

FAQ更新のルーティン

頻度 やること
毎日 ログを確認、未回答質問をチェック
週1回 新しいFAQを追加(2〜3件目安)
月1回 回答精度のレビュー、古いFAQの更新

未回答質問の活用

ログシートから「FAQに情報がございません」と返したケースを抽出する。これらはそのまま新しいFAQ候補になる。


/**
 * 未回答質問を抽出
 */
function extractUnansweredQuestions() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const logSheet = ss.getSheetByName(CONFIG.LOG_SHEET_NAME);
  const data = logSheet.getDataRange().getValues();

  const unanswered = data.filter(row =>
    row[2] && row[2].includes('FAQに情報がございません')
  );

  console.log('未回答質問一覧:');
  unanswered.forEach(row => console.log('- ' + row[1]));

  return unanswered;
}

回答品質の向上ポイント

  • 質問のバリエーションを増やす: 同じ意味の質問を複数登録しておく
  • 回答を具体的にする: 「設定画面で変更」ではなく「画面右上の歯車アイコン→設定→パスワード変更」のように手順を書く
  • キーワードを充実させる: 略語、言い換え、よくある誤字も登録する
  • 優先度を設定する: 頻出の質問は「高」に設定

まとめ

GASとChatGPTを使ったFAQボットの構成は以下のとおりだ。

  • スプレッドシートでFAQを一元管理し、更新も履歴もそこに残る
  • ChatGPTが表現の揺れを吸収して、適切なFAQを自動選択する
  • Googleフォーム連携で問い合わせフォームの自動回答化が実現する
  • Slack連携でチャットベースの即応も可能になる
  • 月数百円のコストで、人件費と比べれば桁違いに安い

導入効果の目安

指標 導入前 導入後
定型質問の対応時間 1件5分 0分(自動化)
対応可能時間 営業時間内 24時間365日
回答の一貫性 バラつきあり 常に統一
担当者の負荷 70%削減

次のステップ

FAQボットをさらに発展させる方向として、以下の選択肢がある。

  • LINE連携: LINE Official AccountのMessaging APIと組み合わせる
  • RAG(検索拡張生成): より高度な回答精度を実現する
  • 多言語対応: 英語・中国語など複数言語のFAQに対応させる

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次