【ChatGPT×Slack】SlackチャンネルにChatGPTを召喚!ChatGPT APIを使った要約アプリの作り方

この記事は、ChatGPT APIを活用してSlack チャンネルの文章を要約する方法を紹介します。
Slackの要約機能は、執筆した3/14現在では、Salesforceのベータ版がありますが、申請が必要です。
プログラムを実行するまでの手順も解説してますので、最後までお読みいただけますとっ!

目次

今回作った要約Bot

チャンネル内の会話を要約するために、ChatGPT APIを活用したSlackアプリを作りました。
要約された様子は以下のようになります。

画像に alt 属性が指定されていません。ファイル名: 24e80e0918e96e8449368a1741933744.png

どんなものかと簡単に申し上げますと

  • (弊社環境では)times チャンネルに、対象チャンネルの要約がリストされる
  • 対象になるチャンネルは25時間以内に会話があったパブリック/プライベートチャンネル

です。

アプリ開発の際、参考にした記事はこちらです。
https://note.com/masuidrive/n/na0ebf8a4c4f0
パブリックチャンネルのみ対象のものでしたので、プライベートチャンネルも対象になるよう修正しております。

実行するプログラムを公開

プライベートチャンネルでも利用いただけるようにしたプログラムはこちらです。
実行するための設定は、このあと写真もりもりで解説します。

#!/usr/bin/env python3
# https://github.com/masuidrive/slack-summarizer
# by [masuidrive](https://twitter.com/masuidrive) @ [Bloom&Co., Inc.](https://www.bloom-and-co.com/) 2023- [APACHE LICENSE, 2.0](https://www.apache.org/licenses/LICENSE-2.0)
import os
import re
import time
import pytz
from slack_sdk.errors import SlackApiError
from slack_sdk import WebClient
from datetime import datetime, timedelta
import openai
from openai.error import InvalidRequestError

#以下の3つはご自身の環境で取得し書き換える必要があります。
openai.api_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
# APIトークンとチャンネルIDを設定する
TOKEN = "xoxb-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
CHANNEL_ID = "XXXXXXXXX"

# OpenAIのAPIを使って要約を行う
def summarize(text):
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        temperature=0.5,
        messages=[
            {"role": "system", "content": "チャットログのフォーマットは発言者: 本文\\nになっている。\\nは改行を表しています。これを踏まえて指示に従います"},
            {"role": "user", "content": f"下記のチャットログを箇条書きで要約してください。1行ずつの説明ではありません。全体として短く。\n\n{text}"}
        ]
    )
    return response["choices"][0]["message"]['content']

# 取得する期間を計算する
HOURS_BACK = 25
JST = pytz.timezone('Asia/Tokyo')
now = datetime.now(JST)
yesterday = now - timedelta(hours=HOURS_BACK)
start_time = datetime(yesterday.year, yesterday.month, yesterday.day,
                      yesterday.hour, yesterday.minute, yesterday.second)
end_time = datetime(now.year, now.month, now.day,
                    now.hour, now.minute, now.second)

# Slack APIクライアントを初期化する
client = WebClient(token=TOKEN)

# ユーザーIDからユーザー名に変換するために、ユーザー情報を取得する
try:
    users_info = client.users_list()
    users = users_info['members']
except SlackApiError as e:
    print("Error : {}".format(e))
    exit(1)

# チャンネルIDからチャンネル名に変換するために、チャンネル情報を取得する
try:
    channels_info = client.conversations_list(
        types="public_channel,private_channel",
        exclude_archived=False,
        limit=200
    )

    channels = [channel for channel in channels_info['channels']
                if not channel["is_archived"] and channel["is_channel"]]
    channels = sorted(channels, key=lambda x: int(re.findall(
        r'\d+', x["name"])[0]) if re.findall(r'\d+', x["name"]) else float('inf'))
except SlackApiError as e:
    print("Error : {}".format(e))
    exit(1)

# 指定したチャンネルの履歴を取得する

def load_messages(channel_id, ts):
    result = None
    result = client.conversations_replies(ts=ts,
                                          channel=channel_id,
                                          oldest=start_time.timestamp(),
                                          latest=end_time.timestamp()
                                          )

    # messages = result["messages"]
    messages = list(filter(lambda m: "subtype" not in m, result["messages"]))

    if len(messages) < 1:
        return None

    messages_text = []

    while result["has_more"]:
        result = client.conversations_replies(ts=ts,
                                              channel=channel_id,
                                              oldest=start_time.timestamp(),
                                              latest=end_time.timestamp(),
                                              cursor=result["response_metadata"]["next_cursor"]
                                              )
        messages.extend(result["messages"])
    for message in messages[::-1]:
        if "bot_id" in message:
            continue
        if message["text"].strip() == '':
            continue
        # ユーザーIDからユーザー名に変換する
        user_id = message['user']
        sender_name = None
        for user in users:
            if user['id'] == user_id:
                sender_name = user['name']
                break
        if sender_name is None:
            sender_name = user_id

        # テキスト取り出し
        text = message["text"].replace("\n", "\\n")

        # メッセージ中に含まれるユーザーIDやチャンネルIDを名前やチャンネル名に展開する
        matches = re.findall(r"<@[A-Z0-9]+>", text)
        for match in matches:
            user_id = match[2:-1]
            user_name = None
            for user in users:
                if user['id'] == user_id:
                    user_name = user['name']
                    break
            if user_name is None:
                user_name = user_id
            text = text.replace(match, f"@{user_name} ")

        matches = re.findall(r"<#[A-Z0-9]+>", text)
        for match in matches:
            channel_id = match[2:-1]
            channel_name = None
            for channel in channels:
                if channel['id'] == channel_id:
                    channel_name = channel['name']
                    break
            if channel_name is None:
                channel_name = channel_id
            text = text.replace(match, f"#{channel_name} ")
        messages_text.append(f"{sender_name}: {text}")
    if len(messages_text) == 0:
        return None
    else:
        return messages_text


result_text = []
for channel in channels:
    try:
        threads = client.conversations_history(channel=channel["id"],
                                               oldest=start_time.timestamp(),
                                               latest=end_time.timestamp())
    except SlackApiError as e:
        if e.response['error'] == 'not_in_channel':
            response = client.conversations_join(
                channel=channel["id"]
            )
            if not response["ok"]:
                raise SlackApiError("conversations_join() failed")
            time.sleep(5)  # チャンネルにjoinした後、少し待つ

            threads = client.conversations_history(
                channel=channel["id"],
                oldest=start_time.timestamp(),
                latest=end_time.timestamp()
            )
        else:
            print("Error : {}".format(e))
            continue

    messages_in_channel = []

    if not threads["messages"]:
        continue

    result_text.append(f"----\n<#{channel['id']}>\n")
    for thread in threads["messages"]:
        if thread:
            messages = load_messages(channel["id"], thread["ts"])
            if messages:
                messages_in_channel.extend(messages[::-1])
    try:
        result_text.append(summarize(messages_in_channel))

    except InvalidRequestError as e:
        result_text.append('4096tokenを超えたため生成に失敗しました')

title = (f"{yesterday.strftime('%Y-%m-%d')}の要約")

text = title+"\n\n"+"\n\n".join(result_text)

response = client.chat_postMessage(
    channel=CHANNEL_ID,
    text=text
)
print("Message posted: ", response["ts"])

プログラムを実行する手順

プログラムを実行するとき、定期実行と都度実行の2パターンがあります。
今回は、都度実行するための手順を紹介します。
実行する前準備として、以下3つの変数に格納している値を書き換えます。

  • openai.api_key
  • TOKEN
  • CHANNEL_ID

プログラムではここですね。

#以下の3つはご自身の環境で取得し書き換える必要があります。
openai.api_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
# APIトークンとチャンネルIDを設定する
TOKEN = "xoxb-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
CHANNEL_ID = "XXXXXXXXX"

こちらは、みなさんの環境独自のデータですので、それぞれ取得していきましょう。

OpenAI APIキーを取得する

openai.api_key は、OpenAI API キーを格納する変数です。
まずはOpenAI の Web サイト アクセスします。
適宜、Sign In して、自身のアカウントアイコン→View API Keysをクリック

「Create new secret key」をクリックする

赤枠内にあるシークレットキーをコピー

OpenAI APIキーの取得手順は以上です。

Slack APIキーを取得する


TOKENは、Slack APIキーを格納している変数です。

Slack API の Web サイトにアクセス。ログインし「Create an app」をクリック

「From an app manifest 」をクリック

アプリを追加したいワークスペースを選ぶ

以下のjsonを貼り付けて「Next」をクリック

{"display_information":{"name":"Summary","description":"Public channelのサマリーを作る","background_color":"#d45f00"},"features":{"bot_user":{"display_name":"Summary","always_online":false}},"oauth_config":{"scopes":{"bot":["channels:history","channels:join","channels:read","chat:write","users:read"]}},"settings":{"org_deploy_enabled":true,"socket_mode_enabled":false,"token_rotation_enabled":false}}

「Install to Workspace」をクリック

「許可する」をクリックし、画面遷移するまで待つ

左側の「OAuth & Permissions 」をクリックすると、「xoxb-」から始まるAPI キーが取得できる。

Slack APIキーの取得手順は以上です。

チャンネルIDを取得する

CHANNEL IDは、アプリを追加したいチャンネルIDを格納している変数です。
チャンネル名をクリックすると、下に表示されます

これで準備完了です。プログラムの該当部分を書き換えてください。

アプリを追加する

「アプリを追加する」 から「timesチャンネル」にSummaryアプリを召喚。

Pythonプログラムを実行すると……(添付の画像は、実行環境の一例です)

各チャンネルの内容が要約されます!

以上で、ChatGPT APIを使ったSlack要約アプリの手順は終了です。

ぜひご活用ください。

Recruit

現在、生成系AI事業急成長のため

積極的に人材採用を行なっています

ChatGPT講演会承っております!

  • URLをコピーしました!
目次