Skip to content
BotServBotServ
DiscordBotOllamaIASlash Commands

Bot de Discord con Ollama

Crea un bot de Discord con Ollama. Slash Commands, respuestas IA, RAG, moderación y ejemplos prácticos.

S

schutzgeist

7 min read
Bot de Discord con Ollama

Bot de Discord con Ollama

Qué cubre este artículo

  • Cómo construir un bot de Discord con Ollama como backend de IA.
  • Cómo funcionan los Slash Commands, las respuestas en chat y RAG.
  • Cómo configurar e implementar el bot en Discord.
  • Casos prácticos para bots de FAQ, moderación y soporte comunitario.
  • Mejores prácticas para rate-limiting, seguridad y moderación.

Introducción: Bot de Discord con Ollama explicado

Un bot de Discord con Ollama responde preguntas en tu servidor de Discord usando un modelo de IA local, sin que el contenido del chat se envíe a OpenAI u otros servicios en la nube. El bot puede responder FAQ, buscar en documentos (RAG), moderar y activar flujos de trabajo.

Este artículo va dirigido a quienes quieren construir un bot de Discord con IA local. Encontrarás fundamentos en Ollama y Discord Bot Grundlagen.

¿Por qué necesito un bot de Discord con Ollama?

Imagina que tu comunidad hace preguntas recurrentes: “¿Cómo instalo X?”, “¿Dónde encuentro Y?”, “¿Qué es Z?”. Un bot con Ollama responde estas preguntas automáticamente, usando tu propio conocimiento (RAG) y de forma local, sin costos de API.

Bot de Discord con Ollama en pocas palabras

Creas un bot de Discord (discord.py o discord.js) que llama a Ollama cuando recibe mensajes y publica la respuesta. Con RAG, el bot puede buscar en tus documentos. Con Slash Commands, los usuarios pueden invocar funciones de forma dirigida.

La idea central es simple: Discord como interfaz, Ollama como cerebro.

Para quién es este artículo

  • Administradores de comunidades que quieren un bot de IA.
  • Desarrolladores que construyen bots de Discord.
  • Self-hosters que ejecutan bots de IA localmente.
  • Equipos que automatizan el soporte interno.

Experiencia con Python/JavaScript y Ollama es útil.

Conceptos clave

  • Discord Bot - Programa en Discord. Cuándo es útil: la interfaz.
  • Ollama - Servidor de modelos local. Cuándo es útil: el backend de IA.
  • Slash Commands - Comandos como /ask. Cuándo es útil: para invocaciones estructuradas.
  • RAG - Base de conocimiento. Cuándo es útil: para FAQ.
  • discord.py / discord.js - Librerías de bot. Cuándo es útil: para la implementación.
  • Intent - Permiso de Discord. Cuándo es útil: para acceso a mensajes.
  • Discord Bot Grundlagen - Conceptos básicos. Cuándo es útil: para configuración.

Configuración: Crear un bot de Discord

1. Discord Developer Portal

  1. Ve a https://discord.com/developers/applications
  2. “New Application” → Ingresa el nombre
  3. Pestaña “Bot” → “Add Bot”
  4. Copia el token (¡nunca lo compartas!)
  5. Pestaña “OAuth2” → “URL Generator”
    • Scope: bot, applications.commands
    • Permissions: Send Messages, Read Messages, Use Slash Commands
  6. Abre la URL generada → Invita el bot a tu servidor

2. Bot con discord.py

# bot.py
import discord
from discord import app_commands
import requests
import os

OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://ollama:11434")
TOKEN = os.environ.get("DISCORD_TOKEN")

intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)
tree = app_commands.CommandTree(client)

def call_ollama(prompt, system=None):
    """Llamar a Ollama"""
    messages = []
    if system:
        messages.append({"role": "system", "content": system})
    messages.append({"role": "user", "content": prompt})

    response = requests.post(
        f"{OLLAMA_URL}/api/chat",
        json={
            "model": "llama3.1",
            "messages": messages,
            "stream": False
        },
        timeout=60
    )
    return response.json()["message"]["content"]

@client.event
async def on_ready():
    await tree.sync()
    print(f"Bot online: {client.user}")

# Slash Command: /ask
@tree.command(name="ask", description="Haz una pregunta al bot de IA")
async def ask(interaction: discord.Interaction, frage: str):
    await interaction.response.defer()  # La IA necesita tiempo

    antwort = call_ollama(
        frage,
        system="Eres un asistente útil en un servidor de Discord. Responde brevemente y con utilidad."
    )

    await interaction.followup.send(f"**Pregunta:** {frage}\n\n{antwort}")

# Responder mensajes (opcional)
@client.event
async def on_message(message):
    if message.author.bot:
        return

    # Solo responder a menciones
    if client.user.mentioned_in(message):
        async with message.channel.typing():
            antwort = call_ollama(
                message.content.replace(f"<@{client.user.id}>", ""),
                system="Eres un asistente útil en Discord."
            )
            await message.reply(antwort)

client.run(TOKEN)

3. Iniciar el bot

pip install discord.py requests

export DISCORD_TOKEN="tu-bot-token"
export OLLAMA_URL="http://localhost:11434"

python bot.py

Caso práctico 1: Bot de FAQ con RAG

import chromadb

class FAQBot:
    def __init__(self):
        self.chroma = chromadb.HttpClient(host="chromadb", port=8000)
        self.collection = self.chroma.get_collection("faq")

    async def answer(self, question):
        """Responder pregunta con RAG"""
        # Buscar documentos similares
        results = self.collection.query(
            query_texts=[question],
            n_results=3
        )

        context = "\n".join(results["documents"][0])

        # Generar respuesta
        response = call_ollama(
            f"Contexto:\n{context}\n\nPregunta: {question}",
            system="Responde la pregunta basándote en el contexto. Proporciona las fuentes."
        )

        return {
            "answer": response,
            "sources": results["metadatas"][0]
        }

# Slash Command
@tree.command(name="faq", description="Pregunta a la FAQ")
async def faq(interaction: discord.Interaction, frage: str):
    await interaction.response.defer()
    result = await faq_bot.answer(frage)

    embed = discord.Embed(
        title="Respuesta de FAQ",
        description=result["answer"],
        color=discord.Color.blue()
    )
    embed.set_footer(text=f"Fuentes: {', '.join(s['source'] for s in result['sources'])}")
    await interaction.followup.send(embed=embed)

Caso práctico 2: Bot de moderación

@client.event
async def on_message(message):
    if message.author.bot:
        return

    # La IA analiza el mensaje
    analysis = call_ollama(
        f"Analiza este mensaje:\n{message.content}\n\n"
        "Categorías: normal, spam, tóxico, nsfw, fuera de tema\n"
        "Responde: CATEGORÍA|RAZÓN",
        system="Eres un asistente de moderación."
    )

    category, reason = analysis.split("|", 1)

    if category in ["spam", "tóxico", "nsfw"]:
        await message.delete()
        await message.channel.send(
            f"⚠️ Mensaje de {message.author.mention} eliminado: {reason}"
        )
        # Log
        log_moderation(message, category, reason)

Caso práctico 3: Carga de documentos → Indexación

@tree.command(name="index", description="Indexar documento para FAQ")
async def index(interaction: discord.Interaction, anhang: discord.Attachment):
    await interaction.response.defer()

    # Descargar archivo
    content = await anhang.read()

    # Extraer texto
    if anhang.filename.endswith(".pdf"):
        text = extract_pdf(content)
    else:
        text = content.decode("utf-8")

    # Crear chunks
    chunks = split_into_chunks(text, 500)

    # Crear embeddings
    for i, chunk in enumerate(chunks):
        embedding = get_embedding(chunk)
        collection.add(
            ids=[f"{anhang.filename}_{i}"],
            documents=[chunk],
            embeddings=[embedding],
            metadatas=[{"source": anhang.filename}]
        )

    await interaction.followup.send(
        f"✅ {anhang.filename} indexado ({len(chunks)} chunks)"
    )

Ejemplo práctico 4: Desencadenar un flujo de trabajo

@tree.command(name="workflow", description="Desencadenar un flujo de trabajo")
async def workflow(interaction: discord.Interaction, name: str, parameter: str):
    await interaction.response.defer()

    # Llamar al webhook de n8n
    response = requests.post(
        f"http://n8n:5678/webhook/{name}",
        json={"parameter": parameter, "user": str(interaction.user)}
    )

    await interaction.followup.send(f"Flujo de trabajo {name} iniciado: {response.text}")

Configuración con Docker

version: "3.8"

services:
  discord-bot:
    build: .
    container_name: discord-bot
    restart: unless-stopped
    environment:
      - DISCORD_TOKEN=${DISCORD_TOKEN}
      - OLLAMA_URL=http://ollama:11434
    networks:
      - ai-network
    depends_on:
      - ollama

  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    restart: unless-stopped
    ports:
      - "11434:11434"
    volumes:
      - ollama_data:/root/.ollama
    networks:
      - ai-network

volumes:
  ollama_data:

networks:
  ai-network:
    driver: bridge

Consideraciones de seguridad

  • Token del bot confidencial: Nunca lo incluyas en el código ni en Git. Usa variables de entorno. Consulta Secrets.
  • Permisos mínimos: El bot debe tener solo permisos de lectura y escritura, nunca derechos de administrador.
  • Rate-Limiting: Discord aplica límites de velocidad. Implementa throttling.
  • Filtro de contenido: La IA puede generar contenido inapropiado. Filtra las salidas. Consulta Guardrails.
  • Inyección de prompts: Los usuarios pueden enviar inyecciones maliciosas. Consulta Prompt Injection.
  • Registro de auditoría: Registra todas las acciones del bot. Consulta Audit Logging.

Errores comunes

  • Token en el código: Nunca incluyas el token directamente. Siempre usa variables de entorno.
  • Intents no configurados: Sin el intent message_content, el bot no puede leer mensajes.
  • Rate-Limits: Discord limita las llamadas a la API. Implementa lógica de reintentos.
  • Respuestas demasiado largas: Discord tiene un límite de 2000 caracteres. Acorta las respuestas o divídelas en partes.
  • Ollama lento: Los modelos grandes requieren tiempo. Usa defer() para Slash Commands.
  • Manejo de errores insuficiente: Si Ollama falla, el bot no debe bloquearse.

Enlaces relacionados

Key Takeaways:

  • Bot de Discord + Ollama: Un bot de IA local para tu servidor.
  • discord.py o discord.js para la implementación.
  • Slash Commands para llamadas estructuradas, on_message para chat.
  • RAG para preguntas frecuentes y consultas sobre documentos.
  • Seguridad: Token confidencial, rate-limiting, filtro de contenido.

FAQ

¿Cómo creo un bot de Discord?

Crea una aplicación en Discord Developer Portal, añade un bot, copia el token, invita el bot al servidor. Luego programa con discord.py o discord.js.

¿discord.py o discord.js?

discord.py para Python (más simple, bien documentado). discord.js para JavaScript/TypeScript (si usas Node.js). Ambas funcionan bien con Ollama.

¿Cómo llamar a Ollama desde el bot?

A través de HTTP: POST http://ollama:11434/api/chat con modelo y mensajes. En Python con requests, en JavaScript con fetch.

¿Puede el bot buscar en documentos?

Sí, con RAG. Los documentos se dividen en fragmentos, se generan embeddings y se almacenan en una base de datos de vectores. Al responder preguntas, se buscan documentos similares para usarlos como contexto.

¿Puede el bot moderar?

Sí. La IA analiza los mensajes para detectar spam, toxicidad y violaciones de reglas. Ante incumplimientos, el bot puede eliminar mensajes, advertir usuarios o notificar a administradores.

¿Cuánto cuesta el bot?

El bot de Discord es gratuito. Ollama es Open Source. Solo pagas por hardware. Sin costos de API por mensaje.

¿Es seguro el bot?

Sí, si mantienes el token confidencial, otorgas permisos mínimos, filtras contenido y evitas inyecciones de prompts. La IA local significa que los chats permanecen en tu servidor.

¿Hay límite de mensajes?

Discord limita las llamadas a la API (50 por segundo globalmente). Ollama está limitado por hardware (2-10 segundos por respuesta). Con alta frecuencia, implementa caché o limita solicitudes.

Fuentes y lecturas adicionales

Volver al blog
Share:

Entradas relacionadas