😄

第4回 Slackから生成AIを呼び出せるようにする

2024/12/12に公開

本エントリはUbie 生成AI Advent Calendar 2024の12日目、「社内用生成AI Webアプリケーションをどのように作っているか」の第4回です。

前回は、第3回 生成AIのモデルと外部データを連携可能にするについて説明しました。今回は、Slackから生成AIを呼び出す方法について解説します。

生成AIをSlackから呼び出したくなる

第1、2、3回では、生成AIを利用できるWebアプリケーションについて説明しました。生成AIを利用できるWebアプリケーションの機能が充実するのは良いことですが、一方で、なにかをやる時にWebページを毎回開かなければならないのはやや面倒でもあります。社内のユーザが常に滞在しているSlackなどのコラボレーションツールから生成AIを利用できると便利そうです。

Slack botを作る

なにはなくとも、とりあえずSlackから利用できるbotを作る必要があります。ただこの辺りは単純にSlack botを作るだけの話なので、大幅に割愛します。公式のドキュメントを読むのがよいでしょう。

https://tools.slack.dev/bolt-js/ja-jp/getting-started/

Dev Genius[1]は、@slack/boltを利用して、Socket ModeでSlackに接続しています。 SlackからのイベントをWebhookで受け取ることもできますが、Dev Geniusは社内限定のアプリケーションのため、外部からのアクセスを制限しています。この制限をWebhookで通過させることは煩雑になるため、Socket Modeを採用しています。

現在はSlackの次のイベントを受け取るようにしています。

  • app_mention メンションされたとき
  • reaction_added リアクションが追加されたとき

実装のイメージは以下の通りです。

import { App } from "@slack/bolt";
const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  appToken: process.env.SLACK_APP_TOKEN,
  socketMode: true,
});

app.event("app_mention", async ({ event, client }) => {
  // 処理
});

app.event("reaction_added", async ({ event, client }) => {
  // 処理
});

メンションに対して返信する

まずはメンションに対して返信することを考えてみます。いくつかのアプローチがあるかと思いますが、Dev Geniusではメンションに対してスレッドを作成し、その中で返信するようにしています。こうすることで、スレッド全体をスコープとした会話が可能になります。スレッド内でのメンションであれば、そのままスレッド内で返信を行います。

app.event("app_mention", async ({ event, client }) => {
    // スレッド全体を読み込む
    const messages = await getRawMessages(event.channel, event.ts);
    // いろんな処理を経て...
    const response = // 生成AIのレスポンス

    // スレッドに返信する
    await app.client.chat.postMessage({
        channel: event.channel,
        text: `<@${event.user}> ${response}`,
        thread_ts: event.ts,
    });
});

export type Message = {
  user?: string;
  text?: string;
};

// スレッド全体を読み込む
export const getRawMessages = async (
  channelId: string,
  messageId: string,
): Promise<Message[] | null> => {
  const messageList: Message[] = [];
  let hasMore = false;
  let cursor = undefined;
  do {
    try {
      const replies = await app.client.conversations.replies({
        channel: channelId,
        ts: messageId,
        cursor: cursor,
      });
      if (replies == null) {
        return null;
      }
      if (replies.messages != null) {
        replies.messages.forEach((message) => {
          if (message.text != null) {
            const attachmentsText =
              message.attachments
                ?.map((attachment) => attachment.text ?? attachment.fallback)
                .filter((text) => text != null)
                .map((text) =>
                  text
                    .split("\n")
                    .map((line) => `> ${line}`)
                    .join("\n"),
                )
                .join("\n") ?? "";

            messageList.push({
              ...message,
              text: attachmentsText
                ? `${message.text}\n${attachmentsText}`
                : message.text,
            });
          }
        });
      }
      hasMore = replies.has_more ?? false;
      cursor = replies.response_metadata?.next_cursor;
    } catch (error) {
      console.error("Error fetching raw messages:", error);
      return null;
    }
  } while (hasMore);

  return messageList;
};

デプロイして動かせば、晴れてSlack上で生成AIを利用できます。


デフォルトではgemini-1.5-pro-001を用いて返信します

Slack上からプリセットを使う

Slack上から生成AIを利用できるだけでもそれなりに便利です。しかし、Webアプリケーション版の既存のアセットであるプリセット[2]を利用できると、更に便利になりそうです。

Dev Geniusでは、特定の記法を用いることで、Slack上からプリセットを呼び出せるようにしています。メンション中に [preset=:id] を含めると、対応したプリセットを探し、システムプロンプトやモデル、temperatureを利用して返信を作成します。


ここでは割愛しますが、プリセットにはURL埋め込みの設定ができ、notionなどの動的な情報ソースを展開して利用する機能などもあります

記法は単なる内部的な仕様なので、処理方法等は特に説明しません。正規表現でidを取り出して探す、といったシンプルな実装です。

リアクションによるプリセットの呼び出しをサポートする

Slackからプリセットを使って呼び出せるのは便利ですが、特別な記法を使うのはかなりハードルが高いです。入力した文章・文脈から自動的に利用すべきプリセットを選定できればよいですが、それはそれでかなり難しいでしょう。

そこで、リアクションによるプリセットの呼び出しをサポートしました。メッセージにリアクションをつけると、そのメッセージに対してプリセットを呼び出すようにしました。このアイデアと実装は同僚の@syu_creamによるものです。

プリセットの編集画面でSlackリアクションの割り当てを設定できるようにしています。

これにより、Slack上でリアクションをすると、反応してくれるようになりました。


めちゃくちゃしょうもない励ましをしてくれるプリセット。社内でも人気です

組織で自由に拡張が可能であることの価値

リアクションによるプリセットの呼び出しをサポートすると、早速便利な設定が追加されていきました。個人的に気に入っている機能をふたつほど紹介します。

次の例は、社内のPBI(プロダクトバックログアイテム)の書き方に沿って、PBI案を作成してくれるプリセットです。Slackで流れる要望や課題にリアクションをつけるだけでたたき台がサッと作れます。タイトルだけの中身なしPBIの撲滅に役立つのではと思っています。

内容をそのままで使えるわけではないが、たたき台としてはかなり役立つ

また、次のように、Github URLを含んだメッセージにリアクションすることで、内容を説明してくれます。これは第3回 生成AIのモデルと外部データを連携可能にするとも複合したプリセットです。

こうした動きを受けて、あれもリアクション化して使えるのではないか、といった会話が各所で発生しており、組織で自由に拡張できる仕組みの価値を実感しています。社内の様々なニーズに応じて、チーム独自のプリセットを作成し、Slackリアクションと組み合わせることで、日々の業務に役立つものを自律的に作れるようになりました。

まとめ

Slackから生成AIを呼び出す方法について紹介しました。どちらかというと、どのように呼び出しているか、というほうが正確なニュアンスかもしれません。Slack botとして単独で呼び出せるだけではやや物足りないところですが、プリセットや外部情報との連携ができることで、Slackから利用できる価値が高まっていると感じています。

脚注
  1. Ubieの社内で内製している生成AI Webアプリケーションです。普段はdev爺と呼ばれています。 ↩︎

  2. プリセットについては第2回 プロンプトを再利用する、プリセットストアと共有機能を参照してください。 ↩︎

Ubie テックブログ

Discussion