0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

生成AIの承認フローをGoogleスプレッドシートとGASで1枚の表にする

0
Last updated at Posted at 2026-06-20

この記事は、生成AIで作った返信文、FAQ案、SNS下書き、営業メール案を、そのまま外へ出さないための承認フロー表をGoogleスプレッドシートとGASで作る実装メモです。

AI APIは呼びません。先に、何を誰が確認し、どの条件なら公開・送信してよいかを1枚の表にします。

作るもの

AI生成物を承認ゲートで確認し、公開・修正・保留へ分ける流れ

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分ごとに動かします。

  1. Apps Scriptの「トリガー」を開く
  2. reviewAiOutputs を選ぶ
  3. イベントのソースを「時間主導型」にする
  4. 5分ごと、または15分ごとにする

顧客送信やSNS投稿まで自動化する必要はありません。

この段階では、承認待ちの一覧を作ることが目的です。

承認者を増やしすぎない

初期運用では、承認者を細かく分けすぎない方が続きます。

おすすめは2段階です。

  • operator: 通常確認。誤字、文脈、公開範囲を見る
  • manager: 高リスク確認。契約、返金、個人情報、苦情、断定表現を見る

法務や専門家確認が必要な領域は、無理にAI運用の中へ入れず、hold にして別ルートへ渡します。

よくある失敗

すべてをapprovedにしてしまう

承認表があっても、毎回すぐ承認していると意味がありません。

差し戻しや保留を正常な状態として扱います。

リスクキーワードだけに頼る

キーワード判定は入口です。

「返金」という単語がなくても、実質的に金銭トラブルの相談である場合があります。

最終判断は人間が行います。

承認理由を残さない

OKかNGだけでは、あとから改善できません。

なぜ修正したか、なぜ止めたかを review_note に残します。

運用チェックリスト

  • 外部に出る生成物の種類を決めた
  • 承認状態を固定値にした
  • 高リスク条件を先に表にした
  • 承認者をoperatorとmanagerに分けた
  • 止めた理由をログへ残した
  • 自動送信や自動公開はまだ入れていない
  • 断定表現、個人情報、契約、返金、苦情を止める条件にした

まとめ

生成AIの運用では、プロンプトより先に承認フローを作る方が安全な場面があります。

特に、顧客返信、SNS投稿、FAQ公開、営業メールのように外部へ出る文章は、AIが作った瞬間ではなく、人間が確認した瞬間をログに残すべきです。

最初は1枚の表で十分です。

生成物、リスク、承認者、状態、次の行動を残せるだけで、AI活用はかなり運用しやすくなります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?