🦔

Difyを用いたSlack要約エージェントの構築

に公開

はじめに

近年、目覚ましい発展を遂げるAI技術、特に大規模言語モデル(LLM)を活用したAIエージェントが、私たちの働き方や情報収集のあり方を大きく変えようとしています。タスクの自動化、情報検索の効率化、そしてコミュニケーションの円滑化など、その可能性は多岐にわたり、様々な分野で実用化が進んでいます。

そんな中、ローコードで高機能なAIアプリケーション開発を可能にするプラットフォーム「Dify」が注目を集めています。Difyを利用することで、複雑なコーディングを伴うことなく、高度な自然言語処理機能を組み込んだAIエージェントを比較的容易に構築できるのです。

本ブログでは、このDifyを活用し、日々の業務で頻繁に利用されるコミュニケーションツール「Slack」のメッセージを自動で要約してくれる便利なエージェントの構築手順を、具体的なステップとコード例を交えながらご紹介します。

開発環境

OS : Windows11
Python : 3.12.7
Visual Studio Code : 1.99.3
ngrok : 3.22.1

requirements.txt

annotated-types==0.7.0
anyio==4.9.0
certifi==2025.4.26
charset-normalizer==3.4.2
click==8.1.8
colorama==0.4.6
fastapi==0.115.12
h11==0.16.0
httptools==0.6.4
idna==3.10
pydantic==2.11.4
pydantic_core==2.33.2
python-dotenv==1.1.0
PyYAML==6.0.2
requests==2.32.3
sniffio==1.3.1
starlette==0.46.2
typing-inspection==0.4.0
typing_extensions==4.13.2
urllib3==2.4.0
uvicorn==0.34.2
watchfiles==1.0.5
websockets==15.0.1

ディレクトリ構造

Slack_Sum_App/
├── __pycache__/
├── venv/
├── .env
├── .gitignore
├── main.py
├── requirements.txt

Slack Bot作成

まず、SlackのAPIサイト にアクセスし、新しいSlack Appを作成します。

  1. サイトにアクセス後、「Create an App」ボタンをクリックします。
  2. アプリの作成方法を選択する画面が表示されるので、「From scratch」を選択します。
  3. アプリ名とワークスペースを選択するダイアログが表示されます。任意のアプリ名を入力し、Botを追加したいワークスペースを選択して「Create App」をクリックします。アプリが正確に作成されると、アプリの管理画面が表示されます。ここでは、アプリの基本的な情報や設定を確認できます。
  4. Botを実際にチャンネル内で利用するためには、作成したアプリを目的のチャンネルに招待する必要があります。Slackのチャンネル内で、以下のコマンドを入力してBotを追加してください。このコマンドを実行することで、Botがチャンネルに参加し、メッセージの受信や送信などの操作が可能になります。
    /invite @アプリ名
    

補足
本手順は、以下のZennの記事を参考にさせていただきました。より詳細な情報やトラブルシューティングについては、こちらの記事も合わせてご確認ください。
https://zenn.dev/nyancat/articles/20211219-create-slack-app

バックエンド環境構築

このセクションでは、Slack要約エージェントのバックエンドとなるFastAPIアプリケーションの開発環境を構築します。

  1. 仮想環境の構築と有効化
    まず、プロジェクトフォルダの直下で以下のコマンドを実行し、Pythonの仮想環境を構築します
    python -m venv venv
    
    仮想環境が作成されたら、以下のコマンドで有効化します。
    Windows :
    cd venv
    Scripts\activate
    
    仮想環境を無効化したい場合は、ターミナルで deactivate コマンドを実行します。
  2. 必要なライブラリのインストール
    仮想環境が有効になっている状態で、以下のコマンドを実行し、FastAPIアプリケーションに必要なライブラリをインストールします。
    pip install fastapi uvicorn[standard] requests python-dotenv
    
    インストールが完了したら、以下のコマンドを実行して、インストールされたライブラリの一覧を requirements.txt ファイルに出力しておきます。
    pip freeze > requirements.txt
    
  3. .gitignoreの設定
    Gitリポジトリを使用する場合、以下の内容を .gitignore ファイルに記述し、仮想環境や環境変数ファイル、Pythonのキャッシュファイルなどがリポジトリにコミットされないように設定します。
    venv/
    .env
    __pycache__/
    
  4. GitHubへのプッシュ(補足)
    今回はローカルでのテストにngrokを使用するため、GitHubリポジトリは必須ではありません。 しかし、将来的にアプリケーションをデプロイする可能性も考慮し、GitHubへの初期プッシュ手順も紹介しておきます。
    git init
    git branch -m main
    git remote add origin https://github.com/ユーザー名/app_name.git
    git add .
    git commit -m "Initial commit for FastAPI Slack app"
    git push -u origin main
    
    GitHubを使用しない方は、上記の手順はスキップしていただいて構いません。

FastAPIによるSlack連携の基礎

ローカルサーバからSlackへメッセージ送信

  1. 環境変数の設定(.env ファイル)
    Slack Botのアクセストークンと、メッセージを送信したいSlackチャンネルのIDを .env ファイルに記述します。
    SLACK_BOT_TOKEN = xoxb-xxx
    SLACK_CHANNEL_ID = C0xxxxxxx
    
  2. python-dotenv のインストールと環境変数の読み込み
    pip install python-dotenv
    
  3. main.pyを以下のように記述
     from fastapi import FastAPI
     import requests
     import os
     from dotenv import load_dotenv
     
     load_dotenv()
     
     
     SLACK_BOT_TOKEN = os.getenv("SLACK_BOT_TOKEN")
     SLACK_CHANNEL_ID = os.getenv("SLACK_CHANNEL_ID")
     
     app = FastAPI()
     
     @app.get("/")
     def root():
         return {"message": "Slack Summary API is running"}
     
     @app.get("/post_message")
     def post_message():
         message = {
             "channel": SLACK_CHANNEL_ID,
             "text": "✅ Slackからのテストメッセージ(FastAPIより送信)"
         }
         headers = {
             "Authorization": f"Bearer {SLACK_BOT_TOKEN}",
             "Content-Type": "application/json"
         }
     
         response = requests.post("https://slack.com/api/chat.postMessage", json=message, headers=headers)
         result = response.json()
         return result
    
  4. 以下のコマンドでアプリを起動
    uvicorn main:app --reload
    
    Webブラウザでターミナル上に表示されたURLに”/post_message”を付けた以下のURLにアクセスします。
    http://localhost:8000/post_message
    
  5. 成功すると、.env ファイルで指定したSlackチャンネルに、以下のメッセージが投稿されます。

チャンネル内の当日の投稿一覧を取得

  1. main.py ファイルに以下のコードを追加します。
     from fastapi import FastAPI, Query
     import requests
     import os
     from dotenv import load_dotenv
     from datetime import datetime, timedelta
     
     
     @app.get("/slack/today_summary")
     def fetch_today_messages(channel_id: str):
         now = datetime.now()
         today_start = datetime(now.year, now.month, now.day)
         oldest = today_start.timestamp()
         latest = (today_start + timedelta(days=1)).timestamp()
     
         headers = {
             "Authorization": f"Bearer {SLACK_BOT_TOKEN}"
         }
         params = {
             "channel": channel_id,
             "oldest": oldest,
             "latest": latest,
             "limit": 1000
         }
     
         response = requests.get("https://slack.com/api/conversations.history", headers=headers, params=params)
         data = response.json()
     
         if not data.get("ok"):
             return {"error": data.get("error", "Failed to fetch messages.")}
     
         messages = data.get("messages", [])
         result = [{"user": m.get("user", ""), "text": m.get("text", ""), "ts": m.get("ts", "")} for m in messages]
         return {"count": len(result), "messages": result}
    
  2. 動作確認
    FastAPIアプリケーションが起動している状態で、Webブラウザで以下のURLにアクセスします。<C0xxxxxxx> の部分は、メッセージを取得したいSlackチャンネルのIDに置き換えてください。
    host:8000/slack/today_summary?channel_id=C0xxxxxxx
    
    成功すると、指定したSlackチャンネルの当日のメッセージ一覧がJSON形式でブラウザに表示されます。各メッセージには、送信ユーザーID、メッセージテキスト、タイムスタンプが含まれています。

このステップでは、FastAPIアプリケーションを通じて、Slack APIから特定のチャンネルの過去のメッセージ(今回は当日分)を取得できることを確認しました。次のステップでは、Difyとの連携について解説します。

Dify連携

ここでは作成したFastAPIアプリケーションからDifyのAPIを呼び出し、Slackのメッセージを要約する機能を追加します。

Difyでの要約ボットの作成

  1. Difyのプラットフォームにアクセスし、「チャットボット」を選択します。
  2. 任意のアプリ名を入力し、「作成する」ボタンをクリックします。
  3. 作成したボットのプロンプトを設定します。今回は、Slackの投稿を要約するために、以下のようなプロンプトを設定します。
     あなたはSlackの投稿を要約するアシスタントです。
    
     渡されたSlack投稿のリストを読み、重要な発言・進捗・質問・決定事項を自然な日本語で簡潔にまとめてください。
     
     できるだけ箇条書きにし、読みやすく伝えてください。
    
  4. DifyのAPIエンドポイントとAPIキーを取得します。アイコン右側をクリックして表示される画面で「サービスAPIエンドポイント」をコピーしておきます。また、左下の「APIキー」ボタンをクリックし、「新しいシークレットキーを作成」を選択してAPIキーを作成し、メモしておきます。
  5. 取得したAPIキーと、ブラウザ上のURLから確認できるAPP_IDを.env ファイルに保存します。
     DIFY_API_KEY=your_dify_api_key
     DIFY_APP_ID=your_dify_app_id
    
  6. main.py ファイルに、Dify APIを呼び出して要約を行うための関数を追加します。
     # Dify要約
     def summarize_with_dify(dify_api_key, app_id, text):
         import requests, json
     
         headers = {
             "Authorization": f"Bearer {dify_api_key}",
             "Content-Type": "application/json"
         }
     
         res = requests.post(
             f"https://api.dify.ai/v1/chat-messages",
             headers=headers,
             json={
                 "inputs": {},
                 "query": text,
                 "response_mode": "blocking",
                 "conversation_id": None,
                 "app_id": app_id,
                 "user": "k-kanke"
             }
         )
     
         try:
             return res.json()["answer"]
         except Exception as e:
             print("Dify Error: ", res.text)
             return f"要約失敗: {res.text}"
    

実装:Slackメッセージの取得、要約、投稿

これまでのステップで構築してきた各機能を統合し、Slackチャンネルの今日のメッセージをDifyで要約して、その結果をSlackに投稿する一連の処理を実装します。

  1. main.py ファイルを以下のように修正します。
     import os
     from fastapi import FastAPI
     from dotenv import load_dotenv
     
     load_dotenv()
     app = FastAPI()
     
     @app.get("/slack/summary")
     def generate_summary():
         slack_token = os.environ["SLACK_BOT_TOKEN"]
         channel_id = os.environ["SLACK_CHANNEL_ID"]
         dify_key = os.environ["DIFY_API_KEY"]
         dify_app_id = os.environ["DIFY_APP_ID"]
     
         messages = get_today_messages(slack_token, channel_id)
         summary = summarize_with_dify(dify_key, dify_app_id, messages)
         post_to_slack(slack_token, channel_id, summary)
     
         return {"message": "要約完了", "summary": summary}
     
     
     # Slack投稿取得
     def get_today_messages(slack_token, channel_id):
         import requests, datetime, time
     
         headers = {"Authorization": f"Bearer {slack_token}"}
         today = datetime.datetime.now()
         start_of_day = datetime.datetime(today.year, today.month, today.day)
         start_ts = time.mktime(start_of_day.timetuple())
     
         res = requests.get("https://slack.com/api/conversations.history", params={
             "channel": channel_id,
             "oldest": start_ts,
             "limit": 1000
         }, headers=headers)
     
         messages = res.json().get("messages", [])
         texts = [msg["text"] for msg in messages if "subtype" not in msg]
         return "\n".join(reversed(texts))
     
     
     # Dify要約
     def summarize_with_dify(dify_api_key, app_id, text):
         import requests, json
     
         headers = {
             "Authorization": f"Bearer {dify_api_key}",
             "Content-Type": "application/json"
         }
     
         res = requests.post(
             f"https://api.dify.ai/v1/chat-messages",
             headers=headers,
             json={
                 "inputs": {},
                 "query": text,
                 "response_mode": "blocking",
                 "conversation_id": None,
                 "app_id": app_id,
                 "user": "k-kanke"
             }
         )
     
         try:
             return res.json()["answer"]
         except Exception as e:
             print("Dify Error: ", res.text)
             return f"要約失敗: {res.text}"
     
     # Slackに送信
     def post_to_slack(slack_token, channel_id, summary_text):
         import requests
         headers = {"Authorization": f"Bearer {slack_token}"}
         res = requests.post("https://slack.com/api/chat.postMessage", headers=headers, json={
             "channel": channel_id,
             "text": f"📌 *本日の要約*\n{summary_text}"
         })
         return res.json()
    
  2. 動作確認
    FastAPIアプリケーションが起動している状態で、Webブラウザで以下のURLにアクセスします。
    http://localhost:8000/slack/summary
    
    成功すると、Slackの指定したチャンネルに、Difyによって要約されたメッセージが投稿されます。また、ブラウザ上には「要約完了」のメッセージと要約結果がJSON形式で表示されます。

Slackからメンションで実行できるように

ここでは、SlackでBotをメンションした際に、その日のメッセージを要約して返信する機能を実現します。

  1. FastAPIでイベント受信エンドポイントを作成
    FastAPI側に、Slackからのイベントを受信する /slack/events というPOSTエンドポイントを作成します。
     from fastapi import FastAPI, Request, Header
     import os, hmac, hashlib, asyncio
     from dotenv import load_dotenv
     
     load_dotenv()
     app = FastAPI()
     
     # 処理済みイベントID記録用セット
     PROCESSED_EVENTS = set()
     
     # エンドポイント作成
     @app.post("/slack/events")
     async def slack_events(
         request: Request, 
         x_slack_signature: str = Header(...), 
         x_slack_request_timestamp: str = Header(...),
         x_slack_retry_num: str = Header(None)
     ):
         raw_body = await request.body()
         body_str = raw_body.decode("utf-8")
     
         # Slack署名検証
         slack_signing_secret = os.environ["SLACK_SIGNING_SECRET"]
         basestring = f"v0:{x_slack_request_timestamp}:{body_str}"
         my_signature = "v0=" + hmac.new(slack_signing_secret.encode(), basestring.encode(), hashlib.sha256).hexdigest()
         if not hmac.compare_digest(my_signature, x_slack_signature):
             return {"error": "invalid signature"}
         
         # リトライチェック
         if x_slack_retry_num is not None:
             return {"status": "ok (retry ignored)"}
         
         # JSONとして解析
         payload = await request.json()
     
         # Slackの初回URL検証
         if payload.get("type") == "url_verification":
             return {"challenge": payload.get("challenge")}
         
         # 重複イベントチェック
         event_id = payload.get("event_id")
         if event_id in PROCESSED_EVENTS:
             return {"status": "ok (already processed)"}
         PROCESSED_EVENTS.add(event_id)
     
         # メンションイベント検知時の非同期処理
         if payload.get("event", {}).get("type") == "app_mention":
             asyncio.create_task(mention_event(payload))
     
         return {"status": "ok"}
     
     # 非同期で実行する処理本体
     async def mention_event(payload):
         user = payload["event"].get("user")
         channel = payload["event"].get("channel")
     
         # メッセージ取得・要約・投稿
         slack_token = os.environ["SLACK_BOT_TOKEN"]
         dify_key = os.environ["DIFY_API_KEY"]
         dify_app_id = os.environ["DIFY_APP_ID"]
     
         text = get_today_messages(slack_token, channel)
         summary = summarize_with_dify(dify_key, dify_app_id, text, user)
         post_to_slack(slack_token, channel, f"<@{user}> 要約です!\n{summary}")
    
  2. リトライ対策
    Slackは、イベント送信に失敗した場合、数回リトライを行います。上記のコードでは、以下の対策を行うことで重複処理を防いでいます。
    • X-Slack-Retry-Num ヘッダーの確認: リトライリクエストである場合、即座に成功応答を返します。
    • event_id の重複チェック: 受信したイベントの event_id を PROCESSED_EVENTS セットに記録し、同じ event_id のイベントを再度受信した場合は処理をスキップします。
    • 非同期処理: メンションイベントの処理を asyncio.create_task で非同期に実行することで、Slackへの即時応答を可能にし、リトライを抑制します。
  3. ngrokによる外部公開
    ローカルで起動したFastAPIアプリケーションをSlackがアクセスできるように、ngrokを使用して外部に公開します。別のターミナルで以下のコマンドを実行し、発行されたURLをメモしておきます。
    ngrok http 8000
    
  4. Slack Appの管理画面 で、以下の設定を行います。
    a. 左側のメニューから「Event Subscriptions」を選択し、「Enable Events」をオンにします。
    b. 「Request URL」に、先ほどngrokで発行されたURLに /slack/events を付与したURLを入力します。例:https://your-ngrok-url.app/slack/events
    c. 「Subscribe to bot events」セクションで、「Add bot events」をクリックし、「app_mention」を追加します。
    d. 設定を保存すると、ワークスペースへの再インストールが促される場合がありますので、その際は再度インストールを実行してください。
  5. Slackからのイベントリクエストの信頼性を検証するために、Slack署名を使用します
    a. Slack Appの管理画面の左側メニューから「Basic Information」を選択し、「Signing Secret」の値をコピーして、.env ファイルに SLACK_SIGNING_SECRET として保存します。
    b. 上記のFastAPIのコードでは、リクエストヘッダーに含まれる X-Slack-Signature と X-Slack-Request-Timestamp を使用して署名を再生成し、Slackからの署名と比較することでリクエストの正当性を検証しています。
  6. 動作確認
    Slackの該当チャンネルで、Botに対してメンション付きで「要約してください」と送信します。実行結果は以下の通りで内容的にも問題なさそうです。

今後の課題

今回のブログでは、Difyという強力なツールを活用することで、比較的容易にSlackの要約エージェントを構築できることをご紹介しました。しかし、このエージェントをより実用的なものにするためには、いくつかの課題が残されています。

  • デプロイ: 今回、FastAPIアプリケーションの公開にはngrokを使用しましたが、これは開発・テスト用途の一時的な手段です。本番環境で永続的に利用するためには、AWS、Google Cloud Platformなどのクラウドプラットフォームを利用するなどして、デプロイする必要があります。
  • 要約品質の向上: Difyによる要約の品質は、プロンプトの設計に大きく左右されます。より自然で、かつ重要な情報を的確に抽出できるようなプロンプトの調整や、Difyの高度な機能(Knowledge Base連携、RAGなど)の活用を検討することで、要約の精度を高めることができます。
  • エラーハンドリングの強化: 今回の実装では基本的なエラーハンドリングのみを行っています。Slack APIやDify APIとの連携におけるエラーをより詳細に検知し、適切なログ出力やユーザーへの通知を行うことで、システムの安定性を向上させる必要があります。

これらの課題に取り組むことで、より堅牢で使いやすいSlack要約エージェントへと進化させることができるはずです。

参考文献

https://zenn.dev/nyancat/articles/20211219-create-slack-app

UPGRADE tech blog

Discussion