🤖

OpenAIのGPT-3を使って、ChatGPTライクに会話ができるSlackBotを作る

2023/02/21に公開1

🤖 作ったもの

OpenAI が提供している GPT-3 を利用して、Slack で ChatGPT ライクに会話ができる SlackBot を作りました。

ChatGPT のように前の会話を考慮して回答してくれるくれるところがポイントです。

🛠️ 仕組み

前の会話を考慮して返答してくれるようにする実装の肝は prompt です。

毎回以下のような prompt を OpenAI の Completion API に渡すことで、前の会話を考慮した回答をもらっています。

const prompt = `
あなたは優秀なSlackBotです。あなたの知識とこれまでの会話の内容を考慮した上で、今の質問に正確な回答をしてください。

### これまでの会話:
${prevMessageText}

### 今の質問:
${text}

### 今の質問の回答:
`;

prevMessageText の変数には Slack のスレッドの前のメッセージををまとめた文字列を渡します。textの部分が今回の質問です。

以下 Slack のBolt フレームワーク をつかった場合の実装例です。

export const useReplyEvent = (app: App) => {
  app.event("message", async ({ event, client, logger }) => {
    const { thread_ts: threadTs, bot_id: botId, text } = event as any;
    // botの返信またはスレッドのメッセージでなければ何もしない
    if (botId || !threadTs) {
      return;
    }

    // スレッドのメッセージを取得
    const threadMessagesResponse = await client.conversations.replies({
      channel: event.channel,
      ts: threadTs,
    });
    const messages = threadMessagesResponse.messages?.sort(
      (a, b) => Number(a.ts) - Number(b.ts)
    );

    // GPT Botへの返信でなければ何もしない
    if (!(messages![0].text?.includes(GPT_BOT_NAME) && messages![0].bot_id)) {
      return;
    }

    try {
      // OpenAIのレスポンスは時間がかかる場合が多いので、仮のメッセージを投稿する
      const thinkingMessageResponse = await postAsGptBot({
        client,
        channel: event.channel,
        threadTs,
        text: "...",
      });

      // 会話の履歴を取得して結合。最大6件まで
      const prevMessages =
        messages!.length < 6 ? messages!.slice(1, -1) : messages!.slice(-6, -1);
      const prevMessageText =
        prevMessages.map((m) => `- ${m.text}`).join("\n") || "";

      // 回答メッセージの作成 with OpenAI
      const prompt = `
あなたは優秀なSlackBotです。あなたの知識とこれまでの会話の内容を考慮した上で、今の質問に正確な回答をしてください。

### これまでの会話:
${prevMessageText}

### 今の質問:
${text}

### 今の質問の回答:
`;
      const configuration = new Configuration({
        apiKey: config.openai.key,
      });
      const openAIClient = new OpenAIApi(configuration);
      const completions = await openAIClient.createCompletion({
        model: "text-davinci-003",
        prompt: prompt,
        max_tokens: 1000,
        top_p: 0.5,
        frequency_penalty: 1,
      });
      const message = completions.data.choices[0].text;

      // 仮のメッセージを削除する
      await client.chat.delete({
        channel: event.channel,
        ts: thinkingMessageResponse.ts!,
      });

      // 回答メッセージを投稿する
      if (message) {
        await postAsGptBot({
          client,
          channel: event.channel,
          threadTs,
          text: message,
        });
      } else {
        throw new Error("message is empty");
      }
    } catch (e) {
      logger.error(e);
      await postAsGptBot({
        client,
        channel: event.channel,
        threadTs,
        text: "大変申し訳ございません。エラーです。別スレッドでやり直してください。",
      });
    }
  });
};

上記のコードを実際に実装した PR です。

https://github.com/kawamataryo/tell-me-bot/pull/11

以前記事で紹介した tell-me-bot という便利な質問回答 Bot にチャット機能を追加しています。

https://b.hatena.ne.jp/entry/s/zenn.dev/ryo_kawamata/articles/tell-me-bot-slack-app

終わりに

prompt を少し工夫することで、毎回単発のリクエストながら会話がなりたっているようにする点など、prompt エンジニアリングをとても感じる体験でした。いろいろ考えさせられますね。

Discussion