Skip to content

필사 모드: Complete Guide to Slack Bot Development — Building an AI Task Automation Bot with Bolt SDK

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

Introduction

Slack is a core communication platform for development teams. A well-built Slack Bot can automate various tasks such as incident alerts, deployment management, code review reminders, and AI queries.

**Bolt for Python** is Slack's official framework that provides a concise API for leveraging various Slack features.

Slack App Setup

1. Create an App

1. Go to https://api.slack.com/apps

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

3. App Name: "DevOps Bot"

4. Select Workspace

2. Bot Token Scopes Configuration

OAuth & Permissions → Bot Token Scopes:

- chat:write (Send messages)

- commands (Slash commands)

- im:history (Read DM history)

- im:read (DM channel info)

- channels:history (Read channel messages)

- channels:read (Channel info)

- users:read (User info)

- reactions:write (Add reactions)

- files:write (Upload files)

3. Event Subscriptions

Event Subscriptions → Enable Events

Subscribe to bot events:

- message.im (Receive DMs)

- message.channels (Channel messages)

- app_mention (Mentions)

- app_home_opened (App Home tab opened)

Project Setup

Install dependencies

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

Project structure

slack-bot/

├── app.py # Main app

├── handlers/

│ ├── __init__.py

│ ├── commands.py # Slash commands

│ ├── events.py # Event handlers

│ ├── actions.py # Button/action handlers

│ └── modals.py # Modal views

├── services/

│ ├── __init__.py

│ ├── ai.py # LLM integration

│ ├── deploy.py # Deployment service

│ └── github.py # GitHub integration

├── .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

Basic App Configuration

app.py

from dotenv import load_dotenv

from slack_bolt import App

from slack_bolt.adapter.socket_mode import SocketModeHandler

load_dotenv()

Create app with Socket Mode (no webhook server required)

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

===== Event Handlers =====

Respond to mentions

@app.event("app_mention")

def handle_mention(event, say, client):

user = event["user"]

text = event["text"]

Look up user info

user_info = client.users_info(user=user)

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

say(f"Hello {name}! How can I help you? 🤖")

Handle DM messages

@app.event("message")

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

Ignore bot's own messages

if event.get("bot_id"):

return

channel_type = event.get("channel_type")

if channel_type == "im":

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

say(f"I'll process your request: '{text}'")

===== Slash Commands =====

@app.command("/deploy")

def handle_deploy_command(ack, body, client):

ack() # Must respond within 3 seconds!

Open modal

client.views_open(

trigger_id=body["trigger_id"],

view={

"type": "modal",

"callback_id": "deploy_modal",

"title": {"type": "plain_text", "text": "Deployment Manager"},

"submit": {"type": "plain_text", "text": "Start Deploy"},

"blocks": [

{

"type": "input",

"block_id": "service_block",

"element": {

"type": "static_select",

"placeholder": {"type": "plain_text", "text": "Select service"},

"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": "Service"},

},

{

"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": "Environment"},

},

{

"type": "input",

"block_id": "version_block",

"element": {

"type": "plain_text_input",

"placeholder": {"type": "plain_text", "text": "v1.2.3 or latest"},

"action_id": "version_input",

},

"label": {"type": "plain_text", "text": "Version"},

},

],

},

)

Handle modal submission

@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"]

Send deployment notification

client.chat_postMessage(

channel="#deployments",

blocks=[

{

"type": "section",

"text": {

"type": "mrkdwn",

"text": f"🚀 *Deployment Started*\n"

f"• Service: `{service}`\n"

f"• Environment: `{env}`\n"

f"• Version: `{version}`\n"

f"• Requester: <@{user_id}>"

}

},

{

"type": "actions",

"elements": [

{

"type": "button",

"text": {"type": "plain_text", "text": "✅ Approve"},

"style": "primary",

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

"action_id": "deploy_approve",

},

{

"type": "button",

"text": {"type": "plain_text", "text": "❌ Reject"},

"style": "danger",

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

"action_id": "deploy_reject",

},

],

},

],

)

Handle button actions

@app.action("deploy_approve")

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

ack()

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

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

Update original message

client.chat_update(

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

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

blocks=[

{

"type": "section",

"text": {

"type": "mrkdwn",

"text": f"✅ *Deployment Approved*\n"

f"• Service: `{service}` → `{env}` ({version})\n"

f"• Approver: <@{user_id}>\n"

f"• Status: Deploying... ⏳"

}

}

],

)

Execute actual deployment logic

deploy_service(service, env, version)

===== Status Query Command =====

@app.command("/status")

def handle_status(ack, say, command):

ack()

say(

blocks=[

{

"type": "header",

"text": {"type": "plain_text", "text": "📊 Service Status"}

},

{

"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"Last updated: <!date^{int(__import__('time').time())}^{{date_short_pretty}} {{time}}|just now>"}

]

}

]

)

===== AI Chatbot Feature =====

@app.command("/ask")

def handle_ask(ack, say, command):

ack()

question = command["text"]

if not question:

say("Please enter a question. Example: `/ask How to handle CrashLoopBackOff in Kubernetes Pods`")

return

Show loading indicator

say(f"🤔 Thinking about '{question}'...")

Call 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-generated answer — please verify accuracy"}

]

}

]

)

===== App Home Tab =====

@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": "*Available commands:*\n"

"• `/deploy` — Deploy a service\n"

"• `/status` — Check service status\n"

"• `/ask [question]` — Ask AI\n"

"• `/incident` — Report an incident"

}

},

{"type": "divider"},

{

"type": "section",

"text": {"type": "mrkdwn", "text": "*Recent deployments:*\n✅ API Server v1.2.3 → Production (2 hours ago)\n✅ Web v2.0.1 → Production (yesterday)"}

},

],

},

)

===== Scheduled Messages =====

def send_daily_standup_reminder():

"""Daily morning standup reminder"""

app.client.chat_postMessage(

channel="#engineering",

text="🌅 *Standup Reminder*\nPlease share your daily plan in the thread!\n1. What you did yesterday\n2. What you'll do today\n3. Blockers",

)

===== Error Handling =====

@app.error

def handle_errors(error, body, logger):

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

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

===== Run =====

if __name__ == "__main__":

Socket Mode (no webhook server needed, works behind firewalls)

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

handler.start()

Incident Management Bot

@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": "🚨 Incident Report"},

"submit": {"type": "plain_text", "text": "Report"},

"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": "Severity"},

},

{

"type": "input",

"block_id": "description",

"element": {

"type": "plain_text_input",

"multiline": True,

"placeholder": {"type": "plain_text", "text": "Describe the incident"},

"action_id": "desc_input",

},

"label": {"type": "plain_text", "text": "Description"},

},

],

},

)

@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"]

Create incident channel

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

channel = client.conversations_create(

name=channel_name,

is_private=False,

)

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

Initial incident message

client.chat_postMessage(

channel=channel_id,

blocks=[

{

"type": "header",

"text": {"type": "plain_text", "text": f"🚨 Incident Report ({severity})"}

},

{

"type": "section",

"text": {

"type": "mrkdwn",

"text": f"*Reporter:* <@{reporter}>\n"

f"*Severity:* {severity}\n"

f"*Description:*\n{description}"

}

},

{"type": "divider"},

{

"type": "section",

"text": {

"type": "mrkdwn",

"text": "⏱ *Please record the timeline in this thread*"

}

},

],

)

Announce in notification channel

client.chat_postMessage(

channel="#incidents",

text=f"🚨 New incident: {severity}\n📝 {description[:100]}...\n🔗 <#{channel_id}>",

)

Docker Deployment

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

Quiz

Call **ack()**. Slack displays a timeout error if it doesn't receive an acknowledgment within 3 seconds.

It uses WebSocket connections, so **no public URL or webhook server is needed**. It works behind firewalls and in local development environments immediately.

**section** (text/fields), **actions** (interactive elements like buttons/selects), and **input** (user input in modals).

`@app.action` handles interactions like button clicks, while `@app.view` handles modal submissions. They match using action_id and callback_id respectively.

The Bot Token is used for Slack API calls such as sending channel messages, while the App Token is used for Socket Mode WebSocket connections.

To centralize incident-related communication in one place, record a timeline, and maintain a history for post-incident analysis (postmortem).

LLM response times may exceed 3 seconds, so you should first respond with ack(), then call the LLM asynchronously and send the result as a separate message.

Conclusion

Slack Bots are tools that significantly boost team productivity. With the Bolt SDK, you can implement slash commands, modals, and button interactions in concise Python code, and deploy immediately without a separate server using Socket Mode.

References

- [Bolt for Python Official Guide](https://docs.slack.dev/tools/bolt-python/getting-started/)

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

- [Slack API Documentation](https://api.slack.com/docs)

현재 단락 (1/396)

Slack is a core communication platform for development teams. A well-built Slack Bot can automate va...

작성 글자: 0원문 글자: 11,241작성 단락: 0/396