一人でサービスやアプリを運営していると、問い合わせへの対応が思った以上に時間を奪っていることに気づきます。
「バグ報告なのか、機能要望なのか、それとも単純な使い方の質問なのか」を読んで判断して、緊急度を考えて、場合によっては別の担当に回す。一件一件は5分もかからない作業ですが、積み重なると午前中があっという間に消えていきます。
Gemini API を使えば、この分類・優先度付け作業を自動化できます。フォームから送られてきた問い合わせを Gemini API で解析し、Slack に整理された形で通知するシステムを Python で作る手順を順番にご紹介します。
作るものの全体像
システムの流れはシンプルです。
- フォームから問い合わせが届く
- Python スクリプトが Gemini API に内容を送る
- Gemini API がカテゴリ分類・優先度スコアを JSON で返す
- 結果を Slack の指定チャンネルに通知する
特別なインフラは不要です。Python と Gemini API キー、Slack の Webhook URL があれば今日から動かせます。既存のフォームツール(Google フォーム、Typeform、自作のお問い合わせページ)に関係なく導入できます。
環境の準備
必要なパッケージをインストールします。
pip install google-genai requests環境変数を設定します。
export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"Gemini API キーは Google AI Studio から無料で取得できます。API の基本的な使い方については Gemini API クイックスタート も参考にしてください。
Slack の Webhook URL は、Slack のワークスペース管理画面から「Incoming Webhooks」アプリを有効化して作成します。特定のチャンネルに紐付けて発行されます。
Gemini API で問い合わせを分類する
核心部分のコードです。Gemini API に問い合わせ内容を送り、構造化された分析結果を受け取ります。
import os
import json
from google import genai
from google.genai import types
client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
def classify_inquiry(subject: str, body: str) -> dict:
"""
問い合わせを分類し、優先度スコアを付ける。
Returns:
{
"category": str,
"priority": int (1-5),
"priority_reason": str,
"summary": str,
"suggested_response_time": str
}
"""
prompt = f"""
以下の問い合わせを分析してください。
件名: {subject}
本文: {body}
以下の形式でJSONを返してください:
{{
"category": "bug_report | feature_request | usage_question | billing | other",
"priority": 1から5の整数(5が最高優先度),
"priority_reason": "優先度の判断理由を1文で",
"summary": "問い合わせ内容を30文字以内で要約",
"suggested_response_time": "即時対応 | 24時間以内 | 3営業日以内 | 1週間以内"
}}
優先度の基準:
- 5: サービス全体に影響するバグ、データ消失の可能性
- 4: 特定機能が使えない、決済に関する問題
- 3: パフォーマンス問題、一部機能の不具合
- 2: 機能要望、改善提案
- 1: 一般的な使い方の質問
"""
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=prompt,
config=types.GenerateContentConfig(
response_mime_type="application/json",
temperature=0.1, # 分類タスクは低めで安定させる
),
)
return json.loads(response.text)temperature=0.1 に設定しているのには理由があります。分類タスクでは創造性より一貫性が重要で、同じ内容の問い合わせに対して毎回ほぼ同じ分類が返ってくるようにしたいからです。私が試したところ、temperature=0 だとモデルによっては JSON 以外の文字が先頭に混入することがあったので、0.1 が安定していました。
response_mime_type="application/json" を指定すると、Gemini が必ず JSON 形式で返してくれます。これを指定しない場合は markdown のコードブロックで包まれて返ってきて、パースが面倒になります。構造化出力についての詳しい解説は Gemini API 構造化出力ガイド を参考にしてください。
Slack に通知を送る
分類結果を視覚的に分かりやすく Slack に送ります。優先度に応じた色付けとメンションで、見落としを防ぎます。
import requests
from datetime import datetime
def send_slack_notification(inquiry_data: dict, classification: dict) -> bool:
"""
分類結果を Slack に送信する。
優先度別に色とメンションを変える。
Returns:
True if success, raises on error
"""
category_emoji = {
"bug_report": "🐛",
"feature_request": "✨",
"usage_question": "❓",
"billing": "💳",
"other": "📝",
}
priority_colors = {
5: "#FF0000", # 赤:緊急
4: "#FF6600", # オレンジ:高
3: "#FFCC00", # 黄:中
2: "#00CC00", # 緑:低
1: "#999999", # グレー:最低
}
category = classification.get("category", "other")
priority = classification.get("priority", 1)
emoji = category_emoji.get(category, "📝")
color = priority_colors.get(priority, "#999999")
# 優先度に応じたヘッダーテキスト
if priority >= 5:
header = f"🚨 <!channel> 緊急対応が必要な問い合わせ"
elif priority >= 4:
header = f"⚠️ 優先対応が必要な問い合わせ {emoji}"
else:
header = f"{emoji} 新しい問い合わせ(優先度 {priority})"
payload = {
"text": header,
"attachments": [
{
"color": color,
"fields": [
{
"title": "要約",
"value": classification.get("summary", ""),
"short": False,
},
{
"title": "カテゴリ",
"value": category,
"short": True,
},
{
"title": "優先度",
"value": f"{'⭐' * priority} ({priority}/5)",
"short": True,
},
{
"title": "推奨対応時間",
"value": classification.get("suggested_response_time", ""),
"short": True,
},
{
"title": "判断理由",
"value": classification.get("priority_reason", ""),
"short": False,
},
{
"title": "差出人",
"value": inquiry_data.get("email", "不明"),
"short": True,
},
],
"footer": f"受信: {datetime.now().strftime('%Y-%m-%d %H:%M JST')}",
}
],
}
response = requests.post(
os.environ["SLACK_WEBHOOK_URL"],
json=payload,
timeout=10,
)
response.raise_for_status()
return True全体をまとめて動かす
エントリーポイントとなる関数です。エラーハンドリングも含めて本番で使える形にしてあります。
def handle_inquiry(inquiry_data: dict) -> dict:
"""
問い合わせデータを受け取り、分類して通知する。
inquiry_data:
subject: 件名
body: 本文
email: 送信者メールアドレス
name: 送信者名(任意)
"""
try:
# Gemini API で分類・優先度付け
classification = classify_inquiry(
subject=inquiry_data.get("subject", ""),
body=inquiry_data.get("body", ""),
)
# Slack に通知
send_slack_notification(inquiry_data, classification)
return {"status": "success", "classification": classification}
except json.JSONDecodeError as e:
# Gemini が JSON 以外を返した場合のフォールバック
print(f"JSON parse error: {e}")
fallback = {
"category": "other",
"priority": 3,
"priority_reason": "自動分類に失敗(手動確認が必要)",
"summary": inquiry_data.get("subject", "")[:30],
"suggested_response_time": "24時間以内",
}
send_slack_notification(inquiry_data, fallback)
return {"status": "fallback", "error": str(e)}
except requests.exceptions.RequestException as e:
# Slack への通知が失敗した場合
print(f"Slack notification failed: {e}")
return {"status": "classified_but_notify_failed", "classification": classification}
except Exception as e:
print(f"Unexpected error: {e}")
raise
# 動作確認
if __name__ == "__main__":
test_inquiry = {
"subject": "ログインできなくなりました",
"body": "昨日まで使えていたのに、今朝からログインしようとすると「認証エラー」が表示されます。パスワードは変えていません。",
"email": "tanaka@example.com",
"name": "田中太郎",
}
result = handle_inquiry(test_inquiry)
print(f"分類結果: {result['classification']}")
# 期待する結果: category=bug_report, priority=4, suggested_response_time=24時間以内実際に使ってみて分かったこと
このシステムを自分のサービスで2ヶ月ほど使ってみて、いくつか気づいたことがあります。
プロンプトの品質がそのまま分類精度に直結します。 カテゴリ名を日本語で定義するか英語で定義するか、優先度の基準をどれだけ具体的に書くかで、分類の一貫性が大きく変わりました。最初は日本語でカテゴリを定義していたのですが、英語の方が誤分類が少なかったです。
曖昧な問い合わせへの対応は思ったより賢い。 「使い方が分からない」だけの問い合わせは、どのカテゴリにすべきか難しそうですが、Gemini は文脈から usage_question に分類してくれることが多く、大体正しい判断でした。一方で、感情的に書かれた怒りのバグ報告は優先度を少し高めに評価する傾向があります。これは人間的には正しい判断だと思います。
コストは無視できる範囲です。 Gemini 2.5 Flash を使えば、問い合わせ1件あたりの API 料金は1円以下。月100件程度なら実質ゼロコストで運用できます。
エラーハンドリングについては Gemini API エラー処理ガイド も参考になります。
Google フォームと連携する
Google フォームを使っている場合は、Flask か FastAPI で Webhook エンドポイントを作り、Apps Script からそこに POST するのが最も手軽です。
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/webhook/inquiry", methods=["POST"])
def inquiry_webhook():
data = request.get_json()
if not data:
return jsonify({"error": "Invalid JSON"}), 400
result = handle_inquiry(data)
return jsonify(result)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)Cloud Run にデプロイすれば、リクエストが来た時だけ起動するサーバーレス構成で月数百円から運用できます。Railway も設定が少なく個人開発者には使いやすいです。
全体を振り返って
問い合わせ対応を自動化して一番実感したのは「見落とし」がなくなったことです。以前は夜間に届いた緊急の問い合わせを翌朝まで気づかないことがありましたが、今は Slack への通知で即座に確認できます。
まずは既存のフォームにこの仕組みを繋いで、Slack の動作を確認してみてください。コードはそのままコピーして動かせる状態にしてあります。カテゴリ定義や優先度基準は、ご自身のサービスの性質に合わせて調整するとより精度が上がります。