Skip to content
BotServBotServ
MattermostBotBot IAOllamaSelf-HostingAlternativa SlackOpen Source

Bot Mattermost con IA Local

Crea un bot Mattermost con Ollama: alternativa Slack auto-hospedada, cuentas bot, Slash-Commands, WebSocket y Docker.

S

schutzgeist

7 min read
Bot Mattermost con IA Local

Bot de Mattermost con IA local

Qué encontrarás en este artículo

  • Cómo construir un bot en tu propio servidor Mattermost con Ollama.
  • Crear cuenta de bot, REST-API y eventos WebSocket.
  • Slash-commands y posts interactivos.
  • Extensiones: RAG sobre conocimiento corporativo, moderación, alertas.
  • Por qué Mattermost + Ollama es la combinación completamente soberana.

Introducción

Mattermost es el clon autohospedado de Slack: código abierto (edición Teams gratuita), funciona en tu propio servidor, todos los datos permanecen contigo. Combinado con Ollama obtienes la solución completamente soberana: plataforma de chat e IA locales, nada sale de tu infraestructura.

La integración de bots es oficial y bien documentada: cuentas de bot con token propio, REST-API para todo, WebSocket para eventos en tiempo real, slash-commands, botones interactivos en mensajes.

Casos de uso típicos

  • Bot de conocimiento interno: base de datos corporativa mediante RAG, completamente on-premise, compatible con GDPR.
  • Asistente DevSecOps: análisis de logs, respuestas de runbooks, soporte en incidentes.
  • Organismos públicos y sectores regulados: chat de IA donde los servicios en la nube no están permitidos.
  • Centro de alertas: monitoreo → canal Mattermost → IA resume automáticamente.
  • Onboarding: nuevos empleados preguntan al bot sin interrumpir a los compañeros.

Requisitos previos

  • Servidor Mattermost en funcionamiento (Docker: mattermost/mattermost-team-edition)
  • Acceso de administrador para crear la cuenta de bot
  • Ollama con modelo en el mismo host o red
  • Python 3.10+

Paso 1: Alojar Mattermost (versión breve)

# docker-compose.yml, infraestructura base de Mattermost
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_DB: mattermost
      POSTGRES_USER: mmuser
      POSTGRES_PASSWORD: geheim-aendern
    volumes: [db_data:/var/lib/postgresql/data]
    restart: always

  mattermost:
    image: mattermost/mattermost-team-edition:latest
    depends_on: [db]
    ports: ["8065:8065"]
    environment:
      MM_SQLSETTINGS_DRIVERNAME: postgres
      MM_SQLSETTINGS_DATASOURCE: "postgres://mmuser:geheim-aendern@db:5432/mattermost?sslmode=disable"
      MM_SERVICESETTINGS_SITEURL: "https://chat.deine-domain.de"
    volumes: [mm_data:/mattermost/data]
    restart: always

volumes:
  db_data:
  mm_data:

En el panel de administración (System Console → Integrations) habilita Bot Accounts.

Paso 2: Crear cuenta de bot

En la interfaz: System Console → Integrations → Bot Accounts → “Add Bot Account” → nombre de usuario ki-assistent. O mediante API/CLI:

# mediante mmctl en el contenedor
docker exec mattermost mmctl --local bot create ki-assistent --display-name "KI-Assistent" --description "Bot de IA local"

Recibirás un Bot-Token (xxx...), como una clave API, guárdalo en .env:

MM_URL=http://mattermost:8065
MM_TOKEN=dein-bot-token
MM_TEAM=dein-team
OLLAMA_URL=http://ollama:11434
OLLAMA_MODEL=llama3.1:8b

Paso 3: Bot con WebSocket (tiempo real)

Mattermost tiene una API WebSocket simple: los mensajes llegan en vivo:

import asyncio
import json
import os
import aiohttp
import websockets
import ollama
from dotenv import load_dotenv

load_dotenv()
MM_URL = os.environ["MM_URL"]
MM_TOKEN = os.environ["MM_TOKEN"]
MODEL = os.environ.get("OLLAMA_MODEL", "llama3.1:8b")

ollama_client = ollama.Client(host=os.environ["OLLAMA_URL"])
HEADERS = {"Authorization": f"Bearer {MM_TOKEN}"}

SYSTEM = "Du bist der interne KI-Assistent. Antworte kurz auf Deutsch."


async def api(method, path, payload=None):
    async with aiohttp.ClientSession() as s:
        async with s.request(method, f"{MM_URL}/api/v4{path}",
                             headers=HEADERS, json=payload) as r:
            return await r.json()


async def post_message(channel_id, text, root_id=None):
    await api("POST", "/posts", {
        "channel_id": channel_id,
        "message": text[:16000],
        "root_id": root_id,
    })


def ask_ollama(text, history=None):
    msgs = [{"role": "system", "content": SYSTEM}]
    if history:
        msgs += history
    msgs.append({"role": "user", "content": text})
    return ollama_client.chat(model=MODEL, messages=msgs)["message"]["content"]


async def main():
    me = await api("GET", "/users/me")
    bot_id = me["id"]

    ws_url = MM_URL.replace("http", "ws") + "/api/v4/websocket"
    async with websockets.connect(ws_url) as ws:
        await ws.send(json.dumps({
            "seq": 1, "action": "authentication_challenge",
            "data": {"token": MM_TOKEN}
        }))
        async for raw in ws:
            evt = json.loads(raw)
            if evt.get("event") != "posted":
                continue
            post = json.loads(evt["data"]["post"])
            if post["user_id"] == bot_id:   # ignorar posts propios
                continue

            text = post["message"]
            mention = f"@{me['username']}"
            channel_type = evt["data"].get("channel_type")

            # responder solo a mensajes directos o @menciones
            if channel_type != "D" and mention not in text:
                continue
            text = text.replace(mention, "").strip()
            if not text:
                continue

            # cargar contexto del thread desde posts raíz
            history = []
            if post.get("root_id"):
                thread = await api("GET", f"/posts/{post['root_id']}")
                for p in thread["posts"].values():
                    role = "assistant" if p["user_id"] == bot_id else "user"
                    history.append({"role": role, "content": p["message"]})

            answer = ask_ollama(text, history)
            await post_message(post["channel_id"], answer,
                               root_id=post.get("root_id") or post["id"])

asyncio.run(main())

Slash-commands

Registra un slash-command /ki en la consola de administración que apunte a tu endpoint HTTP (Mattermost enviará datos hacia ti, el bot necesitará un puerto HTTP):

from aiohttp import web

async def slash_ki(request):
    data = await request.post()
    prompt = data.get("text", "").strip()
    if not prompt:
        return web.json_response({"text": "Uso: `/ki <pregunta>`",
                                  "response_type": "ephemeral"})
    answer = ask_ollama(prompt)
    return web.json_response({
        "text": answer,
        "response_type": "ephemeral",   # solo el usuario que preguntó lo ve
    })

app = web.Application()
app.router.add_post("/slash/ki", slash_ki)
web.run_app(app, port=8080)

Extensiones

RAG sobre conocimiento corporativo

Idéntico al bot de Slack: embeddings de la pregunta → búsqueda en Qdrant en la colección firmenwissen → contexto en el system prompt. En Mattermost es más fácil justificarlo, todos los datos están on-premise de todas formas.

Posts interactivos (botones)

await api("POST", "/posts", {
    "channel_id": channel_id,
    "message": "¿Te fue útil la respuesta?",
    "props": {"attachments": [{
        "actions": [
            {"id": "good", "name": "👍 Útil", "type": "button",
             "integration": {"url": f"{BOT_URL}/action", "context": {"vote": "up"}}},
            {"id": "bad", "name": "👎 Incorrecta", "type": "button",
             "integration": {"url": f"{BOT_URL}/action", "context": {"vote": "down"}}},
        ]
    }]},
})

Alertas Proactivas

El bot escribe directamente en los canales: integración de monitoreo:

# Cron/Systemd-Timer o un manejador de eventos ejecuta:
async def alert(channel_id, service, status):
    summary = ask_ollama(
        f"Fasse diesen Monitoring-Alert kurz zusammen und schlage den "
        f"wahrscheinlichsten Fix vor:\nService: {service}\nStatus: {status}"
    )
    await post_message(channel_id, f"🚨 **{service}**\n{summary}")

Moderación

Clasifica nuevos posts (OK/SPAM/TOXIC) y, en caso de infracción, elimina el post (DELETE /posts/{id}) y advierte al usuario, siguiendo el mismo patrón que en el artículo sobre Telegram.

Despliegue: Todo en un mismo stack

services:
  mm-bot:
    build: ./bot
    restart: always
    env_file: .env
    depends_on: [mattermost, ollama]
    ports: ["127.0.0.1:8080:8080"]   # para Slash-Commands/Actions

  ollama:
    image: ollama/ollama:latest
    volumes: [ollama_data:/root/.ollama]
    restart: always

Bot, Mattermost y Ollama en la misma red Docker: sin dependencias externas, los datos no salen de tu infraestructura.

Seguridad

  • Token del bot: dispone de permisos completos, trátalo como un token de administrador. Guárdalo en .env o Docker Secrets.
  • Aislamiento de canales: el bot solo ve los canales donde es miembro, no accede automáticamente a todo.
  • Rate-Limiting: en equipos públicos, añade límites de acceso por usuario (Token-Bucket).
  • Logging: registra las invocaciones del bot (Audit Logging).
  • TLS: para Slash-Commands/Actions usa HTTPS; dentro de la red Docker basta HTTP.

Problemas Comunes

  • El bot no ve el canal: debe ser miembro del canal, agrega desde la UI o vía POST /channels/{id}/members.
  • WebSocket se desconecta: implementa lógica de reconexión, los parpadeos de red son normales.
  • El bot responde a sus propios posts: verifica post.user_id == bot_id, sino entrará en bucle.
  • SiteURL incorrecta: los botones interactivos y adjuntos necesitan la SiteURL correcta.
  • Límite de mensajes: los posts están limitados a ~16.383 caracteres, divide las respuestas largas.

Enlaces relacionados

  • IRC-Coding.de: tutoriales de programación avanzada: WebSocket, REST APIs, Python asincrónico.
  • Slack-Bot: API casi idéntica, pero alojada en la nube.
  • Rocket.Chat-Bot: segunda alternativa autohospedada.
  • Matrix-Bots: alternativa descentralizada.
  • Docker: bases del despliegue.
  • RAG Local: integra conocimiento empresarial.

Puntos clave:

  • Mattermost + Ollama = plataforma de chat con IA completamente soberana, 100% on-premise.
  • Bot-Account + REST/WebSocket bastan, no necesitas un proceso de aprobación como en Slack.
  • WebSocket para eventos en tiempo real, REST para posts, Slash-Commands vía HTTP-Endpoint.
  • Ideal para empresas, organismos públicos y cualquiera que no quiera compartir datos de chat.
  • La Community Edition es gratuita e ilimitada.

Preguntas Frecuentes

¿Qué edición de Mattermost necesito?

La Community Edition gratuita es suficiente: incluye bots, Slash-Commands y WebSocket. Solo necesitas Professional/Enterprise para SAML, exportaciones de conformidad y clustering.

¿Mattermost o Slack?

Mattermost si la soberanía de datos es crítica (todo on-premise, sin nube). Slack si tu equipo ya lo utiliza y no quieres mantener infraestructura. Los conceptos de API son prácticamente idénticos: el código es portable.

¿Bot-Account o usuario normal?

Siempre usa Bot-Account: dispone de su propio token, se marca como bot en la UI, no puede iniciar sesión con contraseña y tiene permisos más claros. Reutilizar cuentas de usuario normales como bots viola las políticas de Mattermost.

¿Cuántos recursos necesita Mattermost?

Mattermost: ~1 GB RAM, poco CPU. Postgres suma ~500 MB. Ollama con modelo (8B Q4): ~6 GB VRAM. Una mini-PC con 16 GB RAM + GPU es suficiente para equipos pequeños.

¿Ve el bot todos los canales?

No, solo accede a los canales donde es miembro, además de los mensajes directos. Es un mecanismo de seguridad: el control de acceso se gestiona mediante la pertenencia al canal, no por código.

¿Bots o Plugins de Mattermost?

Para integraciones de IA, un bot es suficiente: más simple, independiente del lenguaje y desplegable de forma autónoma. Los plugins (Go/React) solo son necesarios para integración profunda en la UI. También existen plugins oficiales de IA (mattermost-ai).

¿Qué debo respaldar?

La base de datos Postgres (chats), /mattermost/data (descargas) y la configuración/sesión del bot. Consulta el artículo sobre backups. El código del bot en sí es sin estado.

Fuentes y lecturas adicionales

Volver al blog
Share:

Entradas relacionadas