IA para bots de Discord: crea uno con una API LLM
Una IA para bots de Discord responde preguntas de programación dentro de tu servidor, usando un modelo de lenguaje detrás de una API. Este tutorial construye uno en unas 70 líneas de Python y cubre las partes que suelen fallar.
Actualizado

Puntos clave
- El bot necesita discord.py, el paquete openai, un token de bot y una clave de API LLM.
- Habilita la intención de contenido de mensajes en el portal de desarrolladores de Discord o el bot verá mensajes vacíos.
- Mantén un historial corto por canal y recórtalo, ya que el modelo no recuerda nada entre llamadas.
- Divide las respuestas a 2.000 caracteres y limita las llamadas simultáneas para mantenerte bajo el límite de 8 peticiones simultáneas.
Qué hará el bot
El objetivo es un bot de Discord con IA para programar que reaccione cuando alguien lo mencione. Un usuario escribe "@helper, ¿por qué mi comprensión de listas devuelve None?", el bot envía el mensaje al modelo y la respuesta del modelo llega como respuesta en el mismo canal. El bot también recuerda los últimos turnos de cada canal, para que las preguntas de seguimiento funcionen.
El diseño tiene tres partes. discord.py maneja la conexión con Discord. El SDK de Python de OpenAI habla con la API LLM, que funciona aquí porque la API es compatible con OpenAI. Un pequeño diccionario guarda el historial de conversaciones. No necesitas una base de datos para empezar.
Necesitarás Python 3.9 o superior, una cuenta de Discord que pueda crear una aplicación y una clave de API desde la página de claves de API. Las cuentas nuevas pueden reclamar $0,50 de crédito de prueba con un solo clic, suficiente para construir y probar este bot.
Paso 1: crea la aplicación de Discord
- Abre el portal de desarrolladores de Discord y crea una nueva aplicación.
- En la sección Bot, crea el usuario del bot y copia su token. Trátalo como una contraseña.
- En la misma página, activa la Intención de Contenido de Mensajes bajo Intenciones de Gateway Privilegiadas. Sin ello, el bot recibe mensajes con texto vacío y perderás una hora preguntándote por qué nunca responde.
- En OAuth2, genera un enlace de invitación con el ámbito
boty los permisos para leer mensajes y enviar mensajes. Abre el enlace y añade el bot a un servidor de prueba.
Instala las bibliotecas y establece dos variables de entorno.
pip install discord.py openai
export DISCORD_TOKEN="your-discord-bot-token"
export LLM_API_KEY="your-llm-api-key"
# quick check that the LLM key works, using model uncensored
curl -s https://api.codingllmapi.com/v1/chat/completions \
-H "Authorization: Bearer $LLM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"uncensored","messages":[{"role":"user","content":"ping"}],"max_tokens":5}'Paso 2: el código del bot
Guárdalo como bot.py. Responde cuando se le menciona, guarda doce mensajes de historial por canal, muestra el indicador de escritura mientras espera y divide las respuestas largas.
import asyncio
import os
import discord
from openai import AsyncOpenAI, RateLimitError, APIError
llm = AsyncOpenAI(
base_url="https://api.codingllmapi.com/v1",
api_key=os.environ["LLM_API_KEY"],
)
SYSTEM = (
"You are a helpful programming assistant in a Discord server. "
"Answer briefly. Put code in fenced blocks. Say so when you are unsure."
)
MAX_TURNS = 12
history = {} # channel id -> list of messages
slots = asyncio.Semaphore(6) # stay under the 8 concurrent request cap
intents = discord.Intents.default()
intents.message_content = True
bot = discord.Client(intents=intents)
def split_reply(text, limit=1900):
parts = []
while len(text) > limit:
cut = text.rfind("\n", 0, limit)
if cut < limit // 2:
cut = limit
parts.append(text[:cut])
text = text[cut:].lstrip("\n")
parts.append(text)
return parts
@bot.event
async def on_message(message):
if message.author.bot or bot.user not in message.mentions:
return
question = message.clean_content.replace(f"@{bot.user.display_name}", "").strip()
if not question:
await message.reply("Ask me a coding question after the mention.")
return
log = history.setdefault(message.channel.id, [])
log.append({"role": "user", "content": question})
del log[:-MAX_TURNS]
async with message.channel.typing():
try:
async with slots:
resp = await llm.chat.completions.create(
model="uncensored",
messages=[{"role": "system", "content": SYSTEM}] + log,
temperature=0.4,
max_tokens=900,
)
except RateLimitError:
await message.reply("The model is busy. Try again in a few seconds.")
log.pop()
return
except APIError:
await message.reply("The model call failed. Try again shortly.")
log.pop()
return
answer = resp.choices[0].message.content or "(empty reply)"
log.append({"role": "assistant", "content": answer})
for part in split_reply(answer):
await message.reply(part)
bot.run(os.environ["DISCORD_TOKEN"])
Ejecútalo con python bot.py, luego menciona al bot en tu servidor de prueba. La primera respuesta debería llegar en unos segundos.
Por qué se escribe el código así
El historial se recorta a propósito
El modelo no tiene memoria, así que cada llamada envía los turnos recientes. Sin del log[:-MAX_TURNS] la lista crecería para siempre. Eventualmente alcanzaría la ventana de 100.000 tokens y tus costos aumentarían con cada mensaje. Doce entradas son suficientes para una conversación sobre un error. Para un servidor con hilos largos, súbelo o resume los turnos antiguos.
El límite de 2.000 caracteres de Discord
Discord rechaza mensajes más largos de 2.000 caracteres, y las respuestas de código llegan a ese límite rápido. La función auxiliar split_reply corta en un salto de línea cerca del límite. No conoce las vallas de código, así que un bloque puede cortarse a la mitad. Si eso te molesta, añade lógica que cierre una valla al final de una parte y la reabra al inicio de la siguiente.
Un semáforo para la concurrencia
Una clave completa permite 8 peticiones simultáneas, y una clave de prueba permite 2. Un servidor ocupado puede generar varias preguntas a la vez. El semáforo encola llamadas adicionales dentro de tu proceso en lugar de enviarlas y recibir un 429 con el código de error concurrency. Si aún estás en la clave de prueba, cambia Semaphore(6) a Semaphore(2).
Los errores no se cobran
Las peticiones fallidas o rechazadas no se facturan, así que un reintento tras un error no duplica el costo. El código elimina el turno del usuario del historial en caso de fallo, para que un reintento no deje una pregunta colgante que el modelo ya haya visto dos veces.
Mejorar la sensación de las respuestas
Una vez que el bucle básico funciona, unos pequeños cambios lo mejoran mucho.
- Transmite la respuesta. Pasa
stream=Truey edita el mensaje del bot cada segundo más o menos con el texto acumulado. No edites en cada token, porque Discord aplica sus propios límites de peticiones a las ediciones de mensajes. Una petición transmitida mantiene su espacio de concurrencia hasta que termine, así que mantén el semáforo alrededor de toda la transmisión. - Usa comandos de barra. Un comando como
/askevita el análisis de menciones y no necesita la intención de contenido de mensajes en absoluto, lo que también hace que el bot sea más fácil de verificar más adelante si crece más allá de un pequeño número de servidores. - Historial por usuario. Organiza el historial por canal y usuario si varias personas hacen preguntas no relacionadas en un mismo canal.
- Ajusta el prompt del sistema. Nombra los idiomas que usa tu comunidad, pide respuestas cortas y dile al modelo que haga una pregunta de aclaración cuando el problema sea vago. Los cambios de prompt son gratuitos y a menudo superan a los cambios de modelo.
- Baja la temperatura. Para código, valores entre 0,2 y 0,4 dan resultados más estables que el valor predeterminado.
Solución de problemas de los fallos habituales
Cuando un bot nuevo permanece en silencio, la causa es casi siempre uno de los elementos siguientes. Revísalos en este orden.
- El bot está en línea pero nunca responde. La intención de contenido de mensajes está desactivada en el portal de desarrolladores, o falta
intents.message_content = Trueen el código. Ambos deben estar configurados. - Responde solo en algunos canales. El bot carece de permiso para leer o enviar mensajes en los demás. Revisa las anulaciones de permisos del canal, no solo el rol a nivel de servidor.
- Cada respuesta es un mensaje de "ocupado". Probablemente estés alcanzando un 429. Imprime el cuerpo de la excepción una vez. Si el código dice
rate_limited, has superado el límite por minuto de 300 peticiones con una clave completa o 60 con una clave de prueba. Si diceconcurrency, reduce el tamaño del semáforo. - Aparece un error de autenticación en la primera llamada. Falta la clave LLM del entorno del proceso que ejecuta el bot. Una clave exportada en una pestaña de terminal no es visible para un servicio iniciado en otro lugar.
- La respuesta está cortada. El
max_tokens=900en la muestra detiene las respuestas largas. Súbelo o elimínalo, ya quemax_tokenses opcional y la completación puede usar el resto de la ventana. - El bot responde a sus propios mensajes. Ese bucle ocurre si omites la comprobación
message.author.bot. Mantenla.
Para depurar, registra el valor de X-Request-Id para cada llamada. Con el SDK de OpenAI puedes leerlo de la respuesta sin procesar llamando a llm.chat.completions.with_raw_response.create(...) y luego leyendo .headers. Si contactas a soporte sobre un fallo específico, esa ID les permite encontrar la petición exacta.
Cuánto cuesta ejecutarlo
Los precios son $0,15 por 1M de tokens de entrada y $0,50 por 1M de tokens de salida, sin suscripción. Toma un ejemplo típico: el prompt del sistema más doce entradas de historial suman alrededor de 2.500 tokens de entrada, y la respuesta es de alrededor de 400 tokens.
Entrada: 2,500 x $0.15 / 1,000,000 = $0.000375. Salida: 400 x $0.50 / 1,000,000 = $0.0002. Una respuesta cuesta alrededor de $0.000575. Una comunidad que haga 500 preguntas en un día gastaría 500 x $0.000575 = $0.2875, un poco menos de treinta centavos. Tus números variarán con pegados de código más largos, así que registra resp.usage durante una semana y lee los promedios reales. La página de precios muestra las opciones de recarga: cualquier cantidad entera desde $20 hasta $500, con +5% desde $50, +10% desde $100 y +15% desde $200, pagando con tarjeta o cripto.
Para limitar el abuso, añade un tiempo de espera por usuario y un límite en la longitud de la pregunta. Esas dos comprobaciones protegen tu saldo mejor que cualquier otra cosa que puedas añadir.
Notas de alojamiento y seguridad
El bot es un proceso de ejecución prolongada. Una pequeña máquina virtual o un host de contenedores es suficiente, ya que el trabajo pesado ocurre en el lado de la API. Ejecútalo bajo un gestor de procesos para que se reinicie tras un fallo, y mantén ambos tokens en variables de entorno o en un almacén de secretos, nunca en el repositorio.
Piensa en lo que permite tu servidor. El modelo está ajustado para responder sin rechazos de contenido para uso adulto lícito, por lo que discutirá pruebas de seguridad o ficción arriesgada que un asistente más estricto podría rechazar. Si tu comunidad incluye miembros menores de edad, establece reglas en el prompt del sistema, restringe el bot a canales específicos y recuerda que los propios términos de Discord también se aplican a tu bot. El contenido sexual que involucre a menores está prohibido en esta API sin excepción.
Los detalles completos de los parámetros, como stop, seed y llamadas a funciones, están en la documentación de la API. Si quieres que el bot ejecute código o lea repositorios, las llamadas a funciones son el camino, y tú decides qué funciones puede solicitar el modelo. Para uso de este modelo enfocado en programación, consulta la página sobre el modelo de IA sin censura para programar.
Más guías
Preguntas y respuestas
¿Necesito la intención de contenido del mensaje?
Sí, si el bot lee mensajes normales y menciones. Si solo usas comandos slash, no la necesitas.
¿Puede el bot transmitir respuestas?
Sí. Establece stream=True y edita el mensaje de Discord a un intervalo tranquilo, como una vez por segundo, para que te mantengas dentro de los límites propios de Discord.
¿Qué sucede si demasiadas personas preguntan a la vez?
Tras 8 peticiones simultáneas con una clave completa, la API devuelve HTTP 429 con el código concurrency. Un semáforo en tu código encola las preguntas adicionales.
¿Recordará el bot las conversaciones después de un reinicio?
No con el diccionario utilizado aquí. Almacena el historial en SQLite o Redis si necesitas que sobreviva a los reinicios.
¿Es segura la clave de API en un bot de Discord?
Sí si se mantiene en tu servidor en una variable de entorno. Nunca la publiques en un canal ni la confirmes en un repositorio público.
Tu clave está a un formulario de distancia
Crea una cuenta, copia la clave, cambia la URL base. Esa es toda la configuración.
Obtener clave de API