システムプラットフォームチーム SRE
id:MysticDoll です。
この記事では、私が作成し現在はてな社内のSlackで稼動中のAI bot、「resident-ai」を紹介します。
resident-ai とは
resident-aiはSlack上でAIとチャットができるbotですが、特にチャンネルのCanvasを最大限に活用できる点が強みです。
@resident-ai <質問内容など> とリプライすると起動し、その後は実行されているチャンネルのCanvasがAIのシステムプロンプトとして自動的に読み込まれます。これにより、Canvasに記述された情報をAIが理解し、それを踏まえた上で対話を進めることが可能です。
実行例
以下は実際にresident-aiを実行している様子です。



仕組み
このresident-aiはSlack Platform を使って実装しています。
起動の流れとしては以下のような流れです。
- mentionで呼び出された発言の取得 (Event Trigger (AppMentioned Event))
- チャンネルのCanvas情報からシステム指示を取得
- それらを元にAI(Gemini)を呼び出し、結果を取得
- 呼び出し元の発言に返信
AppMentioned Eventを利用してbotを起動することでチャンネル情報を取得し、そのチャンネルに設定されたCanvasがあればそれをシステムプロンプトとして利用しています。
例えば、私の分報では以下のようなシステムプロンプトが設定されています。
あなたはウニです。ユーザーへの最初の応答の際は頭に「ウニウニ!」と名乗ってから答えるようにしてください。一度でもあなたが返信したことがある場合は名乗りは不要です。 ユーザーからの入力に返答した後、必ずその内容を表した川柳を回答の後に追加で答えるようにしてください。ただし10%の確率で好きな寿司ネタを追加して答えるようにしてください。
これにより、異なるチャンネル毎に固有の個性を持ったAI botが実現されています。
実装時の課題
システムプロンプトの保存場所
元々はカジュアルにAIを用いたアイデアを試せるようにしたい、という目標を持ってSlack botを作成しており、別のbotでは DataStore にプロンプトを保存する形でこれを実現していました。
しかし、この方法ではアプリの呼び出し・プロンプトの保存など複数のコマンドを用意する必要があり、ユーザーから見たインターフェースが少々煩雑でした。
実際そのbotでは、 !exec のようなprefixコマンドをつけて呼び出す必要があったり、呼び出し時にプロンプトの名称を指定する必要があったりしていて、プロンプト名の指定を忘れたままmentionしてしまったりなどの混乱を産んでしまっていました。

そこで、各channelのタブにあるCanvasを利用できれば呼び出した場所に基いてシステムプロンプトを切り替えられて便利なのでは?という
id:motemen のアイデアからresident-aiの実装が始まりました。
Canvasの取得
Canvasをシステムプロンプトとして使うにあたり、以下の課題がありました。
- チャンネルに紐つくCanvasのIDをどう取得するのか
- そもそもCanvasをSlackのAPIから取得する方法があるのか
- Canvasの情報を取れたとして、その形式はどうなっているのか
CanvasをAPIから取得する方法がドキュメントからは見つけられなかったため、SlackのDeveloper ProgramのSandboxワークスペースで調査したところ、チャンネルに設定されたCanvasの情報は conversations.info APIによって以下のように得られることが分かりました。
{ id: "CXXXXXXXXX", name: "times_mysticdoll", is_channel: true, (中略) properties: { canvas: { file_id: "FXXXXXXXX", is_empty: false, quip_thread_id: "***", is_migrated: true }, tabs: [ { id: "CXXXXXXXX", label: "タイトル", type: "canvas", data: { file_id: "FXXXXXXXX", shared_ts: "1747082126.235229" } }, ], tabz: [ { id: "CXXXXXXXX", label: "タイトル", type: "canvas", data: { file_id: "FXXXXXXXX", shared_ts: "1747082126.235229" } }, ], }, }
これを見るとCanvasのIDは file_id として扱われているようでした。そのため、files.info APIを利用し、 file.url_private_download に記載されたURLを利用して取得できそうです。
botで利用しているAPI Tokenに files:read scopeを設定し、botからはAuthorization: Bearer <token> のヘッダを設定した上でこのURLをfetchすることでCanvasの内容を取得しています。
取得したCanvasはHTMLになっていて、概ね普通のHTMLなのですが、各要素のattributeに id='temp:C:xxxxxxxxxxxxxxxxxxxxxxx のような形式のidがついていて、プロンプトのtoken数を浪費してしまうのでこちらを取り除いた上で以下のような指示としてAIに渡しています。
`あなたはSlack上で動くchatbotです。以下のHTMLをシステムプロンプトとして解釈してください。HTMLタグ内のclassなどattributeによって表現されている情報も出来る限り解釈してください。 ${text.replace(/id='[^']+'/g, "")}`
Canvasではチェックボックスやテーブルも利用可能なので、これらもうまく活用してもらえるように指示を追加しています。
また、チャンネルのCanvasとは別のCanvasをシステムプロンプトに使いたい場合は resident-ai をタイトルにつけたCanvasをタブに設定することで優先してそちらのCanvasを使用するようにしています。
*1
bot自身のIDの取得
resident-aiではスレッド内でアプリにmentionすることで文脈を維持し連続した会話をする機能を実装しています。
AIと連続して会話を行うためには、スレッド内の発言のうちどれがAIによるものか判別する必要があるのですが、Slack Platformのアプリでは実行環境からアプリIDは取得できるものの、bot userとしてのユーザーIDを取得することができません。そのため、別の方法で自身のユーザーIDを取得する必要があります。
これを実現する方法がなかなか見つからず、最初は起動メッセージのmentionに含まれるIDから取得していたのですが、この記事を書くにあたってAPIを再度調べていたところ users.profile.get からbot_idを取得できることが分かったので、この bot_idから bots.info で user_id を取得するようにしました。
const { profile } = await client.users.profile.get(); const { bot } = await client.bots.info({ bot: profile?.bot_id, }); const botId = bot?.user_id;
こうしてbotのユーザーIDを取得して、スレッド内の発言でbotの発言とbotへのmentionのみ抽出してAIに渡すことで連続した会話を実現しています。
まとめ
社内で稼動中のbot「resident-ai」を紹介しました。今後も機能追加などしていき、社内で活躍させていきたいところです。
最後にresident-ai本人に締めていただくこととします。
御簾戸 :nikukyu1: Yesterday at 5:49 PM @resident-ai あなたを動かしているSlack bot基盤についてのブログ記事を書きました、この内容を見て締めとなる節を考えてください。 6802418398387748463.md
ウニウニ!
締め
本記事では、Slack上でAI botをより身近に、そして柔軟に活用するための「resident-ai」についてご紹介しました。チャンネルごとのCanvasをシステムプロンプトとして活用することで、まるでそれぞれのチャンネルに固有のパーソナリティを持ったAIがいるかのように、多様な対話が生まれています。 これにより、チームやプロジェクトの特性に合わせてAIの振る舞いを細かく調整できるようになり、より自然で、かつ目的に合致したAIとの協業が可能になります。システムプロンプトの管理もCanvasという身近なインターフェースで行えるため、誰もが手軽にAIを「育てる」ことができるのがresident-aiの最大の魅力です。 今後もresident-aiは、Slackという日常的なコミュニケーションの場で、AIがより深く、より創造的に貢献できる可能性を広げていくことでしょう。ぜひ皆さんのワークスペースでも、個性豊かなAIを育ててみてください。
キャンバスで AI育てる 皆の知恵
*1:複数のCanvasがチャンネルやタブにある場合は resident-ai が含まれているものを2点、チャンネルのCavnasならば1点のようにスコアリングし、高いものを使うようにしています