皆さん、日々の業務でこんなSlackのやり取り、心当たりはありませんか?
Aさん: 「このOOOに関するお問い合わせ、誰が担当でしたっけ?」
Bさん: 「以前は〇〇さんでしたが、今はちょっと分からないですね…」
Cさん: 「過去のスレッドを検索しても情報が古いし、状況によって担当者も違うんですよね…。とりあえず前回対応してくれた△△さんにメンションしておきますか…」
あるいは、自分宛てのメンションだと思って急いで確認したら、
「(あ、これ自分じゃなくて他開発チーム向けの依頼だった…)」
といった経験。
一つ一つは些細なことですが、この「担当者を探す」「自分宛てか確認する」という地味な作業は、私たちの集中力を削がれて、意外と工数が取られてしまいます。
この問題を解決するため、問い合わせ内容を AI が判断し、適切な担当者へ自動でメンションしてくれる Slack Bot を作ってみました。
本記事ではその開発の裏側をご紹介します。
▼ こんな方におすすめの記事です
- 手軽に一時受付担当者を自動的に割り振る slack bot を作成したい方
- notion DB x slack bot x Gemini API を用いた自動化に興味がある方
- この記事でわかること
- コンセプト
- アーキテクチャと技術選定
- 全体構成
- 実装のポイント
- 結果
- 課題と今後の展望
- まとめ
- Notion をデータベース(知識ベース)として利用した AI Bot のコンセプト
- Google Apps Script (GAS) を使った、サーバーレスで手軽な実装方法
- Gemini API を活用した、問い合わせ内容からの担当者推定ロジック
今回の Bot 開発で最も重要視したのは、「AIにゼロから考えさせない」 という点です。
汎用的な AI にいきなり「担当者は誰?」と聞いても、正確な答えは返ってきません。
そこで、チームの担当者リストという「カンニングペーパー」を AI に渡し、その情報に基づいて賢く判断してもらうアーキテクチャを採用しました。
そして、そのカンニングペーパーの役割を担うのが、情報集約のハブとして最近注目されている Notion データベースです。
全体構成
Bot は以下のシンプルなフローで動作します。
-
Slack
a. 監視対象のチャンネルでメッセージを検知(Botへのメンションなど) -
Google Apps Script (GAS)
a. Slackからのイベントを受け取る
b. Notion API を叩き、最新の担当者リストを取得する
c. 問い合わせ内容と担当者リストを Gemini API に渡し、担当者を推定させる -
Slack
a. GAS が Gemini の回答を元に、担当者へメンション付きでスレッドに返信する
なぜこの構成なのか?
今回の課題は「あると嬉しいけど、無いと業務が止まるほどではない」というレベル感のものです。
そのため、本格的な RAG(Retrieval-Augmented Generation) 環境を AWS などで構築するのは、コストと工数が見合いません
そこで、「手軽に」「素早く」 実現できることを最優先とし、以下の理由で技術選定を行いました。
-
Google Apps Script (GAS)
- サーバーレス環境なので、サーバーの管理が不要
- Google アカウントさえあれば無料で始められる
- Slack の Event Subscriptions からの POST リクエストを受け取る Web アプリとして簡単に機能する
- 外部 API(Notion, Gemini) の呼び出しも標準機能で十分可能
-
Gemini API (via Google AI Studio)
- Google AI Studio から API キーを即時発行でき、すぐに試せる
-
gemini-1.5-flash
は、高速かつ低コストで、今回の用途には十分な性能を持つ
-
Notion
- API が公開されており、データベースの読み書きが容易
- エンジニア以外でも担当者情報の更新が簡単に行える
実装のポイント
1. Notionデータベースの準備
まず、カンニングペーパーとなる Notion データベースを用意します。最低限、以下のプロパティがあれば機能します。
プロパティ名 | 種類 | 説明 |
---|---|---|
担当者名 |
テキスト | Slack での表示名 |
SlackID |
テキスト | メンションに使用する Slack のメンバーID |
対応領域 |
テキスト | 担当業務のキーワード(例:開発, 採用, 経費精算) |
対応領域
の記述が AI の精度を左右する重要なキーとなります。
できるだけ具体的かつ網羅的に記述するのがポイントです。
今回は例として、シンプルな Notion データベースを用意しました。
2. GASによる実装 (抜粋)
GAS では、Slack からの POST リクエストを受け取る doPost(e)
関数を実装します。
// Slackからのイベントを受け取るメイン関数
function doPost(e) {
try {
const postData = JSON.parse(e.postData.contents);
const eventData = postData.event;
// 指定チャンネルからの、ボットではない新規投稿のみを処理
if (eventData && eventData.type === 'message' && !eventData.bot_id && !eventData.thread_ts && eventData.channel === TARGET_CHANNEL_ID) {
// 新規投稿のテキスト情報
const inquiryText = eventData.text;
// 投稿されたチャンネル ID
const channelId = eventData.channel;
// 投稿されたメッセージのスレッド情報
const threadTs = eventData.ts;
// Notion カラ取得したメンバー情報う
const members = getMembersFromNotion();
// AI に担当者「リスト」を取得させる
const assignedPeople = getAssignedPeopleFromGemini(inquiryText, members);
// AI が一人以上の担当者を特定した場合、 slack への投稿処理を行う
if (assignedPeople && assignedPeople.length > 0) {
const userIds = assignedPeople.map(person => person.slackUserId);
postToSlackThread(channelId, threadTs, userIds);
} else {
// Geminiが担当者を特定できなかったため、フォールバック「総合窓口」を探す
const fallbackPeople = members.filter(member => member.area.includes('総合窓口'));
if (fallbackPeople.length > 0) {
const userIds = fallbackPeople.map(person => person.slackUserId);
postToSlackThread(channelId, threadTs, userIds, "担当者が見つからなかったため、総合窓口にご連絡します。");
} else {
postToSlackThread(channelId, threadTs, [], "担当者を自動で割り当てられませんでした。手動でのご確認をお願いします。");
}
}
}
return ContentService.createTextOutput(JSON.stringify({'status':'success'})).setMimeType(ContentService.MimeType.JSON);
} catch (err) {
console.error("エラーが発生しました:", err);
}
}
// Notion APIを呼び出してデータを取得する関数(Notion DB のインテグレーションを発行して、API を叩く)
function getMembersFromNotion() {
// Notion APIを叩いてDBのデータを取得する処理
// ...
return parsedNotionData;
}
// Gemini APIを呼び出して担当者を推定する関数のイメージ
function getAssignedMembersFromGemini(message, memberList) {
const modelName = 'gemini-1.5-flash-latest';
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`;
// プロンプトを組み立て
const prompt = `
以下の問い合わせ内容に対して、最も適切に対応できる担当者を下記の担当者リストから**全員**選んでください。
担当者の「対応領域」を最優先で考慮してください。
返答は、該当者の JSON オブジェクトを要素とする**配列形式**で返してください。
該当者がいない場合は、空の配列 [] を返してください。
他のテキストは一切含めないでください。
ただ、「総合窓口」という担当領域は、担当者の選定の考慮外としてください。
# 問い合わせ内容
${message}
# 担当者リスト
${JSON.stringify(memberList)}
`;
# 出力形式 (例)
[
{"name": "総合窓口", "area": "総合窓口", "slackUserId": "UXXXXXXXXX"},
{"name": "採用担当者", "area": "採用", "slackUserId": "UXXXXXXXXXX"}
]
// APIリクエストの実行とレスポンスの解析
// ...
return assignedMemberIds;
}
// Slackに投稿する関数のイメージ
function postToSlack(text, thread_ts) {
// Slack API (chat.postMessage) を叩いてスレッドに返信する処理
// ...
}
3. プロンプトの作り方
作成したプロンプトのポイントとしては、以下です。
- 役割と目的の明確化
- 最も適切に対応できる担当者を全員選んでくださいと具体的に指示
- 考慮事項の指定
- 「対応領域」を最優先で考慮させることで、判断の軸をブレさせない
- 出力形式の厳密な指定
- JSON オブジェクトを要素とする配列形式で他のテキストは一切含めないと指定することで、GASでの後続処理を劇的に楽にする
- 例外ルールの設定
- 「総合窓口」という担当領域は、…考慮外としてくださいのように、特殊なルールを明記することで、意図しない挙動を防ぐ
もちろん、今回作成したプロンプトも改善点が多くあるので、ぜひ「もっとこうした方が良い」等あればコメントいただけると幸いです!
結果として、問い合わせからわずか数秒で、適切な担当者へ自動でメンションが飛ぶようになりました!
※ 個人ワークスペースにつき、メンションを暫定的に文字列にしています。
担当者が見つからなかった場合は、事前に設定した「総合窓口」にメンションするフォールバック機能も備えています。
もちろん、完璧なシステムではありません。
現状見えている課題と、今後の改善案を最後にお伝えできればと思います
課題1:モデルの精度
gemini-1.5-flash を利用しているため、時々意図しない挙動があります。
例えば、問い合わせ文に複数のキーワードが含まれていると、関連する担当者全員にメンションが飛んでしまうことがあります。
改善案
プロンプトの改良を行い「最も関連性の高い担当者を1名だけ選んで」のようにユースケースに応じて指示を変更する。
もしくは、プロンプトにいくつか正解例(入力と期待する出力)を含めることで、AI の回答精度を向上させる。
Notion DB の整備を行い、対応領域のキーワードをより精査し、重複や曖昧さをなくす。
課題2:メンテナンスコスト
AI の精度は Notion DB の質に依存するため、情報の鮮度を保つためのメンテナンスが不可欠であること
改善案
運用ルールの徹底を行いチーム内で担当業務が変更になった際に、必ず Notion を更新するフローを確立する。
※ そもそも上記の運用が行われてからの導入が良いかもしれません。
今回の開発を通じて、いくつかの学びがありました。
AI は「質の良いカンペ(データ)」が命であること
AI の能力は、与える情報の質と量に大きく左右されます。
高品質な知識ベース(今回はNotion DB)を用意することが、今回の場合だと重要でした。
プロンプトは超重要
プロンプトの書き方一つで、出力結果は劇的に変わります。
いかに AI に「誤解させず」「期待通りの動きをさせるか」を考えることが、今回のメインと言ってもいいほど重要でした。
GAS はプロトタイピングの強い味方
「ちょっとした業務改善」レベルのアイデアであれば、やっぱり GAS は最高のツールです。
※ サーバーレスで手軽に始められ、外部 API 連携も柔軟に行えるため
この記事が、皆さんのチームの「地味な時間泥棒」を撲滅する一助となれば幸いです。
Views: 0