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
.envo 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
SiteURLcorrecta. - 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?
¿Mattermost o Slack?
¿Bot-Account o usuario normal?
¿Cuántos recursos necesita Mattermost?
¿Ve el bot todos los canales?
¿Bots o Plugins de Mattermost?
¿Qué debo respaldar?
Fuentes y lecturas adicionales
- Mattermost: Chat autohospedado.
- Mattermost API: referencia REST.
- Mattermost Bot Accounts: documentación de bots.
- IRC-Coding.de: tutoriales de programación.


