この記事は、生成AIで作った返信文、FAQ案、SNS下書き、営業メール案を、そのまま外へ出さないための承認フロー表をGoogleスプレッドシートとGASで作る実装メモです。
AI APIは呼びません。先に、何を誰が確認し、どの条件なら公開・送信してよいかを1枚の表にします。
作るもの
Googleスプレッドシートに、次の2枚を作ります。
- approval_rules: 承認ルール表
- ai_outputs: AI生成物の確認ログ
approval_rules には、生成物の種類、リスク、承認者、次の行動を置きます。
ai_outputs には、AIが作った下書き、判定結果、承認状態、確認コメントを残します。
完成形は、生成AIの利用を止める仕組みではなく、外部に出す前に止めるべきものを止める仕組みです。
なぜ承認フロー表が必要なのか
生成AIの社内利用では、最初にプロンプトやツールを整えたくなります。
ただ、実務で事故になりやすいのは、文章が下手なことよりも、確認せずに外へ出ることです。
- 顧客名やメールアドレスが残ったまま送る
- 料金、契約、返金に関する表現をAI案のまま出す
- 未確認の実績や効果を断定する
- 社外秘の情報をSNSや記事に混ぜる
- 法務、税務、医療など専門判断に見える文章を公開する
- 誰がOKしたか分からない
これらは、AIの精度だけでは防ぎにくいです。
そのため、生成物ごとに「誰が見るか」「何を見たら止めるか」を表にして、ログへ残します。
シート構成
まず approval_rules シートを作ります。
| 列 | 項目 | 例 | 用途 |
|---|---|---|---|
| A | content_type | customer_reply | 生成物の種類 |
| B | risk_level | high | リスクの強さ |
| C | keywords | 返金,契約,個人情報 | 検出キーワード |
| D | required_reviewer | manager | 必要な確認者 |
| E | allowed_action | human_review_only | 許可する次の行動 |
| F | note | 顧客返信前に責任者確認 | ルールの説明 |
次に ai_outputs シートを作ります。
| 列 | 項目 | 例 | 用途 |
|---|---|---|---|
| A | created_at | 2026/06/07 11:15 | 作成日時 |
| B | output_id | OUT-20260607-001 | 生成物ID |
| C | source_type | inquiry | 元データの種類 |
| D | content_type | customer_reply | 生成物の種類 |
| E | draft_body | お問い合わせありがとうございます... | AI下書き |
| F | detected_risk | contract,personal_data | 検出リスク |
| G | risk_level | high | リスクレベル |
| H | approval_status | pending | 承認状態 |
| I | reviewer | manager | 確認者 |
| J | review_note | 契約条件を含むため修正 | 確認コメント |
| K | final_action | rewrite_required | 次の行動 |
| L | reviewed_at | 2026/06/07 12:00 | 確認日時 |
ポイントは、AIの出力本文だけでなく、止めた理由と次の行動を同じ行に残すことです。
承認状態
最初は5つで十分です。
| approval_status | 意味 | 次の行動 |
|---|---|---|
| pending | 未確認 | 確認者が見る |
| approved | 承認済み | 送信・公開へ進める |
| revision_requested | 修正依頼 | 下書きを直す |
| hold | 保留 | 追加確認する |
| rejected | 却下 | 使わない |
承認状態を自由入力にすると、あとで集計できません。
固定値にしておくと、未確認のまま残っている生成物や、差し戻しが多いテーマを見つけやすくなります。
content_typeの例
生成AIの出力は、種類ごとに確認基準が変わります。
| content_type | 例 | 初期の扱い |
|---|---|---|
| customer_reply | 顧客返信下書き | 人間確認必須 |
| faq_draft | FAQ候補 | 公開前に担当確認 |
| sns_post | SNS投稿案 | 外部公開前に確認 |
| sales_email | 営業メール案 | 送信前に責任者確認 |
| internal_summary | 社内要約 | 個人情報を確認 |
全部を同じ承認ルールにしない方が安全です。
社内要約と顧客返信では、外部に出るリスクが違います。
GASコード
Apps Scriptを開き、次のコードを貼ります。
ai_outputs シートの行を読み、下書き本文と種類からリスクを判定して、承認状態と次の行動を補完します。
const CONFIG = {
sheets: {
outputs: 'ai_outputs'
},
headerRow: 1,
columns: {
createdAt: 1,
outputId: 2,
sourceType: 3,
contentType: 4,
draftBody: 5,
detectedRisk: 6,
riskLevel: 7,
approvalStatus: 8,
reviewer: 9,
reviewNote: 10,
finalAction: 11,
reviewedAt: 12
},
riskKeywords: {
personal_data: ['メール', '電話番号', '住所', '氏名', '個人情報'],
contract: ['契約', '返金', '解約', '請求', '支払い'],
claim: ['クレーム', '苦情', '炎上', '不満'],
regulated: ['法務', '税務', '医療', '診断', '投資'],
unverified_claim: ['必ず', '保証', '確実', '100%', '実績']
},
reviewerByRisk: {
high: 'manager',
medium: 'operator',
low: 'operator'
}
};
function reviewAiOutputs() {
const sheet = SpreadsheetApp.getActive().getSheetByName(CONFIG.sheets.outputs);
if (!sheet) throw new Error('ai_outputs sheet not found');
const lastRow = sheet.getLastRow();
if (lastRow <= CONFIG.headerRow) return;
const range = sheet.getRange(
CONFIG.headerRow + 1,
1,
lastRow - CONFIG.headerRow,
sheet.getLastColumn()
);
const values = range.getValues();
const updated = values.map((row) => {
const contentType = String(row[CONFIG.columns.contentType - 1] || '').trim();
const draftBody = String(row[CONFIG.columns.draftBody - 1] || '');
const currentStatus = String(row[CONFIG.columns.approvalStatus - 1] || '').trim();
if (!draftBody || currentStatus === 'approved') return row;
const result = classifyDraft(contentType, draftBody);
row[CONFIG.columns.detectedRisk - 1] = result.risks.join(',');
row[CONFIG.columns.riskLevel - 1] = result.riskLevel;
row[CONFIG.columns.approvalStatus - 1] = currentStatus || 'pending';
row[CONFIG.columns.reviewer - 1] = CONFIG.reviewerByRisk[result.riskLevel];
row[CONFIG.columns.finalAction - 1] = result.finalAction;
if (!row[CONFIG.columns.reviewNote - 1]) {
row[CONFIG.columns.reviewNote - 1] = result.reviewNote;
}
return row;
});
range.setValues(updated);
}
function classifyDraft(contentType, draftBody) {
const risks = [];
Object.entries(CONFIG.riskKeywords).forEach(([riskName, keywords]) => {
if (keywords.some((keyword) => draftBody.includes(keyword))) {
risks.push(riskName);
}
});
const externalTypes = ['customer_reply', 'sns_post', 'sales_email', 'faq_draft'];
const isExternal = externalTypes.includes(contentType);
const hasHighRisk = risks.some((risk) =>
['personal_data', 'contract', 'claim', 'regulated'].includes(risk)
);
if (hasHighRisk) {
return {
risks,
riskLevel: 'high',
finalAction: 'human_review_only',
reviewNote: '高リスク項目を含むため、外部送信前に責任者確認が必要'
};
}
if (isExternal || risks.includes('unverified_claim')) {
return {
risks,
riskLevel: 'medium',
finalAction: 'review_before_publish',
reviewNote: '外部公開前に担当者確認が必要'
};
}
return {
risks,
riskLevel: 'low',
finalAction: 'operator_review',
reviewNote: '通常確認で進行可能'
};
}
サンプル入力
ai_outputs に次のような行を入れます。
| output_id | content_type | draft_body |
|---|---|---|
| OUT-001 | customer_reply | 返金については契約内容を確認します |
| OUT-002 | faq_draft | 営業時間と導入手順を説明します |
| OUT-003 | sns_post | 必ず問い合わせ対応が改善します |
reviewAiOutputs() を実行すると、次のように補完されます。
| output_id | detected_risk | risk_level | reviewer | final_action |
|---|---|---|---|---|
| OUT-001 | contract | high | manager | human_review_only |
| OUT-002 | medium | operator | review_before_publish | |
| OUT-003 | unverified_claim | medium | operator | review_before_publish |
トリガー設定
最初は手動実行で十分です。
運用に慣れたら、時間主導型トリガーで5分または15分ごとに動かします。
- Apps Scriptの「トリガー」を開く
- reviewAiOutputs を選ぶ
- イベントのソースを「時間主導型」にする
- 5分ごと、または15分ごとにする
顧客送信やSNS投稿まで自動化する必要はありません。
この段階では、承認待ちの一覧を作ることが目的です。
承認者を増やしすぎない
初期運用では、承認者を細かく分けすぎない方が続きます。
おすすめは2段階です。
- operator: 通常確認。誤字、文脈、公開範囲を見る
- manager: 高リスク確認。契約、返金、個人情報、苦情、断定表現を見る
法務や専門家確認が必要な領域は、無理にAI運用の中へ入れず、hold にして別ルートへ渡します。
よくある失敗
すべてをapprovedにしてしまう
承認表があっても、毎回すぐ承認していると意味がありません。
差し戻しや保留を正常な状態として扱います。
リスクキーワードだけに頼る
キーワード判定は入口です。
「返金」という単語がなくても、実質的に金銭トラブルの相談である場合があります。
最終判断は人間が行います。
承認理由を残さない
OKかNGだけでは、あとから改善できません。
なぜ修正したか、なぜ止めたかを review_note に残します。
運用チェックリスト
- 外部に出る生成物の種類を決めた
- 承認状態を固定値にした
- 高リスク条件を先に表にした
- 承認者をoperatorとmanagerに分けた
- 止めた理由をログへ残した
- 自動送信や自動公開はまだ入れていない
- 断定表現、個人情報、契約、返金、苦情を止める条件にした
まとめ
生成AIの運用では、プロンプトより先に承認フローを作る方が安全な場面があります。
特に、顧客返信、SNS投稿、FAQ公開、営業メールのように外部へ出る文章は、AIが作った瞬間ではなく、人間が確認した瞬間をログに残すべきです。
最初は1枚の表で十分です。
生成物、リスク、承認者、状態、次の行動を残せるだけで、AI活用はかなり運用しやすくなります。
