GASで問い合わせ履歴から
AIでFAQを自動生成する方法
スプレッドシートに溜まった問い合わせをClaude API(Anthropic社の対話型AIをプログラムから呼び出す仕組み)に渡し、よく聞かれる質問を「質問」と「回答」のFAQ形式に自動で束ねる方法を、動くコードで解説します。
Table of Contents
なぜ問い合わせ履歴からFAQを自動生成するのか
問い合わせフォームやメールに寄せられる質問は、実は同じような内容が繰り返されがちです。 これを人手で分類してFAQにまとめるのは手間がかかります。AIに任せれば、似た質問をまとめて 「質問」と「回答の下書き」のペアに整理でき、サポートページの更新や返信テンプレートづくりが一気に楽になります。
問い合わせ工数の削減
よくある質問をFAQ化して先回りで答え、同じ返信の繰り返しを減らせる
傾向の見える化
どんな質問が多いかを件数付きで把握でき、商品説明やLPの改善につながる
FAQページの土台づくり
ゼロから書くより、AIの下書きを直す方が圧倒的に速い
属人化の解消
ベテランの頭の中にあった『よく聞かれること』をデータから見える形にできる
全体の流れとシート構成
処理は「問い合わせを集める → まとめてAIに渡す → FAQを受け取る → シートに書き出す」の4ステップです。 入力となる「問い合わせログ」シートと、出力先の「FAQ下書き」シートの2枚を使います。
問い合わせを集める
「問い合わせログ」シートのB列(本文)を配列にまとめる
まとめてAIに渡す
50件ずつのバッチに分け、Claude APIに似た質問の統合を依頼する
FAQを受け取る
質問・回答・件数のJSON配列で受け取り、全バッチ分を統合する
シートに書き出す
件数が多い順に並べ替え、「FAQ下書き」シートへ出力する
APIキーを安全に保管する
Claude APIを使うにはAPIキーが必要です。キーはコードに直接書かず、 スクリプトプロパティ(プロジェクトごとに保存できる非公開の設定領域)に保管します。 こうすればコードを共有してもキーが漏れません。
// APIキーはコードに直書きせず、スクリプトプロパティに保管する
// エディタ左の「プロジェクトの設定 > スクリプト プロパティ」で
// ANTHROPIC_API_KEY を登録しておく
function getApiKey() {
const key = PropertiesService.getScriptProperties()
.getProperty("ANTHROPIC_API_KEY");
if (!key) throw new Error("APIキーが未設定です");
return key;
}問い合わせをシートから集める
まずは入力データを用意します。「問い合わせログ」シートのB列に問い合わせ本文が入っている前提で、 空欄を除いた本文だけを配列にまとめます。getDataRange().getValues()で全データを一括取得すると高速です。
// 「問い合わせログ」シートのB列(問い合わせ本文)を集める
// 前提: 1行目はヘッダー、B列に本文が入っている
function collectInquiries() {
const sheet = SpreadsheetApp.getActive().getSheetByName("問い合わせログ");
const values = sheet.getDataRange().getValues();
const rows = values.slice(1); // ヘッダーを除く
return rows
.map(function (row) { return String(row[1]).trim(); })
.filter(function (text) { return text.length > 0; });
}まとめてAIに渡しFAQをJSONで受け取る
問い合わせ一覧を1回のリクエストにまとめて渡し、似た質問を統合したFAQをJSON配列で受け取ります。プロンプトで「意味が近い質問は1つにまとめる」「代表的な質問文を1つだけ書く」と明示するのがポイントです。 回答は断定を避け、あくまで下書きとして書かせます。
// 問い合わせ一覧をまとめてAIに渡し、FAQ配列を受け取る
// items: 問い合わせ本文の配列(1バッチ分)
function generateFaqBatch(items) {
// 1件ずつ番号を振って渡すと、AIが件数を数えやすい
const list = items
.map(function (t, i) { return (i + 1) + ". " + t; })
.join("\n");
const instruction =
"あなたはカスタマーサポートのFAQ作成担当です。\n" +
"次は、実際に届いた問い合わせの一覧です。\n" +
"----\n" + list + "\n----\n" +
"条件:\n" +
"・意味が近い問い合わせは1つのFAQにまとめる。\n" +
"・1つのFAQには、代表的な質問文をひとつだけ書く。\n" +
"・question は検索されやすい自然な疑問文にする。\n" +
"・answer は事実の断定を避け、下書きとして簡潔に書く。\n" +
"・count は、そのFAQにまとめた問い合わせのおおよその件数。\n" +
"・多く聞かれている順に並べる。\n" +
"余計な説明は出力せず、JSON配列だけを返してください。\n" +
'形式: [{"question": "質問文", "answer": "回答の下書き", "count": 件数}]';
const payload = {
model: "claude-sonnet-4-20250514",
max_tokens: 2000,
messages: [{ role: "user", content: instruction }],
};
const res = UrlFetchApp.fetch("https://api.anthropic.com/v1/messages", {
method: "post",
contentType: "application/json",
headers: {
"x-api-key": getApiKey(),
"anthropic-version": "2023-06-01",
},
payload: JSON.stringify(payload),
muteHttpExceptions: true,
});
if (res.getResponseCode() !== 200) {
throw new Error("API error: " + res.getContentText());
}
const responseText = JSON.parse(res.getContentText()).content[0].text;
return JSON.parse(extractJsonArray(responseText));
}
// レスポンスから [ ... ] の部分だけを取り出す(前後の余計な文字対策)
function extractJsonArray(text) {
const start = text.indexOf("[");
const end = text.lastIndexOf("]");
return text.slice(start, end + 1);
}AIは前後に説明文を付けることがあるため、extractJsonArray()で最初の[から最後の]までを切り出してからパースし、余計な文字で失敗しないようにしています。
バッチ処理とリトライで安定させる
問い合わせが数百件あると、一度に渡すとトークン上限や6分の実行時間制限に達します。 50件ずつのバッチに分けて渡し、各バッチの結果を統合します。 レート制限などで一時的に失敗しても止まらないよう、少し待って再試行するリトライも入れます。
// 全問い合わせを50件ずつに分けてAIに渡し、結果を統合する
const BATCH_SIZE = 50;
function buildFaqDrafts() {
const inquiries = collectInquiries();
let faqs = [];
for (let i = 0; i < inquiries.length; i += BATCH_SIZE) {
const batch = inquiries.slice(i, i + BATCH_SIZE);
const result = callWithRetry(function () {
return generateFaqBatch(batch);
});
faqs = faqs.concat(result);
Utilities.sleep(1000); // 連続呼び出しの間隔をあける
}
return faqs;
}
// レート制限などで失敗したとき、少し待って再試行する
function callWithRetry(fn) {
for (let attempt = 1; attempt <= 3; attempt++) {
try {
return fn();
} catch (e) {
if (attempt === 3) throw e;
Utilities.sleep(2000 * attempt);
}
}
}FAQをシートに書き出す
統合したFAQを「FAQ下書き」シートに書き出します。件数が多い順に並べ替えてから出力すると、 優先して整備すべき質問がひと目で分かります。シートが無ければ自動で作成します。
// FAQ下書きを「FAQ下書き」シートに書き出す
function writeFaqSheet(faqs) {
const ss = SpreadsheetApp.getActive();
let sheet = ss.getSheetByName("FAQ下書き");
if (!sheet) sheet = ss.insertSheet("FAQ下書き");
sheet.clearContents();
sheet.getRange(1, 1, 1, 3).setValues([["質問", "回答(下書き)", "件数"]]);
const rows = faqs.map(function (f) {
return [f.question, f.answer, f.count || ""];
});
if (rows.length > 0) {
sheet.getRange(2, 1, rows.length, 3).setValues(rows);
}
}
// 実行のエントリーポイント
function runFaqGeneration() {
const faqs = buildFaqDrafts();
// 件数が多い順に並べ替えてから書き出す
faqs.sort(function (a, b) { return (b.count || 0) - (a.count || 0); });
writeFaqSheet(faqs);
}定期トリガーで自動更新する
問い合わせは日々増えていきます。時間主導トリガー(決まった時間に関数を自動実行する仕組み)を使い、 たとえば毎週月曜の朝にFAQを作り直せば、直近の傾向を反映した最新のFAQ下書きへ自動更新できます。
// 毎週月曜の朝8時台にFAQを作り直すトリガーを作成する
function createWeeklyFaqTrigger() {
ScriptApp.newTrigger("runFaqGeneration")
.timeBased()
.onWeekDay(ScriptApp.WeekDay.MONDAY)
.atHour(8)
.create();
}精度を上げるコツと注意点
1. 回答文は必ず人が確認してから公開する
AIの回答はあくまで下書きです。料金・仕様・納期など事実にかかわる部分は、 担当者が自社の正しい情報に直してから公開してください。そのままの公開は避けます。
2. 個人情報は渡す前に取り除く
問い合わせ本文に氏名・電話番号・メールアドレスが含まれることがあります。 FAQ作成には不要なため、AIに渡す前にマスキングするか、本文だけを抜き出す運用にすると安全です。
3. バッチをまたぐ重複は二段階でまとめる
50件ずつ分けて渡すと、別々のバッチで似たFAQができることがあります。 気になる場合は、生成したFAQ一覧をもう一度AIに渡して重複を統合する二段構えにすると、よりきれいにまとまります。
4. 件数はあくまで目安として扱う
AIが返す件数は概算です。正確な集計が必要なら、生成後にキーワードで元データを数え直すなど、 GAS側で裏付けを取ると信頼性が上がります。
まとめ
GASとClaude APIを組み合わせれば、スプレッドシートに溜まった問い合わせ履歴から、 よく聞かれる質問をFAQ形式に自動で束ねられます。バッチ処理とリトライで安定させ、 定期トリガーで最新化し、回答は人が確認してから公開する——この流れを押さえれば、 問い合わせ対応の工数削減とFAQページの充実を同時に進められます。
よくある質問
顧客から繰り返し寄せられる質問を人手で分類しなくても、AIが似た内容の問い合わせをまとめて「質問」と「回答」のペアに整理してくれます。サポートページのFAQ更新や、返信テンプレートの叩き台づくりにそのまま使えるため、問い合わせ対応の工数を減らせます。
数十件からでも傾向はつかめますが、精度を上げるなら100件以上をまとめて渡すのがおすすめです。1回のAPI呼び出しに入れる件数が多すぎるとトークン上限や実行時間制限に達するため、この記事のコードでは50件ずつのバッチに分けて渡し、結果を後で統合しています。
そのまま公開するのは避けてください。AIの回答はあくまで下書きです。料金・仕様・納期など事実にかかわる部分は必ず担当者が確認し、自社の正しい情報に直してから公開する運用を前提にしています。この記事のコードも、生成結果を確認用シートに書き出す形にしています。
プロンプトで「意味が近い質問は1つのFAQにまとめる」「1つのFAQには最も代表的な質問文を1つだけ書く」と明示すると安定します。それでも細かく分かれる場合は、生成したFAQをもう一度AIに渡して重複を統合させる二段構えにすると、さらにきれいにまとまります。
できます。時間主導トリガー(ScriptApp.newTrigger)で、たとえば毎週月曜の朝にFAQ生成関数を実行するよう設定すれば、直近の問い合わせ傾向を反映したFAQへ自動で更新できます。新しい質問が増える時期の変化も追いやすくなります。
AI×GASで問い合わせ対応を
自動化する。
FAQ自動生成のほかにも、問い合わせの自動分類・返信下書き・緊急度判定など、Claude APIとGASを組み合わせた業務自動化をご相談いただけます。