この記事は、AI導入前に 自動化しない業務 を登録し、問い合わせログやCRMメモの前段で止めるための実装メモです。
AI APIの呼び出しや、返信文の自動生成は扱いません。先に、GoogleスプレッドシートとGoogle Apps Scriptで「AIに任せない条件」「許可する補助範囲」「確認者」「次の行動」を管理します。
作るもの
次の3枚のシートを使います。
| シート | 役割 |
|---|---|
scope_rules |
自動化しない条件と許可範囲を登録する |
inquiry_log |
問い合わせや業務メモを受ける |
scope_decisions |
AI処理へ進めるか、人間確認へ戻すかを残す |
この構成の目的は、AIにできることを増やす前に、AIへ渡さない範囲を明文化することです。
なぜ先に「自動化しない業務」を登録するのか
問い合わせ対応や営業メモでは、通常ケースだけを見るとAI下書きが便利に見えます。
しかし実際には、次のような内容が混ざります。
- 返金、解約、契約条件に触れる相談
- 個人情報や社内情報を含む問い合わせ
- 苦情、不満、採用、不採用など感情的な影響が大きい対応
- 根拠が未確認の社外向け説明
- 担当者や責任者の判断が必要な例外ケース
この線引きがないままAI下書きを入れると、担当者ごとに判断がぶれます。
最初に「AIは補助まで」「AIへ送らない」「人間確認へ戻す」を登録しておくと、AI活用が止まるのではなく、任せてよい範囲がはっきりします。
scope_rules の列
まず、ルール表を作ります。
scope_id
workflow_area
trigger_keywords
contains_personal_data
contains_contract_or_money
scope
ai_allowed_actions
blocked_actions
reviewer
reason
next_step
enabled
サンプルは次のようにします。
scope_id,workflow_area,trigger_keywords,contains_personal_data,contains_contract_or_money,scope,ai_allowed_actions,blocked_actions,reviewer,reason,next_step,enabled
NOSCOPE-001,inquiry,"返金,解約,キャンセル",false,true,human_review_required,"summarize,classify","auto_reply,auto_send",cs_owner,金銭や契約に関わる判断を含むため,責任者が返信方針を確認する,true
NOSCOPE-002,inquiry,"住所,電話番号,メールアドレス",true,false,summarize_only,"summarize_after_masking","send_raw_text_to_ai,auto_reply",operations_owner,個人情報を含む可能性があるため,マスキング後に要約だけ使う,true
NOSCOPE-003,support,"営業時間,資料請求,使い方",false,false,draft_allowed,"summarize,draft_reply,faq_candidate","auto_send",cs_owner,定型案内に近いが送信前確認は残すため,AI下書き後に人間が送信確認する,true
scope は細かくしすぎない方が運用しやすいです。
| 値 | 意味 |
|---|---|
draft_allowed |
AI下書きまで進めてよい |
summarize_only |
要約や分類だけ許可する |
human_review_required |
AI処理前または外部送信前に人間確認が必要 |
ai_blocked |
AIへ送らない |
inquiry_log の列
問い合わせ側には、AIへ渡す前の材料だけを置きます。
request_id
received_at
source
workflow_area
message_summary
contains_personal_data
contains_contract_or_money
sentiment_risk
requested_action
ここでは、問い合わせ本文の原文を長く残す前提にしません。AIへ渡す前の判断に必要な要約、フラグ、依頼内容だけで始めます。
GASでルールを読み込む
次のコードは、scope_rules と inquiry_log を読み、判定結果を scope_decisions に追記します。
const SHEETS = {
rules: 'scope_rules',
inquiries: 'inquiry_log',
decisions: 'scope_decisions',
};
function runScopeDecision() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const rules = readObjects_(ss.getSheetByName(SHEETS.rules))
.filter((rule) => String(rule.enabled).toLowerCase() === 'true');
const inquiries = readObjects_(ss.getSheetByName(SHEETS.inquiries));
const decisionSheet = ss.getSheetByName(SHEETS.decisions);
const existingIds = new Set(
readObjects_(decisionSheet).map((row) => String(row.request_id))
);
const rows = [];
for (const inquiry of inquiries) {
const requestId = String(inquiry.request_id || '').trim();
if (!requestId || existingIds.has(requestId)) continue;
const decision = decideScope_(inquiry, rules);
rows.push([
requestId,
new Date(),
decision.scope_id,
decision.scope,
decision.ai_allowed_actions,
decision.blocked_actions,
decision.reviewer,
decision.reason,
decision.next_step,
]);
}
if (rows.length > 0) {
decisionSheet
.getRange(decisionSheet.getLastRow() + 1, 1, rows.length, rows[0].length)
.setValues(rows);
}
}
function decideScope_(inquiry, rules) {
for (const rule of rules) {
if (matchesRule_(inquiry, rule)) {
return {
scope_id: rule.scope_id,
scope: rule.scope,
ai_allowed_actions: rule.ai_allowed_actions,
blocked_actions: rule.blocked_actions,
reviewer: rule.reviewer,
reason: rule.reason,
next_step: rule.next_step,
};
}
}
return {
scope_id: 'DEFAULT-HOLD',
scope: 'human_review_required',
ai_allowed_actions: '',
blocked_actions: 'auto_reply,auto_send',
reviewer: 'operations_owner',
reason: '該当ルールがないため',
next_step: '新しい業務パターンとして確認する',
};
}
function matchesRule_(inquiry, rule) {
if (rule.workflow_area && rule.workflow_area !== inquiry.workflow_area) {
return false;
}
if (isTrue_(rule.contains_personal_data) && !isTrue_(inquiry.contains_personal_data)) {
return false;
}
if (
isTrue_(rule.contains_contract_or_money) &&
!isTrue_(inquiry.contains_contract_or_money)
) {
return false;
}
const keywords = String(rule.trigger_keywords || '')
.split(',')
.map((word) => word.trim())
.filter(Boolean);
if (keywords.length === 0) return true;
const targetText = [
inquiry.message_summary,
inquiry.requested_action,
inquiry.sentiment_risk,
].join(' ');
return keywords.some((keyword) => targetText.includes(keyword));
}
function readObjects_(sheet) {
const values = sheet.getDataRange().getValues();
if (values.length < 2) return [];
const headers = values[0].map((header) => String(header).trim());
return values.slice(1).map((row) => {
return headers.reduce((obj, header, index) => {
obj[header] = row[index];
return obj;
}, {});
});
}
function isTrue_(value) {
return String(value).toLowerCase() === 'true';
}
scope_decisions の列
判定結果は、あとから説明できる形で残します。
request_id
decided_at
scope_id
scope
ai_allowed_actions
blocked_actions
reviewer
reason
next_step
ここで大切なのは、AIへ渡したかどうかだけでなく、なぜ止めたか、次に誰が見るかを残すことです。
トリガー設定
最初は時間主導トリガーで十分です。
- Apps Scriptエディタで
runScopeDecisionを選ぶ - トリガーを追加する
- イベントのソースを「時間主導型」にする
- 5分または10分ごとに実行する
フォーム送信時トリガーにしてもよいですが、最初はまとめて処理する方が検証しやすいです。
運用チェックリスト
公開や本番利用の前に、次を確認します。
- 実際の問い合わせ本文や個人情報を、不要にAIへ送らない
-
ai_blockedとhuman_review_requiredの違いを担当者が理解している -
DEFAULT-HOLDが増えたら、ルールを追加する -
auto_sendは最初から許可しない - 返金、契約、法務、採用、不採用、苦情は人間確認へ戻す
- 判定理由と次の行動が空欄になっていない
まとめ
AI導入前に「自動化しない業務」を登録しておくと、AI活用は狭まるのではなく、任せてよい範囲が明確になります。
最初に作るものは、大きなワークフローではありません。scope_rules、inquiry_log、scope_decisions の3枚と、単純なGASで十分です。
Miraigentでは、AI導入前の無料診断でも、ツール選定より先に「AIへ渡さない情報」「人間確認に戻す条件」「判断ログへ残す項目」を整理します。
