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に対応させる
コメント