2025年12月8日
ENSOUを使った問い合わせ対応&ナレッジ自動生成エージェントの作成方法
ENSOUのカスタムチャットボット機能を使って、問い合わせ対応とナレッジの自動生成を行う問い合わせ対応エージェントの作成方法を画像やコード付きで徹底解説します。

.png)
はじめに
エージェントとは、特定の条件やルールに基づいて自律的にアクションを実行するソフトウェアやシステムのことです。
本記事では弊社のENSOU AIをベースにした、問い合わせ対応とナレッジの自動生成を行うエージェントの作成方法を画像やコピペして使えるコードを用いながらわかりやすく解説します。
株式会社Digeonでは生成AI導入のファーストステップとして最適な法人向けの生成AIサービスである「ENSOU AI」を提供しています。
無料ですぐに使えるフリープランのご利用開始はこちらから👇
問い合わせ対応自動化エージェントのメリット
本記事で作成するエージェントは、以下の課題・悩みを解決します。
- 人力での問い合わせ対応に多くの時間や労力を浪費してしまっている。
- 人力だと問い合わせできる時間帯が限られる。
- 問い合わせ対応の履歴をデータ化するのが面倒。
- 問い合わせ対応の履歴をナレッジデータとして蓄積したい。
- 問い合わせ対応にチャットボットを使っているが、運用コストがかかる。
今回のエージェントでは、ナレッジにないがゆえにAIチャットボットでは対応できない問い合わせに対して、担当者がスプレッドシートに入力した回答をGeminiを用いてナレッジとして自動で生成します。
このエージェントによって、同じ問い合わせが発生しない仕組みが構築され、自動的に日々ナレッジが蓄積されていきます。
ユーザーはチャットボット単体では対応できなかった問い合わせについても、以前に同じ問い合わせがあれば、担当者に連絡することなく解決できます。
問い合わせ対応の担当者は、スプレッドシートに回答を一度記入すれば以降は同じ問い合わせに対応する必要がなくなります。
今回作成するエージェントの概要
本記事で作成するエージェントの全体像は以下の通りです。
- カスタムチャットボットで回答が得られなかった場合にGoogleフォーム(問い合わせ申請)のリンクを出力する。
- Googleフォームに記載された問い合わせ内容が担当者に送信される。
- Googleフォームの内容がスプレッドシートに自動で記録される。
- 連携されたスプレッドシートの問い合わせ内容に対して回答を記入する。
- そのスプレッドシートをもとにGeminiを用いてナレッジファイル用のQ&Aスプレッドシートを生成させる。
- 生成されたスプレッドシートはナレッジ連携されているので、次回以降はその問い合わせに対してチャットボットが回答する。
システムのイメージ図はこのようになります。

このエージェントで使用するGeminiAPIについて「無料サービス」を使用する場合は、送信データがGeminiの学習やGoogleのプロダクト開発に利用されるので、プライベート情報や機密情報、個人情報は送信しないように注意してください。ただし、「有料サービス」を使用する場合はこの限りではありません。
詳細についてはこちらをご覧ください。
エージェントの作成
それでは、実際にエージェントを画面・コード付きで分かりやすく解説していきます。
使用するツール
今回の構築に使用するツールは以下の4つです。
- ENSOU AI
- Googleスプレッドシート
- Googleフォーム
- Gemini
ENSOU AIとGoogleのアカウントがあれば誰でも構築が可能です。
Googleスプレッドシートを作成
Googleドライブを開き、「+新規」をクリックし「Googleスプレッドシート」を選択します。
タイトル名は「社内問い合わせ対応Q&A」などとして下さい。
そして、「ツール」から「新しいフォームを作成」をクリックします。

Googleフォームの作成
先程作成されたGoogleフォームに以下のように項目を設定します。
- 部署名(記述式・短文)
- 氏名(記述式・短文)
- メールアドレス(記述式・短文)
- 問い合わせ内容(段落)

次に「回答」タブから「スプレッドシートで表示」をクリックします。
ここで、スプレッドシートに項目が反映されていることを確認してください。

公開範囲については公開をクリックすると表示される画面で管理を押下し、回答者ビューを「リンクを知っている全員」に設定します。その後、「完了」を押し、続けて「公開」をクリックします。

組織でGoogleアカウントを配布している場合は回答者ビューが制限付きのままでも大丈夫です。
スプレッドシートについてA~E列はGoogleフォームをもとに自動で作成されます。
F列に回答用の「回答(人間による入力)」を追加します。

GASの構築
新たにスプレッドシートを作成します。
タイトルは「ENSOU生成Q&A」とします。
A列に「タイムスタンプ」、B列に「問い合わせ内容」、C列に「問い合わせ回答」を入力します。

メニューの「拡張機能」→「Apps Script」の順にクリックします。

「コード.gs」に下記のソースコードをコピー&ペーストして画像のように貼り付けます。

ソースコード
/******** 設定ここから ********/
// ▼ フォーム連携スプレッドシート(ファイルA)のIDを貼る
// URLの /d/ と /edit の間の文字列
const SRC_SPREADSHEET_ID = 'ここにフォーム連携スプシのIDを貼る'; // ← ここだけ編集
// ▼ フォーム連携側のシート名
const SRC_SHEET_NAME = 'フォームの回答 1'; // 実際の名前に合わせて変更OK
// ▼ 清書用Q&Aスプレッドシート側のシート名
const DEST_SHEET_NAME = 'シート1'; // 実際の名前に合わせて変更OK
// ▼ Gemini APIキー(★スクリプトプロパティから取得★)
const GEMINI_API_KEY =
PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY');
// ▼ Geminiのエンドポイントとモデル
const GEMINI_ENDPOINT =
'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent';
/******** 設定ここまで ********/
/**
* メイン処理
* - フォーム連携スプシ(A)の全行を読む
* - 「問い合わせ内容+回答」が同じものを1つにまとめる(完全一致ベース)
* - 清書用Q&Aスプシ(B)をヘッダー以外クリアして、
* UniqueなQ&AだけをGeminiで整形 → タイムスタンプ・質問・回答の3列で書き込む
*/
function rebuildQAWithGemini() {
// フォーム連携スプシ(A)
const srcSS = SpreadsheetApp.openById(SRC_SPREADSHEET_ID);
const srcSheet = srcSS.getSheetByName(SRC_SHEET_NAME);
// 清書用Q&Aスプシ(B = このスクリプトが紐づいているファイル)
const destSS = SpreadsheetApp.getActiveSpreadsheet();
const destSheet = destSS.getSheetByName(DEST_SHEET_NAME);
const lastRow = srcSheet.getLastRow();
if (lastRow < 2) {
Logger.log('フォーム連携シートにデータがありません');
clearDestBody_(destSheet);
return;
}
// A2〜最終行まで取得
const lastCol = srcSheet.getLastColumn();
const values = srcSheet.getRange(2, 1, lastRow - 1, lastCol).getValues();
// ---- 「問い合わせ内容+回答」が同じものを1つにまとめる ----
const uniqueMap = new Map(); // key: normalized question+answer, value: 最初のレコード
for (let i = 0; i < values.length; i++) {
const row = values[i];
const timestamp = row[0]; // A: タイムスタンプ
const department = row[1]; // B: 部署名
const name = row[2]; // C: 氏名
const email = row[3]; // D: メールアドレス
const question = row[4]; // E: 問い合わせ内容
const answer = row[5]; // F: 回答(人間による入力)
// 回答が空の行はスキップ(まだ対応中など)
if (!answer) continue;
// 正規化してキーを作る(前後の空白や改行の違い程度は潰す)
const key = normalizeQAKey_(question, answer);
if (!uniqueMap.has(key)) {
uniqueMap.set(key, {
timestamp,
department,
name,
email,
question,
answer
});
}
}
Logger.log(`Unique Q&A count: ${uniqueMap.size}`);
// ---- 清書用Q&Aシート(B)をヘッダー以外クリア ----
clearDestBody_(destSheet);
// ---- UniqueなQ&Aだけ、1件ずつGeminiで整形して書き込み ----
for (const [, record] of uniqueMap) {
const { timestamp, department, name, email, question, answer } = record;
const prompt = createPromptForQA(question, answer, department, name, email, timestamp);
const rawResponse = callGemini(prompt);
if (!rawResponse) {
Logger.log(`Gemini返却なし: timestamp=${timestamp}, name=${name}`);
continue;
}
const qa = parseGeminiJson_(rawResponse);
if (!qa) {
Logger.log(`JSONパース失敗: timestamp=${timestamp}, name=${name}, raw=${rawResponse}`);
continue;
}
// 清書用Q&Aスプシには「タイムスタンプ」「問い合わせ内容」「問い合わせ回答」の3列で書き込む
destSheet.appendRow([
timestamp,
qa.question,
qa.answer
]);
// レート制限対策(必要に応じて調整)
Utilities.sleep(1200);
}
}
/**
* 質問+回答から、重複判定用のキーを作る
*/
function normalizeQAKey_(question, answer) {
const normalize = (text) => {
if (!text) return '';
return String(text)
.replace(/\s+/g, ' ')
.trim();
};
return normalize(question) + '||' + normalize(answer);
}
/**
* 清書用Q&Aシートのヘッダー以降をクリアする
*/
function clearDestBody_(sheet) {
const lastRow = sheet.getLastRow();
const lastCol = sheet.getLastColumn();
if (lastRow > 1) {
sheet.getRange(2, 1, lastRow - 1, lastCol).clearContent();
}
}
/**
* RAG用にQ&Aを整形するためのプロンプト
* - 出力はJSONのみ({"question": "...", "answer": "..."})
*/
function createPromptForQA(question, answer, department, name, email, timestamp) {
return `
あなたは社内ヘルプデスクのナレッジ整形担当です。
以下の「質問」と「それに対する回答」を、ベクトル検索ベースのRAGシステムに登録しやすいように整形してください。
【目的】
- 後から検索しやすいように、「問い合わせ内容」と「それに対する回答」をそれぞれ分かりやすい文章に整形する
- 問い合わせの背景・前提条件・質問の意図を、質問文の中に自然に含める
- 回答は、要点が分かりやすく、社内ドキュメントとしてそのまま使えるレベルの文章にする
- 不要な個人情報(電話番号・住所など)は削除する
- 間違った情報を推測で含めないようにしてください
【出力フォーマット(重要)】
- JSON形式のみで出力してください。日本語の説明文などは一切書かないでください。
- フォーマットは次のとおりです:
{
"question": "整形後の問い合わせ内容をここに入れる。日本語、敬体(〜です/〜ます)。",
"answer": "整形後の問い合わせ回答をここに入れる。日本語、敬体(〜です/〜ます)。"
}
【メタ情報】
- 部署: ${department}
- 氏名: ${name}
- メールアドレス: ${email}
- 問い合わせ日時: ${timestamp}
【元の質問】
${question}
【元の回答】
${answer}
`;
}
/**
* Gemini APIを呼び出してテキストを生成する関数
* - 戻り値は「モデルが返したテキスト全体」(JSON文字列の想定)
*/
function callGemini(prompt) {
if (!GEMINI_API_KEY) {
throw new Error('GEMINI_API_KEY がスクリプトプロパティに設定されていません。');
}
const url = `${GEMINI_ENDPOINT}?key=${GEMINI_API_KEY}`;
const payload = {
contents: [
{
parts: [
{ text: prompt }
]
}
]
};
const options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
const res = UrlFetchApp.fetch(url, options);
const code = res.getResponseCode();
const body = res.getContentText();
if (code !== 200) {
Logger.log('Gemini error: ' + code + ' ' + body);
return null;
}
const data = JSON.parse(body);
const text =
data.candidates &&
data.candidates[0] &&
data.candidates[0].content &&
data.candidates[0].content.parts &&
data.candidates[0].content.parts[0].text;
return text || '';
}
/**
* Geminiからの応答(text)をJSONとしてパースする
*/
function parseGeminiJson_(rawText) {
if (!rawText) return null;
let text = String(rawText).trim();
// ```json ... ``` で囲まれている場合に中身だけ抜き出す
const codeBlockMatch = text.match(/```(?:json)?([\s\S]*?)```/i);
if (codeBlockMatch) {
text = codeBlockMatch[1].trim();
}
// 一番最初の { から最後の } までを抜き出す
const firstBrace = text.indexOf('{');
const lastBrace = text.lastIndexOf('}');
if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
return null;
}
const jsonPart = text.substring(firstBrace, lastBrace + 1);
try {
const obj = JSON.parse(jsonPart);
if (!obj.question || !obj.answer) {
return null;
}
return {
question: String(obj.question),
answer: String(obj.answer)
};
} catch (e) {
Logger.log('JSON.parse error: ' + e + ' / text=' + jsonPart);
return null;
}
}ソースコードの「const SRC_SPREADSHEET_ID = `ここにフォーム連携スプシのIDを貼る`; // ← ここだけ編集」の「SRC_SPREADSHEET_ID」を以下の手順に従って変更してください。
- 先程の「社内問い合わせ対応Q&Aスプレッドシート」を開く
- URLの/d/と/edit の間にある文字をコピー
- コピーしたものを貼り付け
例えば”https://docs.google.com/spreadsheets/d/1ni4Adjvpasdhfjx3/edit…”のようなURLの場合は、下のようになります。
const SRC_SPREADSHEET_ID = "1ni4Adjvpasdhfjx3”;
次に、サイドバーの「プロジェクトの設定」をクリックします。

下部にある、「スクリプトプロパティを追加」をクリックします。

プロパティに「GEMINI_API_KEY」を入力してください。
値はAlza...から始まるGeminiのAPIキーを入力し、「スクリプトプロパティを保存」をクリックします。
APIキーは外部に共有しないよう注意してください。

なぜ生成AIにナレッジを生成させるのか?
生のQ&Aでは口語や個人情報などナレッジファイルとして扱うには、不要な情報が多く含まれます。
また、Q&Aのデータには重複する内容も含まれるために、Q&Aをもとに生成AIに校正させることで、RAGの回答精度を向上させることが可能です。
GeminiのAPIキーの発行
Google AI Studioを開き、サイドバーのGet API keyをクリックし、「Copy API key」を選択してください。

冒頭でも紹介した通り、GeminiAPIの使用に際して「無料サービス」を利用する場合は、Geminiに送信されたコンテンツと生成された回答がGoogleのプロダクトの改善に用いられるのでプライベート情報や機密情報、個人情報は送信しないようにして下さい。
「有料サービス」の場合はこの限りではありません。
詳細はこちらをご覧ください。
GASを実行してみる
コード.gsを開いて実行をクリックする。

最初の実行のみ承認が必要になるので、「権限を確認」を押下して「詳細を表示」→「無題のプロジェクト(安全ではないページ)に移動」をクリックします。(GASでスプレッドシートやGeminiにアクセスする際に必ず承認が必要になります。)

全て選択をクリックし、続行します。

ENSOUのカスタムチャットボットを作成
ENSOU AIにログインしてカスタムチャットボットを選択します。
ENSOU AIのフリープランはメールアドレスのみでご利用いただけます。
ご登録はこちら。

すると以下のような画面になるので、Googleドライブ連携で問い合わせ対応用の任意のナレッジファイルと「ENSOU生成Q&A」を登録し、カスタム指示にプロンプトを入力します。

以下、カスタム指示の全文になります。実際に使用する場合は、{Googleフォームの回答用URL}を変更してください。
## 出力のルール
出力の最後に必ず「解決しない場合は {Googleフォームの回答用URL}からお問い合わせください」を挿入してください。使い方のイメージ
具体的な使い方としては以下のようになります。
- カスタムチャットボットでGoogleフォームのURLを提示する
- ユーザーがカスタムチャットボットで回答を得られない場合はGoogleフォームから問い合わせ申請をさせる
- 担当者が回答を「社内問い合わせ対応Q&A」のF列に入力する
- 定期的にGASを実行する
まとめ
本記事ではENSOUのカスタムチャットボットをベースにした、問い合わせ対応とナレッジへの登録を自動化するエージェントの作成方法について、実際の画面やコピペできるコードを紹介しながら分かりやすく解説しました。
このエージェントの導入により、従業員が本来注力すべき業務に集中できるようになり、組織全体の生産性向上が期待できます。
ぜひ、この記事を参考に問い合わせ対応&ナレッジの自動生成エージェントを作成してみてください。
株式会社Digeonでは法人向けにGPT-5.1をセキュアに使えるサービスである「ENSOUチャットボット」を提供しています。
業務全体でのナレッジ活用に興味のある方は、ENSOUチャットボットをお試しください。
無料ですぐに使えるフリープランのご利用開始はこちらから👇
ご相談、無料トライアルは以下からお問い合わせください👇
サービス紹介資料はこちらからダウンロードいただけます👇
あわせて読む
ENSOUのカスタムチャットボットや問い合わせ対応に関する記事です。よろしければこちらの記事も併せてご覧ください。

.png)


.webp)
