Skip to content

필사 모드: Slack Bot開発完全ガイド — Bolt SDKで作るAI業務自動化ボット

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

はじめに

Slackは開発チームの中核コミュニケーションプラットフォームです。よくできたSlack Botは、障害アラート、デプロイ管理、コードレビューリマインダー、AI問い合わせなど、さまざまな業務を自動化します。

**Bolt for Python**はSlack公式フレームワークで、簡潔なAPIでさまざまなSlack機能を活用できます。

Slackアプリの設定

1. アプリの作成

1. https://api.slack.com/apps にアクセス

2. "Create New App" → "From scratch"

3. App Name: "DevOps Bot"

4. Workspaceを選択

2. Bot Token Scopesの設定

OAuth & Permissions → Bot Token Scopes:

- chat:write (メッセージ送信)

- commands (スラッシュコマンド)

- im:history (DM履歴読み取り)

- im:read (DMチャンネル情報)

- channels:history (チャンネルメッセージ読み取り)

- channels:read (チャンネル情報)

- users:read (ユーザー情報)

- reactions:write (リアクション追加)

- files:write (ファイルアップロード)

3. Event Subscriptions

Event Subscriptions → Enable Events

Subscribe to bot events:

- message.im (DM受信)

- message.channels (チャンネルメッセージ)

- app_mention (メンション)

- app_home_opened (アプリホームタブを開く)

プロジェクト設定

依存関係のインストール

pip install slack-bolt slack-sdk python-dotenv openai

プロジェクト構造

slack-bot/

├── app.py # メインアプリ

├── handlers/

│ ├── __init__.py

│ ├── commands.py # スラッシュコマンド

│ ├── events.py # イベントハンドラー

│ ├── actions.py # ボタン/アクションハンドラー

│ └── modals.py # モーダルビュー

├── services/

│ ├── __init__.py

│ ├── ai.py # LLM統合

│ ├── deploy.py # デプロイサービス

│ └── github.py # GitHub連携

├── .env

└── requirements.txt

.env

SLACK_BOT_TOKEN=xoxb-your-bot-token

SLACK_APP_TOKEN=xapp-your-app-token

SLACK_SIGNING_SECRET=your-signing-secret

OPENAI_API_KEY=sk-your-key

基本アプリ構成

app.py

from dotenv import load_dotenv

from slack_bolt import App

from slack_bolt.adapter.socket_mode import SocketModeHandler

load_dotenv()

Socket Modeでアプリ作成(Webhookサーバー不要)

app = App(token=os.environ["SLACK_BOT_TOKEN"])

===== イベントハンドラー =====

メンションに応答

@app.event("app_mention")

def handle_mention(event, say, client):

user = event["user"]

text = event["text"]

ユーザー情報を取得

user_info = client.users_info(user=user)

name = user_info["user"]["real_name"]

say(f"こんにちは{name}さん!何かお手伝いしましょうか? 🤖")

DMメッセージの処理

@app.event("message")

def handle_dm(event, say, client, logger):

ボット自身のメッセージは無視

if event.get("bot_id"):

return

channel_type = event.get("channel_type")

if channel_type == "im":

text = event.get("text", "")

say(f"お伝えいただいた内容を処理いたします: '{text}'")

===== スラッシュコマンド =====

@app.command("/deploy")

def handle_deploy_command(ack, body, client):

ack() # 3秒以内の応答が必須!

モーダルを開く

client.views_open(

trigger_id=body["trigger_id"],

view={

"type": "modal",

"callback_id": "deploy_modal",

"title": {"type": "plain_text", "text": "デプロイ管理"},

"submit": {"type": "plain_text", "text": "デプロイ開始"},

"blocks": [

{

"type": "input",

"block_id": "service_block",

"element": {

"type": "static_select",

"placeholder": {"type": "plain_text", "text": "サービスを選択"},

"options": [

{"text": {"type": "plain_text", "text": "API Server"}, "value": "api"},

{"text": {"type": "plain_text", "text": "Web Frontend"}, "value": "web"},

{"text": {"type": "plain_text", "text": "Worker"}, "value": "worker"},

],

"action_id": "service_select",

},

"label": {"type": "plain_text", "text": "サービス"},

},

{

"type": "input",

"block_id": "env_block",

"element": {

"type": "static_select",

"options": [

{"text": {"type": "plain_text", "text": "Staging"}, "value": "staging"},

{"text": {"type": "plain_text", "text": "Production"}, "value": "production"},

],

"action_id": "env_select",

},

"label": {"type": "plain_text", "text": "環境"},

},

{

"type": "input",

"block_id": "version_block",

"element": {

"type": "plain_text_input",

"placeholder": {"type": "plain_text", "text": "v1.2.3 または latest"},

"action_id": "version_input",

},

"label": {"type": "plain_text", "text": "バージョン"},

},

],

},

)

モーダル送信の処理

@app.view("deploy_modal")

def handle_deploy_submission(ack, body, client, view):

ack()

values = view["state"]["values"]

service = values["service_block"]["service_select"]["selected_option"]["value"]

env = values["env_block"]["env_select"]["selected_option"]["value"]

version = values["version_block"]["version_input"]["value"]

user_id = body["user"]["id"]

デプロイ通知を送信

client.chat_postMessage(

channel="#deployments",

blocks=[

{

"type": "section",

"text": {

"type": "mrkdwn",

"text": f"🚀 *デプロイ開始*\n"

f"• サービス: `{service}`\n"

f"• 環境: `{env}`\n"

f"• バージョン: `{version}`\n"

f"• 依頼者: <@{user_id}>"

}

},

{

"type": "actions",

"elements": [

{

"type": "button",

"text": {"type": "plain_text", "text": "✅ 承認"},

"style": "primary",

"value": f"{service}|{env}|{version}",

"action_id": "deploy_approve",

},

{

"type": "button",

"text": {"type": "plain_text", "text": "❌ 拒否"},

"style": "danger",

"value": f"{service}|{env}|{version}",

"action_id": "deploy_reject",

},

],

},

],

)

ボタンアクションの処理

@app.action("deploy_approve")

def handle_approve(ack, body, client, action):

ack()

service, env, version = action["value"].split("|")

user_id = body["user"]["id"]

元のメッセージを更新

client.chat_update(

channel=body["channel"]["id"],

ts=body["message"]["ts"],

blocks=[

{

"type": "section",

"text": {

"type": "mrkdwn",

"text": f"✅ *デプロイ承認済み*\n"

f"• サービス: `{service}` → `{env}` ({version})\n"

f"• 承認者: <@{user_id}>\n"

f"• ステータス: デプロイ進行中... ⏳"

}

}

],

)

実際のデプロイロジックを実行

deploy_service(service, env, version)

===== ステータス照会コマンド =====

@app.command("/status")

def handle_status(ack, say, command):

ack()

say(

blocks=[

{

"type": "header",

"text": {"type": "plain_text", "text": "📊 サービスステータス"}

},

{

"type": "section",

"fields": [

{"type": "mrkdwn", "text": "*API Server*\n🟢 Healthy (v1.2.3)"},

{"type": "mrkdwn", "text": "*Web Frontend*\n🟢 Healthy (v2.0.1)"},

{"type": "mrkdwn", "text": "*Worker*\n🟡 Degraded (v1.1.0)"},

{"type": "mrkdwn", "text": "*Database*\n🟢 Healthy (CPU: 45%)"},

]

},

{"type": "divider"},

{

"type": "context",

"elements": [

{"type": "mrkdwn", "text": f"最終更新: <!date^{int(__import__('time').time())}^{{date_short_pretty}} {{time}}|just now>"}

]

}

]

)

===== AIチャットボット機能 =====

@app.command("/ask")

def handle_ask(ack, say, command):

ack()

question = command["text"]

if not question:

say("質問を入力してください。例: `/ask Kubernetes PodがCrashLoopBackOffの時の対処法`")

return

ローディング表示

say(f"🤔 '{question}'について考えています...")

LLM呼び出し

from openai import OpenAI

client_ai = OpenAI()

response = client_ai.chat.completions.create(

model="gpt-4o",

messages=[

{"role": "system", "content": "You are a helpful DevOps assistant. Answer in Korean. Be concise."},

{"role": "user", "content": question}

],

max_tokens=1000,

)

answer = response.choices[0].message.content

say(

blocks=[

{

"type": "section",

"text": {"type": "mrkdwn", "text": f"*Q: {question}*"}

},

{"type": "divider"},

{

"type": "section",

"text": {"type": "mrkdwn", "text": answer}

},

{

"type": "context",

"elements": [

{"type": "mrkdwn", "text": "💡 AI生成回答 — 正確性をご確認ください"}

]

}

]

)

===== アプリホームタブ =====

@app.event("app_home_opened")

def update_home_tab(client, event):

client.views_publish(

user_id=event["user"],

view={

"type": "home",

"blocks": [

{

"type": "header",

"text": {"type": "plain_text", "text": "🤖 DevOps Bot Dashboard"}

},

{

"type": "section",

"text": {

"type": "mrkdwn",

"text": "*利用可能なコマンド:*\n"

"• `/deploy` — サービスデプロイ\n"

"• `/status` — サービスステータス確認\n"

"• `/ask [質問]` — AIに質問\n"

"• `/incident` — 障害報告"

}

},

{"type": "divider"},

{

"type": "section",

"text": {"type": "mrkdwn", "text": "*最近のデプロイ:*\n✅ API Server v1.2.3 → Production(2時間前)\n✅ Web v2.0.1 → Production(昨日)"}

},

],

},

)

===== スケジュールメッセージ =====

def send_daily_standup_reminder():

"""毎朝のスタンドアップリマインダー"""

app.client.chat_postMessage(

channel="#engineering",

text="🌅 *スタンドアップリマインダー*\n今日の計画をスレッドで共有してください!\n1. 昨日やったこと\n2. 今日やること\n3. ブロッカー",

)

===== エラーハンドリング =====

@app.error

def handle_errors(error, body, logger):

logger.exception(f"Error: {error}")

logger.info(f"Request body: {body}")

===== 実行 =====

if __name__ == "__main__":

Socket Mode(Webhookサーバー不要、ファイアウォール内でも動作)

handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])

handler.start()

障害管理ボット

@app.command("/incident")

def handle_incident(ack, body, client):

ack()

client.views_open(

trigger_id=body["trigger_id"],

view={

"type": "modal",

"callback_id": "incident_modal",

"title": {"type": "plain_text", "text": "🚨 障害報告"},

"submit": {"type": "plain_text", "text": "報告"},

"blocks": [

{

"type": "input",

"block_id": "severity",

"element": {

"type": "static_select",

"options": [

{"text": {"type": "plain_text", "text": "🔴 P1 - Critical"}, "value": "P1"},

{"text": {"type": "plain_text", "text": "🟠 P2 - Major"}, "value": "P2"},

{"text": {"type": "plain_text", "text": "🟡 P3 - Minor"}, "value": "P3"},

],

"action_id": "severity_select",

},

"label": {"type": "plain_text", "text": "深刻度"},

},

{

"type": "input",

"block_id": "description",

"element": {

"type": "plain_text_input",

"multiline": True,

"placeholder": {"type": "plain_text", "text": "障害状況を説明してください"},

"action_id": "desc_input",

},

"label": {"type": "plain_text", "text": "説明"},

},

],

},

)

@app.view("incident_modal")

def handle_incident_submit(ack, body, client, view):

ack()

values = view["state"]["values"]

severity = values["severity"]["severity_select"]["selected_option"]["value"]

description = values["description"]["desc_input"]["value"]

reporter = body["user"]["id"]

障害チャンネルを作成

channel_name = f"incident-{int(time.time())}"

channel = client.conversations_create(

name=channel_name,

is_private=False,

)

channel_id = channel["channel"]["id"]

障害の初期メッセージ

client.chat_postMessage(

channel=channel_id,

blocks=[

{

"type": "header",

"text": {"type": "plain_text", "text": f"🚨 障害報告 ({severity})"}

},

{

"type": "section",

"text": {

"type": "mrkdwn",

"text": f"*報告者:* <@{reporter}>\n"

f"*深刻度:* {severity}\n"

f"*説明:*\n{description}"

}

},

{"type": "divider"},

{

"type": "section",

"text": {

"type": "mrkdwn",

"text": "⏱ *タイムラインをこのスレッドに記録してください*"

}

},

],

)

通知チャンネルにアナウンス

client.chat_postMessage(

channel="#incidents",

text=f"🚨 新規障害発生: {severity}\n📝 {description[:100]}...\n🔗 <#{channel_id}>",

)

Dockerデプロイ

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "app.py"]

docker-compose.yml

version: '3.8'

services:

slack-bot:

build: .

env_file: .env

restart: unless-stopped

healthcheck:

test: ['CMD', 'python', '-c', "import requests; requests.get('http://localhost:3000/health')"]

interval: 30s

timeout: 10s

retries: 3

クイズ

**ack()**の呼び出しです。Slackは3秒以内に応答(acknowledge)を受け取れないとタイムアウトエラーを表示します。

WebSocket接続を使用するため、**公開URLやWebhookサーバーが不要**です。ファイアウォール内でも、ローカル開発でもすぐに動作します。

**section**(テキスト/フィールド)、**actions**(ボタン/セレクトなどのインタラクティブ要素)、**input**(モーダルでのユーザー入力)です。

`@app.action`はボタンクリックなどのインタラクションを、`@app.view`はモーダルの送信(submit)を処理します。それぞれaction_idとcallback_idでマッチングされます。

Bot Tokenはチャンネルメッセージ送信などのSlack API呼び出しに使用し、App TokenはSocket ModeのWebSocket接続に使用します。

障害関連のコミュニケーションを一か所に集中させ、タイムラインを記録し、事後分析(ポストモーテム)のための履歴を残すためです。

LLMの応答時間が3秒を超える可能性があるため、まずack()で応答した後、非同期でLLMを呼び出し、結果を別メッセージで送信する必要があります。

まとめ

Slack Botはチームの生産性を大きく向上させるツールです。Bolt SDKを使えば、スラッシュコマンド、モーダル、ボタンインタラクションなどを簡潔なPythonコードで実装でき、Socket Modeで別途サーバーなしですぐにデプロイできます。

参考資料

- [Bolt for Python公式ガイド](https://docs.slack.dev/tools/bolt-python/getting-started/)

- [Slack Block Kit Builder](https://app.slack.com/block-kit-builder)

- [Slack APIドキュメント](https://api.slack.com/docs)

현재 단락 (1/396)

Slackは開発チームの中核コミュニケーションプラットフォームです。よくできたSlack Botは、障害アラート、デプロイ管理、コードレビューリマインダー、AI問い合わせなど、さまざまな業務を自動化し...

작성 글자: 0원문 글자: 9,965작성 단락: 0/396