Slack-Bot mit lokaler KI
Was dieser Artikel behandelt
- Wie Du eine Slack-App mit dem Bolt-Framework und Ollama baust.
- Socket Mode vs. HTTP-Events, warum Socket Mode für Self-Hoster ideal ist.
- Slash-Commands, Mentions, Thread-Kontext und interaktive Blöcke.
- Erweiterungen: RAG, Datei-Uploads, Mehrbenutzer-Kontext.
- Deployment und Sicherheit im Unternehmensumfeld.
Einleitung
Slack ist der Standard in Tech-Teams und hat eine hervorragende offizielle Bot-API. Slack-Apps können Slash-Commands, Mentions, Buttons, Modals und Threads. Der Clou für Self-Hoster: Socket Mode verbindet den Bot per WebSocket zu Slack, kein öffentlicher Endpoint, kein Reverse Proxy, keine Firewall-Löcher nötig.
Mit Ollama dahinter wird der Slack-Bot zum internen Team-Assistenten: Firmenwissen per RAG, Code-Fragen, Zusammenfassungen, ohne dass Slack-Inhalte an KI-Clouds gehen (Slack selbst sieht sie natürlich trotzdem).
Typische Anwendungsszenarien
- Interner Wissensbot: „Wie beantrage ich Urlaub?” → Bot antwortet aus dem Wiki per RAG.
- DevOps-Assistent: „/ki warum schlägt der Build fehl?” → Log-Ausschnitt analysieren lassen.
- Onboarding-Helfer: Neue Mitarbeiter fragen den Bot statt Kollegen.
- Code-Review-Bot: Diff hochladen, Review-Kommentare von lokalem Coding-Modell.
- Meeting-Zusammenfassungen: Thread-Inhalte zusammenfassen lassen.
- IT-Helpdesk-First-Level: Häufige Fragen abfangen, Rest eskalieren.
Voraussetzungen
- Slack-Workspace (Free reicht, aber Achtung: Nachrichten-Verlauf im Free-Plan nur 90 Tage)
- Admin-Rechte im Workspace (oder Freigabe zum App-Installieren)
- Server mit Ollama + Python 3.10+
Schritt 1: Slack-App erstellen
Auf api.slack.com/apps → „Create New App” → „From scratch”:
- Socket Mode aktivieren (Settings → Socket Mode → Enable) → App-Level-Token
xapp-...generieren (Scope:connections:write) - Bot Token Scopes (OAuth & Permissions):
app_mentions:read,chat:write,commands,channels:history,im:history,files:read - Slash Command anlegen:
/ki(Request URL egal bei Socket Mode, kurzer Dummy) - Event Subscriptions:
app_mention,message.imabonnieren - Install to Workspace → Bot-Token
xoxb-...kopieren
Zwei Tokens: xapp- (App-Level, Socket Mode) + xoxb- (Bot-Token). Beide in .env:
SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...
OLLAMA_URL=http://localhost:11434
OLLAMA_MODEL=llama3.1:8b
Schritt 2: Bot mit Bolt (Python)
pip install slack-bolt ollama python-dotenv
bot.py:
import os
import ollama
from dotenv import load_dotenv
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
load_dotenv()
app = App(token=os.environ["SLACK_BOT_TOKEN"])
ollama_client = ollama.Client(host=os.environ["OLLAMA_URL"])
MODEL = os.environ.get("OLLAMA_MODEL", "llama3.1:8b")
SYSTEM = ("Du bist der interne KI-Assistent. Antworte kurz, präzise, "
"auf Deutsch. Bei Code: Markdown-Codeblöcke.")
def ask_ollama(prompt: str, history: list | None = None) -> str:
messages = [{"role": "system", "content": SYSTEM}]
if history:
messages.extend(history)
messages.append({"role": "user", "content": prompt})
resp = ollama_client.chat(model=MODEL, messages=messages)
return resp["message"]["content"]
# Thread-Verlauf laden — Slack liefert ihn selbst!
def thread_history(client, channel, thread_ts) -> list:
if not thread_ts:
return []
result = client.conversations_replies(channel=channel, ts=thread_ts, limit=20)
history = []
for m in result["messages"][1:]: # erste Nachricht = die aktuelle
role = "assistant" if m.get("bot_id") else "user"
history.append({"role": role, "content": m.get("text", "")})
return history
@app.event("app_mention")
def handle_mention(event, say, client):
history = thread_history(client, event["channel"], event.get("thread_ts"))
# Mention-Markup <@U123> entfernen
text = event["text"].split(">", 1)[-1].strip()
answer = ask_ollama(text, history)
say(text=answer, thread_ts=event.get("thread_ts") or event["ts"])
@app.event("message")
def handle_dm(event, say, client):
# Nur Direktnachrichten (Channel-Typ "im"), keine eigenen Bot-Nachrichten
if event.get("channel_type") != "im" or event.get("bot_id"):
return
history = thread_history(client, event["channel"], event.get("thread_ts"))
answer = ask_ollama(event.get("text", ""), history)
say(text=answer, thread_ts=event.get("thread_ts") or event["ts"])
@app.command("/ki")
def cmd_ki(ack, respond, command):
ack() # Slack verlangt ACK in <3 Sekunden!
prompt = command.get("text", "").strip()
if not prompt:
respond("Nutzung: `/ki <Frage>`")
return
# Antwort kann >3s dauern → delayed respond nutzen
answer = ask_ollama(prompt)
respond(answer) # standardmäßig "ephemeral" — nur der Fragende sieht es
if __name__ == "__main__":
SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()
Das war’s, kein Webserver, kein Port, keine Domain. Der Bot verbindet sich per WebSocket zu Slack und ist sofort erreichbar.
Socket Mode vs. HTTP Events
| Aspekt | Socket Mode | HTTP Events API |
|---|---|---|
| Verbindung | Bot baut WebSocket zu Slack auf | Slack pusht an Deinen HTTPS-Endpoint |
| Öffentlicher Endpoint | Nicht nötig | Nötig (Domain + TLS + Reverse Proxy) |
| Firewall/NAT | Funktioniert überall | Eingehende Ports nötig |
| Skalierung | 1 Verbindung pro Prozess | Beliebig viele Instanzen |
| Best for | Self-Hosting, interne Bots | Öffentliche Slack-Marketplace-Apps |
Für interne Team-Bots ist Socket Mode klar die bessere Wahl.
Erweiterungen
Interaktive Blöcke (Buttons, Modals)
@app.command("/ki")
def cmd_ki(ack, respond, command):
ack()
respond(blocks=[
{"type": "section", "text": {"type": "mrkdwn",
"text": f"*Frage:* {command['text']}"}},
{"type": "actions", "elements": [
{"type": "button", "text": {"type": "plain_text",
"text": "Als Code"}, "action_id": "fmt_code",
"value": command["text"]},
{"type": "button", "text": {"type": "plain_text",
"text": "Ausführlich"}, "action_id": "fmt_long",
"value": command["text"]},
]},
])
@app.action("fmt_code")
def on_code(ack, body, respond):
ack()
prompt = body["actions"][0]["value"]
answer = ask_ollama(prompt + "\nAntworte nur mit einem Codeblock.")
respond(answer)
RAG: Internes Firmenwissen
Der Bot durchsucht zuerst die Vektordatenbank (Wiki-Export, Handbücher, Runbooks gechunkt mit bge-m3):
def ask_with_rag(prompt, history=None):
emb = ollama_client.embeddings(model="bge-m3", prompt=prompt)["embedding"]
hits = qdrant.search("firmenwissen", query_vector=emb, limit=4)
context = "\n\n".join(h.payload["text"] for h in hits)
system = SYSTEM + f"\n\nRelevante interne Dokumente:\n{context}\n" \
"Zitiere die Quelle am Ende der Antwort."
# ...
Wichtig: Pro Channel/DM kann Kontext-Freigabe nötig sein, nicht jeder Channel darf jedes Dokument sehen. Zugriffsfilter in die Suche einbauen (filter={"must": [{"key": "allowed_channels", ...}]}).
Datei-Uploads analysieren
Nutzer laden eine Datei hoch → Bot lädt sie von Slack → an KI:
@app.event("message")
def handle_file(event, say, client):
for f in event.get("files", []):
if f["filetype"] in ("png", "jpg"):
# Vision-Modell
data = download_slack_file(f["url_private"])
resp = ollama_client.chat(model="minicpm-v", messages=[{
"role": "user", "content": "Was ist auf diesem Bild?",
"images": [data],
}])
say(resp["message"]["content"], thread_ts=event["ts"])
Streaming-Ähnlichkeit: Message-Updates
Slack erlaubt chat.update: Antwort schrittweise ins selbe Posting schreiben:
posted = say("…", thread_ts=event["ts"])
text = ""
for chunk in ollama_client.chat(model=MODEL, messages=msgs, stream=True):
text += chunk["message"]["content"]
if len(text) % 100 == 0:
client.chat_update(channel=channel, ts=posted["ts"], text=text)
client.chat_update(channel=channel, ts=posted["ts"], text=text)
Deployment
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY bot.py .
CMD ["python", "bot.py"]
services:
slack-bot:
build: .
restart: always
env_file: .env
depends_on: [ollama]
ollama:
image: ollama/ollama:latest
volumes: [ollama_data:/root/.ollama]
volumes:
ollama_data:
Socket Mode braucht nur ausgehende Verbindungen, der Bot läuft hinter jeder Firewall, auch im Home-LAN ohne Port-Forwarding.
Sicherheit und Compliance
- Zwei Tokens, beide geheim:
xoxb(Bot-Rechte) undxapp(Socket-Verbindung). Bei Leak in api.slack.com revoken. - Scopes minimal halten: Nur was der Bot braucht.
channels:historynur wenn Thread-Kontext in Channels nötig. - DSGVO: Slack ist ein US-Dienst (Salesforce): Chat-Inhalte gehen an Slack-Server. Für vertrauliche interne Kommunikation: Mattermost oder Matrix selbst hosten.
- Audit: Bot-Aufrufe loggen (wer fragte was, welche Dokumente abgerufen), siehe Audit Logging.
- Channel-Isolation: Der Bot sollte nicht jedes interne Dokument in jeden Channel bringen: Zugriffsrechte im RAG-Filter spiegeln.
Typische Stolpersteine
- 3-Sekunden-ACK: Slash-Commands und Actions müssen in <3s ge-ackt werden, sonst „operation_timeout”. Erst
ack(), dann langsam antworten viarespond(). - Mention-Markup:
event["text"]enthält<@U0123ABC>, vor dem Prompt entfernen. - message-Event flutet:
messagefeuert für alle sichtbaren Nachrichten, aufchannel_type=="im"oder eigene Präfixe filtern, sonst verarbeitet der Bot jeden Channel-Post. - Duplicate Events: Slack sendet Events manchmal doppelt (Retries):
event_id-Deduplizierung einbauen. - Free-Plan-Limit: 90-Tage-Verlauf, ältere Thread-Nachrichten sind weg, Thread-Kontext bricht dann ab.
Weiterführende Links
- IRC-Coding.de: Vertiefende Programmier-Tutorials: Slack-Bolt, WebSocket-Handling, Block Kit.
- Mattermost-Bot: Self-hosted Slack-Alternative.
- Teams-Bot: Microsoft-Alternative.
- Plattformen-Vergleich: Alle Plattformen im Vergleich.
- Ollama: Modellserver.
- Lokales RAG: Firmenwissen anbinden.
Key Takeaways:
- Socket Mode = kein öffentlicher Endpoint nötig, ideal für Self-Hoster.
- Bolt-Framework (Python/JS) ist der offizielle, einfachste Weg.
- Slash-Commands brauchen ACK in <3s: Antwort per respond() nachliefern.
- Thread-Verlauf liefert Slack selbst: Kontext fast gratis.
- Für vertrauliche Inhalte: Mattermost oder Matrix statt Slack.
FAQ
Was ist Socket Mode?
Brauche ich Admin-Rechte?
Kann nur der Fragende die Antwort sehen?
Wie bekommt der Bot Thread-Kontext?
Ist ein Slack-Bot DSGVO-konform?
Was kostet das?
Mehrere Nutzer gleichzeitig?
Der Bot reagiert auf alles, wie filtern?
Quellen und weiterführende Literatur
- Slack Bolt for Python: Offizielle Framework-Doku.
- Slack API: API-Referenz und App-Verwaltung.
- Socket Mode: Socket-Mode-Doku.
- IRC-Coding.de: Programmier-Tutorials.


