LayerX エンジニアブログ

LayerX の エンジニアブログです。

ChatGPT Bot を new Slack Platform で動かしてみた

こんにちは、LayerX の Enabling Team の suguru です。

ChatGPT のAPIが公開されて、ものすごい勢いで Slack へのインテグレーションが始まりそうです。 API 利用する場合は、会話の内容が学習などに使われることはない、ということで、安心して利用できそうです。 LayerX でも、早速ChatGPTが応答してくれるボットを作ってみることにしました。

今回は new Slack Platform を使うことにしました。new Slack Platform は、Slack Cloud と呼ばれる Slack 側のサーバーでコードを動かす機能があります。Slack にコードをデプロイするだけで、サーバーの準備等をしなくても ChatGPT を自分たちの SlackBot として埋め込むことができます。

注 - new Slack Platform は執筆時点(2023-03-05)でまだベータ版なので、実際に利用する際はご注意ください。

api.slack.com

まずは、Introduction を元に Slack のアプリを作ります。(アプリ作成には権限や、管理者の承認が必要になることがあります) new Slack Platform は、基本的に CLI から操作を行います。

slack CLI で認証情報を使うために、まずはログインします。

slack login

Slack App のプロジェクトを作ります。今回は Hello World を使います

slack create slack-chatgpt

最初に、OpenAI のAPIにアクセスして、結果を返答する Custom Function をつくります。この Custom Function が、Slack Cloud 上で稼働します。 今回は functions/chatgpt_function.ts とします。ちなみに Function 内の console.log は、slack activity コマンドで流れるログを確認できるので、 デバッグログなどを入れておくと便利です。

import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";

export const ChatGPTFunction = DefineFunction({
  callback_id: "chatgpt_function",
  title: "Ask ChatGPT",
  description: "Ask questions to ChatGPT",
  source_file: "functions/chatgpt_function.ts",
  input_parameters: {
    properties: {
      user_id: {
        type: Schema.slack.types.user_id,
        description: "user ID",
      },
      question: {
        type: Schema.types.string,
        description: "question to chatgpt",
      },
    },
    required: ["question", "user_id"],
  },
  output_parameters: {
    properties: {
      answer: {
        type: Schema.types.string,
        description: "Answer from AI",
      },
    },
    required: ["answer"],
  },
});

export default SlackFunction(
  ChatGPTFunction,
  async ({ inputs, env }) => {
    // omit user id expressions
    const content = inputs.question.replaceAll(/\<\@.+?\>/g, " ");
    const role = "user";

    const res = await fetch(
      "https://api.openai.com/v1/chat/completions",
      {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${env.OPENAI_API_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          model: "gpt-3.5-turbo",
          messages: [{ role, content }],
        }),
      },
    );
    if (res.status != 200) {
      const body = await res.text();
      return {
        error: `Failed to call OpenAPI AI. status:${res.status} body:${body}`,
      };
    }
    const body = await res.json();
    console.log("chatgpt api response", { role, content }, body);
    if (body.choices && body.choices.length >= 0) {
      const answer = body.choices[0].message.content as string;
      return { outputs: { answer } };
    }
    return {
      error: `No choices provided. body:${JSON.stringify(body)}`,
    };
  },
);

次に、上記のFunctionをつないだワークフローを作成します。workflows/chatgpt_workflow.ts としましょう。 この workflow は、先程の ChatGPTFunction をコールするステップと、その結果を Channel にメッセージとして送信するステップを持っています。

import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";
import { ChatGPTFunction } from "../functions/chatgpt_function.ts";

const ChatGPTWorkflow = DefineWorkflow({
  callback_id: "chatgpt_workflow",
  title: "Ask AI",
  description: "Respond question to users",
  input_parameters: {
    properties: {
      channel_id: {
        type: Schema.slack.types.channel_id,
      },
      user_id: {
        type: Schema.slack.types.user_id,
      },
      question: {
        type: Schema.types.string,
      },
    },
    required: ["channel_id", "question"],
  },
});

// OpenAI をコールする Step
const chatGPTFunctionStep = ChatGPTWorkflow.addStep(
  ChatGPTFunction,
  {
    user_id: ChatGPTWorkflow.inputs.user_id,
    question: ChatGPTWorkflow.inputs.question,
  },
);

// メッセージをチャネルに送信する Step
ChatGPTWorkflow.addStep(Schema.slack.functions.SendMessage, {
  channel_id: ChatGPTWorkflow.inputs.channel_id,
  message: chatGPTFunctionStep.outputs.answer,
});

export default ChatGPTWorkflow;

次に、Slack App のイベントトリガーを作ります。今回はアプリがメンションされた発言をターゲットにします。 このトリガーは、対象となるチャンネルIDのリストが必要になります。実際にBotを稼働させたいチャネルを登録してください。

triggers/appmention_trigger.ts

import { Trigger } from "deno-slack-api/types.ts";
import ChatGPTWorkflow from "../workflows/chatgpt_workflow.ts";

const appmentionTrigger: Trigger<typeof ChatGPTWorkflow.definition> = {
  type: "event",
  name: "Trigger workflow with app mentioned",
  workflow: `#/workflows/${ChatGPTWorkflow.definition.callback_id}`,
  event: {
    event_type: "slack#/events/app_mentioned",
    channel_ids: [
      "CXXXXXXXX" // <- ここにアプリが機能するチャンネルIDのリストを投入する
    ],
  },
  inputs: {
    channel_id: { value: "{{data.channel_id}}" },
    user_id: { value: "{{data.user_id}}" },
    question: { value: "{{data.text}}" },
  },
};

export default appmentionTrigger;

最後に、アプリのマニフェストファイル manifest.ts を編集します。 今回作成したワークフローをマニフェストに登録します。 OpenAI のAPIをコールするため、outgoingDomains に api.openai.com を追加します。 また、デフォルトのアイコンは味気ないので、 assets/icon.png にボットの画像を置いておきましょう。 botScopes は、chat bot に提供する権限スコープを記述します。 name は Slack 内でのアプリ名になります。Botの名前もそのままこれになるので、適宜好きな名前にしてください。

import { Manifest } from "deno-slack-sdk/mod.ts";
import ChatGPTWorkflow from "./workflows/chatgpt_workflow.ts";

export default Manifest({
  name: "ChatGPT",
  description: "chat bot answers any questions with official ChatGPT API",
  icon: "assets/icon.png",
  workflows: [
    ChatGPTWorkflow,
  ],
  outgoingDomains: [
    "api.openai.com",
  ],
  botScopes: [
    "commands",
    "app_mentions:read",
    "chat:write",
    "chat:write.public",
    "channels:read",
  ],
});

このワークフローを動かすには、OpenAI の API Key が必要になります。 ローカルで稼働する場合は、プロジェクトのディレクトリに.env ファイルを作成し、APIキーを設定します。

OPENAI_API_KEY=sk_XXXXXXXXXX

これらのファイルを作成したら、まずはローカル環境でプロセスを動かし、動作確認することができます。 new Slack Platform は、ローカルで動かしたプロセスを、実際に Slack 上で動作確認しながら確認することができ、とても便利です。 slack run コマンドで、このモードでプロセスを起動することができます。

起動すると、対象となる Workspace を選択する状態になります。 さらに、開発用アプリを自動的にインストールします。(権限がなければ、アプリリクエストが管理者に飛びます)

slack run
? Choose a local environment layer-x XXXXXXXX
Updating dev app install for workspace "LayerX"

⚠️  Breaking Changes Announced (February 2023)
   Impacts apps using datastores, external auth, interactivity, and built-in functions
   Learn more: https://api.slack.com/future/changelog

⚡ Triggers installed to the app
   Trigger 1
     Trigger ID:   XXXXXXX
     Trigger Type: event
     Trigger Name: Trigger workflow with app mentioned
     Trigger Created Time: 2023-03-04 11:16:51 +09:00
     Trigger Updated Time: 2023-03-04 11:16:51 +09:00

✨  87b3bb4d-758b-40b8-b2 of LayerX
Connected, awaiting events

アプリがインストールされていれば、Slackに接続され、実際に対象のチャネルでメンションをして発言すると、 ローカルのプロセスにイベントが飛んできます。 開発用アプリは、ChatGPT (dev) のように、名前に (dev) suffix がついた状態で登録されます。

試しに Slack でこのアプリをメンションしてみます。

テスト用チャネルでメンションする

ローカルのターミナルには、実際にイベントログが流れます。もし動作しない場合は、トリガーのチャネルIDが間違っていないかなど確認する必要があります。

動作確認ができたら、次に Slack Cloud にコードをアップロードします。

slack deploy

このコマンドで、アプリケーションのインストールが自動的に行われます。(権限がない場合は、自動でインストールされる) OpenAIのAPI Keyを下記のコマンドで登録します。

slack env add
? Choose a deployed environment layer-x XXXXXX
? Variable name OPENAI_API_KEY
? Variable value **********

アプリのインストールができたら、試しに Slack で発言してみます。

Slack Cloud にデプロイした App にメンションする

というわけで、サーバーの用意なしに ChatGPT を簡単に Slack 上のBotにする方法でした。 new Slack Platform は他にも拡張性の高い機能がたくさんあるので、ChatGPT をもっと活用したアプリを作ることに挑戦していきたいと思います。

ソースコードは今後社内用のロジックが入ってくる可能性が高いため、記事内でのみの公開にしました 🙇‍♂️