Skip to content
BotServBotServ
SlackBotBot de IAOllamaBoltSocket ModeIA para equipos

Bot de Slack con IA Local

Crea un bot de Slack con Ollama: Framework Bolt, Socket Mode, Slash Commands, threads y extensiones.

S

schutzgeist

8 min read
Bot de Slack con IA Local

Bot de Slack con IA local

Qué cubre este artículo

  • Cómo construir una app de Slack con el framework Bolt y Ollama.
  • Socket Mode vs. HTTP Events: por qué Socket Mode es ideal para self-hosters.
  • Slash commands, menciones, contexto de threads y bloques interactivos.
  • Extensiones: RAG, carga de archivos, contexto multiusuario.
  • Despliegue y seguridad en entornos empresariales.

Introducción

Slack es el estándar en equipos tech y cuenta con una excelente API oficial para bots. Las apps de Slack soportan slash commands, menciones, botones, modales y threads. Lo mejor para self-hosters: Socket Mode conecta el bot a Slack por WebSocket, sin necesidad de endpoint público, sin reverse proxy ni agujeros en el firewall.

Con Ollama detrás, el bot de Slack se convierte en un asistente interno del equipo: conocimiento empresarial mediante RAG, respuestas a preguntas sobre código, resúmenes, todo sin enviar contenido de Slack a servidores de IA en la nube (aunque Slack en sí, por supuesto, sigue viéndolo).

Casos de uso típicos

  • Bot de conocimiento interno: “¿Cómo solicito vacaciones?” → El bot responde desde el wiki mediante RAG.
  • Asistente de DevOps: “/ki ¿por qué falla la build?” → Analiza fragmentos de logs.
  • Ayuda en onboarding: Los nuevos empleados consultan al bot en lugar de molestar a colegas.
  • Bot de revisión de código: Carga un diff, obtén comentarios de revisión de un modelo local.
  • Resúmenes de reuniones: Resume el contenido de threads automáticamente.
  • Primer nivel de IT Help Desk: Captura preguntas frecuentes, escala el resto.

Requisitos previos

  • Workspace de Slack (el plan gratuito funciona, pero cuidado: el historial de mensajes solo se conserva 90 días)
  • Permisos de admin en el workspace (o autorización para instalar apps)
  • Servidor con Ollama + Python 3.10+

Paso 1: Crear la app de Slack

En api.slack.com/apps → “Create New App” → “From scratch”:

  1. Activa Socket Mode (Settings → Socket Mode → Enable) → Genera un token a nivel de app xapp-... (Scope: connections:write)
  2. Bot Token Scopes (OAuth & Permissions): app_mentions:read, chat:write, commands, channels:history, im:history, files:read
  3. Crea un slash command: /ki (la Request URL no importa en Socket Mode, puedes dejar un valor dummy)
  4. Event Subscriptions: Suscríbete a app_mention, message.im
  5. Install to Workspace → Copia el bot token xoxb-...

Dos tokens: xapp- (a nivel de app, para Socket Mode) + xoxb- (token del bot). Ambos en .env:

SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...
OLLAMA_URL=http://localhost:11434
OLLAMA_MODEL=llama3.1:8b

Paso 2: Bot con 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 = ("Eres el asistente IA interno. Responde de forma concisa y precisa, "
          "en español. Para código: usa bloques de markdown.")


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


# Carga el historial del thread, ¡Slack lo proporciona!
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:]:   # el primer mensaje es el actual
        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"))
    # Elimina el marcado de mención <@U123>
    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):
    # Solo mensajes directos (tipo de canal "im"), no respuestas del bot
    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 requiere ACK en <3 segundos
    prompt = command.get("text", "").strip()
    if not prompt:
        respond("Uso: `/ki <pregunta>`")
        return
    # La respuesta puede tardar >3s, usa respond retrasado
    answer = ask_ollama(prompt)
    respond(answer)   # por defecto "ephemeral", solo quien pregunta la ve


if __name__ == "__main__":
    SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()

Eso es todo, sin servidor web, sin puertos, sin dominios. El bot se conecta por WebSocket a Slack y está disponible al instante.

Socket Mode vs. HTTP Events

AspectoSocket ModeHTTP Events API
ConexiónEl bot abre un WebSocket a SlackSlack hace push a tu endpoint HTTPS
Endpoint públicoNo necesarioObligatorio (dominio + TLS + reverse proxy)
Firewall/NATFunciona en cualquier lugarRequiere puertos de entrada
Escalabilidad1 conexión por procesoCualquier número de instancias
Mejor paraSelf-hosting, bots internosApps públicas del Slack Marketplace

Para bots internos de equipo, Socket Mode es claramente la opción superior.

Extensiones

Bloques interactivos (botones, modales)

@app.command("/ki")
def cmd_ki(ack, respond, command):
    ack()
    respond(blocks=[
        {"type": "section", "text": {"type": "mrkdwn",
          "text": f"*Pregunta:* {command['text']}"}},
        {"type": "actions", "elements": [
            {"type": "button", "text": {"type": "plain_text",
             "text": "Como código"}, "action_id": "fmt_code",
             "value": command["text"]},
            {"type": "button", "text": {"type": "plain_text",
             "text": "Detallado"}, "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 + "\nResponde solo con un bloque de código.")
    respond(answer)

RAG: Conocimiento empresarial interno

El bot busca primero en la base de datos vectorial (exportación de wiki, manuales, runbooks segmentados con 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\nDocumentos internos relevantes:\n{context}\n" \
              "Cita la fuente al final de tu respuesta."
    # ...

Importante: Por canal o DM puede ser necesario compartir contexto, no todos los canales deben acceder a todos los documentos. Construye filtros de acceso en la búsqueda (filter={"must": [{"key": "allowed_channels"...}]}).

Analizar cargas de archivos

El usuario sube un archivo → el bot lo descarga desde Slack → lo envía a la IA:

@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 similar: actualizaciones de mensajes

Slack permite chat.update: escribir la respuesta de forma incremental en el mismo post:

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)

Despliegue

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 requiere solo conexiones salientes, por lo que el bot funciona detrás de cualquier firewall, incluso en una LAN doméstica sin redirección de puertos.

Seguridad y cumplimiento normativo

  • Dos tokens, ambos secretos: xoxb (permisos del bot) y xapp (conexión Socket). Si se filtra en api.slack.com, revócalos inmediatamente.
  • Mantén los permisos al mínimo: solo lo que el bot necesita. channels:history solo si requieres contexto de hilos en canales.
  • RGPD: Slack es un servicio estadounidense (Salesforce), los contenidos de chat van a servidores Slack. Para comunicaciones internas confidenciales: aloja Mattermost o Matrix tú mismo.
  • Auditoría: registra las invocaciones del bot (quién preguntó qué, qué documentos se recuperaron), consulta Audit Logging.
  • Aislamiento de canales: el bot no debe llevar cada documento interno a cada canal. Refleja los derechos de acceso en el filtro RAG.

Errores comunes

  • ACK de 3 segundos: los slash commands y las acciones deben reconocerse en menos de 3s, de lo contrario aparece “operation_timeout”. Primero ack(), luego responde lentamente vía respond().
  • Markup de menciones: event["text"] contiene <@U0123ABC>, elimínalo antes del prompt.
  • El evento message genera demasiados disparos: message se activa para todos los mensajes visibles, filtra por channel_type=="im" o por prefijos personalizados, de lo contrario el bot procesa cada post del canal.
  • Eventos duplicados: Slack a veces envía eventos dos veces (reintentos), implementa deduplicación por event_id.
  • Límite del plan gratuito: historial de 90 días, los mensajes de hilos más antiguos desaparecen y el contexto se interrumpe.

Enlaces útiles

Puntos clave:

  • Socket Mode no requiere endpoint público, ideal para autohospedaje.
  • Bolt Framework (Python/JS) es la forma oficial y más simple.
  • Los slash commands necesitan ACK en menos de 3s: proporciona la respuesta después via respond().
  • Slack proporciona el historial de hilos: el contexto sale casi gratis.
  • Para contenidos confidenciales: aloja Mattermost o Matrix en lugar de Slack.

Preguntas frecuentes

¿Qué es Socket Mode?

El bot se conecta a Slack mediante WebSocket saliente: Slack envía eventos a través de esta conexión. No se necesita endpoint HTTPS público: es ideal detrás de firewall/NAT sin redirección de puertos.

¿Necesito permisos de administrador?

Sí, para instalar la aplicación (o que el administrador la apruebe). En muchos espacios de trabajo empresariales la instalación de aplicaciones está restringida, consulta previamente con el administrador del espacio de trabajo.

¿Puede solo el que pregunta ver la respuesta?

Sí: las respuestas de slash commands son “efímeras” por defecto (visibles solo para quien las activó). Para que sea visible para todos: usa response_type: in_channel o say().

¿Cómo obtiene el bot el contexto de un hilo?

Slack lo proporciona directamente: conversations_replies(channel, thread_ts) devuelve todos los mensajes del hilo. No necesitas almacenar nada por tu cuenta, una ventaja sobre Telegram o Signal.

¿Es un bot de Slack conforme al RGPD?

Slack almacena mensajes en servidores estadounidenses (alojamiento en la UE disponible con planes Enterprise). Para bots de equipos internos es común y justificable, para datos de clientes verifica la legalidad. Máxima soberanía: aloja Mattermost por tu cuenta.

¿Cuánto cuesta?

La API de Slack es gratuita (incluso en el plan gratuito). Solo incurres en costos de servidor. El plan gratuito tiene un límite de 90 días de mensajes, para RAG sobre documentación usa mejor tu propia fuente de datos.

¿Múltiples usuarios simultáneamente?

Sí: Bolt es compatible con async. El cuello de botella es Ollama (inferencia en serie). Con muchos usuarios: usa un backend vLLM o una cola con notificación de espera.

El bot responde a todo, ¿cómo filtro?

El evento message se dispara en cada mensaje de canales visibles. Filtra por channel_type==‘im’ para mensajes directos, por menciones (@bot) en canales, o por un prefijo como ‘!ki’.

Fuentes y lecturas complementarias

Volver al blog
Share:

Entradas relacionadas