Un bot de Telegram puede enviar hasta 30 mensajes por segundo, según la documentación oficial de la plataforma, y la API de bots es gratuita. El gasto de un asistente conversacional casi nunca está ahí: está en los tokens que factura cada proveedor de modelos en la nube. Si el modelo corre en tu propia máquina, esa factura baja a cero, y el montaje se reduce a tres piezas: BotFather, Ollama y un script que las una. Recorremos el camino completo con DeepSeek y Gemma como candidatos.
Por qué ejecutar el modelo en tu servidor
Un bot conectado a una API comercial envía cada mensaje de tus usuarios a un tercero. Con un modelo local, ese eslabón desaparece: los mensajes siguen pasando por la nube de Telegram, algo inevitable si quieres usar la plataforma, pero el contenido no llega a ningún proveedor de IA.
Privacidad: los mensajes no salen de tu infraestructura
Con un modelo local el eslabón del tercero desaparece: los mensajes pasan por tu servidor y tu servidor únicamente. El registro de conversaciones queda bajo tu control, no en la política de retención de otra empresa.
Luego está el dinero. Las APIs de LLM facturan por token, y un bot con uso sostenido convierte cada conversación en gasto recurrente. Con Ollama pagas la RAM y la electricidad; el coste marginal por mensaje es cero.
El control completa el cuadro. DeepSeek publicó los pesos de R1 bajo licencia MIT, que permite ejecutarlos y modificarlos sin condiciones comerciales; Gemma 3 se distribuye bajo los términos de Gemma de Google, más restrictivos pero pensados también para despliegues propios. Un modelo que ya vive en tu disco no puede subir de precio, agotar su cuota ni cambiar de condiciones un lunes por la mañana.
Antes de escribir código, enumera los datos que manejará el bot. Si va a tocar documentación interna o conversaciones de clientes, montar bots de IA en Telegram con modelos locales pasa de capricho a requisito.
Las tres piezas del montaje
Primero, el bot. BotFather, el bot oficial de Telegram para crear y gestionar bots, te entrega un token tras tres mensajes; con ese token tu código habla con la API. En desarrollo conviene el long polling: tu script pregunta cada pocos segundos si hay mensajes nuevos y no necesitas exponer puertos al exterior. El webhook, que exige una URL pública con HTTPS, tiene sentido cuando el bot vive en un servidor ya accesible desde internet.
BotFather y el token del bot
BotFather entrega el token tras tres mensajes: /newbot, nombre y username. Con ese token habla tu código con la API de Telegram.
Ollama como servidor de modelos
Una instalación única y un pull del modelo deja Ollama sirviendo en el puerto 11434, sin configuración adicional para uso local.
El contenedor del bot y su red
El bot vive en su propio contenedor dentro de la misma red Docker que Ollama; solo necesita el token de Telegram y la URL interna del servidor de modelos.
El token merece el mismo trato que una contraseña: cualquiera que lo obtenga controla tu bot. Guárdalo en una variable de entorno o en un gestor de secretos, no en el repositorio.
Segundo, la librería que traduce los eventos de Telegram a Python. python-telegram-bot funciona sobre asyncio desde su versión 20, y aiogram es la alternativa habitual entre quienes programan en Python. El ejemplo de este artículo usa la primera.
Tercero, Ollama. Instala modelos cuantizados con ollama pull, los mantiene en RAM y expone una API REST en el puerto 11434. También ofrece un endpoint compatible con OpenAI bajo /v1, de modo que código escrito para el SDK de OpenAI funciona cambiando la URL base.
La prueba de que todo responde son dos comandos: ollama pull gemma3:4b y una petición a http://localhost:11434 con curl. Si devuelve JSON, la mitad del montaje está lista.
DeepSeek o Gemma: cómo elegir
DeepSeek-R1, publicado en enero de 2025, es un modelo de razonamiento: antes de responder genera una cadena de pensamiento que le da buenos resultados en matemáticas, lógica y código. El modelo completo es un MoE tan grande que no cabe en un equipo doméstico, así que lo práctico son sus versiones destiladas, que van de 1.500 millones a 70.000 millones de parámetros y se apoyan en Qwen y Llama. En la librería de Ollama eso se traduce en etiquetas como deepseek-r1:1.5b, deepseek-r1:8b o deepseek-r1:14b.
Razonamiento frente a respuesta rápida
DeepSeek-R1 genera cadena de pensamiento antes de responder: mejor en matemáticas y código, más lento por naturaleza. Gemma responde directo y pesa menos en RAM. La elección depende del uso, no del ranking.
Ventana de contexto y recorte del historial
La ventana por defecto de Ollama es modesta: guarda un historial por chat y recórtalo antes de que el modelo empiece a olvidar el inicio de la conversación.
Ten en cuenta el carácter de estos destilados: razonan antes de contestar, así que sus respuestas tienden a ser largas. Si el plan es charlar desde el navegador en lugar de Telegram, este chat privado con Open WebUI es la vía directa. En un chat de Telegram conviene fijar en el system prompt que quieres la conclusión, no el razonamiento completo.
Gemma 3, la generación actual de los modelos abiertos de Google, presentada en marzo de 2025, cubre otro perfil. Viene en tamaños de 1B, 4B, 12B y 27B; desde 4B acepta imágenes, y los tamaños de 4B hacia arriba manejan contextos de hasta 128.000 tokens. Escribe bien en español y su tono por defecto es más directo que el de un modelo de razonamiento.
En hardware, los cuantizados a 4 bits de los destilados de 7B u 8B pesan en torno a 5 GB y funcionan en portátiles con 16 GB de RAM, aunque con una GPU de consumo la latencia cae de forma notable. Un 27B de Gemma ya pide una GPU con VRAM de sobra o mucha paciencia en CPU.
La regla práctica: para un bot de soporte que redacta respuestas concisas, Gemma 3 en 4B o 12B va sobrado; para un asistente técnico que razona sobre problemas, un destilado de DeepSeek de 14B o 32B compensa su lentitud extra.
No elijas por la ficha técnica. Pasa por ambos modelos los diez mensajes reales más frecuentes de tu caso de uso y compara calidad de respuesta y segundos por contestación.
El bot mínimo viable en Python
Con las tres piezas instaladas, el bot cabe en unas sesenta líneas. Este ejemplo guarda un historial por chat y lo recorta para no desbordar la ventana de contexto:
Sesenta líneas que guardan y recortan historial
El bot completo cabe en unas sesenta líneas de Python: una librería de Telegram, un cliente HTTP contra Ollama y un dict para el historial por chat.
import ollama
from telegram import Update
from telegram.constants import ChatAction
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
MODELO = "gemma3:4b"
cliente = ollama.AsyncClient()
HISTORIAL: dict[int, list[dict]] = {}
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("Pregúntame lo que quieras.")
async def chat(update: Update, context: ContextTypes.DEFAULT_TYPE):
chat_id = update.effective_chat.id
await context.bot.send_chat_action(chat_id, ChatAction.TYPING)
hilo = HISTORIAL.setdefault(chat_id, [
{"role": "system", "content": "Responde en español, conciso y sin relleno."}
])
hilo.append({"role": "user", "content": update.message.text})
if len(hilo) > 21:
hilo[:] = [hilo[0]] + hilo[-20:] # conserva el system prompt y los últimos 20 turnos
respuesta = await cliente.chat(model=MODELO, messages=hilo)
texto = respuesta["message"]["content"]
hilo.append({"role": "assistant", "content": texto})
await update.message.reply_text(texto)
app = Application.builder().token("TU_TOKEN_DE_BOTFATHER").build()
app.add_handler(CommandHandler("start", start))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, chat))
app.run_polling()
Dos detalles importan más de lo que parece. La llamada al modelo usa await porque ollama expone un cliente asíncrono: si bloqueas el bucle de eventos, el bot ignora a los demás chats mientras genera una respuesta. Y el historial vive en un diccionario en memoria, suficiente para probar pero frágil en producción, porque un reinicio del proceso borra todas las conversaciones.
Si quieres que el bot escriba mientras piensa, el cliente de Ollama admite streaming por fragmentos y python-telegram-bot permite editar el mensaje con cada trozo recibido. En un grupo de trabajo, esa respuesta progresiva marca la diferencia con un chat que se queda en silencio durante veinte segundos.
Ejecuta python bot.py, envía un /start desde tu cuenta y comprueba que responde. El paso siguiente natural es sustituir run_polling por un webhook cuando el bot se mude a un servidor con HTTPS.
Ajustes que evitan sustos en producción
La ventana de contexto por defecto de Ollama es modesta, y un bot con historial largo la desborda: el modelo empieza a olvidar el principio de la conversación. Ajusta num_ctx, en el Modelfile o en la propia llamada, vigilando la RAM, porque más contexto significa más memoria.
num_ctx, keep_alive y límites por usuario
Sube num_ctx a 8192 si el historial crece, fija keep_alive para no recargar el modelo y limita mensajes por minuto por chat: tres ajustes que evitan los sustos típicos.
Ollama descarga el modelo de la memoria tras cinco minutos de inactividad, su valor por defecto de keep_alive. Con un bot de tráfico disperso, cada mensaje que llega tras una pausa espera a que el modelo se recargue; fija un keep_alive más alto si la RAM sobra.
Telegram no restringe quién habla con tu bot: cualquiera que lo encuentre puede escribirle. Filtra por una lista de chat_id permitidos antes de llamar al modelo y persiste los hilos en SQLite si deben sobrevivir a reinicios.
Respeta los límites de la API: un mensaje por segundo en cada chat y 30 por segundo en total, según la documentación de Telegram. Y captura los errores del modelo: un timeout de Ollama debe convertirse en un aviso al usuario, no en un bot que se queda callado.
Lista de comprobación antes de abrir el bot a más gente: num_ctx ajustado, keep_alive decidido, lista de chats permitidos activa, historial en disco y errores con respuesta amable.
Cuando el bot tiene que conocer tus documentos
Un modelo local solo sabe lo que trajo de su entrenamiento. En cuanto el bot debe responder sobre tu documentación, tu catálogo o tus procedimientos internos, necesitas RAG: recuperar los fragmentos pertinentes y entregarlos al modelo como contexto.
RAG: trocear, embeber y recuperar
Cuando el bot debe responder sobre tu documentación, entra RAG: trocea los documentos, almacena los embeddings en Qdrant y recupera los más cercanos antes de generar la respuesta.
Ollama también sirve para la parte de recuperación. Expone modelos de embeddings como nomic-embed-text, con los que puedes indexar documentos en una base vectorial y buscar por significado en lugar de por palabras. Tenemos un montaje de ese tipo aplicado a WordPress en la guía de búsqueda semántica con Qdrant y Ollama; la parte de Qdrant y Ollama aplica igual aunque tu bot no tenga nada que ver con WordPress.
El RAG trae puntos de fallo propios, y casi todos nacen de una indexación mal planteada: fragmentación demasiado agresiva, metadatos sucios o recuperaciones que devuelven texto sin relación con la pregunta. En tres errores comunes al montar RAG en WordPress desmontamos los deslices más frecuentes, y los mismos patrones se repiten en cualquier proyecto de RAG.
Empieza con un corpus pequeño: diez documentos reales, indexados y verificados a mano. Si la recuperación funciona ahí, escalará; si no, el problema no era el tamaño.
Para terminar: un plan para esta semana
Los bots de IA en Telegram con modelos locales no eliminan la complejidad, la reubican: desaparece la factura por tokens y aparece una infraestructura que mantener, con su RAM, su ventana de contexto y sus reinicios. El primer despliegue útil suele ser un bot dedicado a una sola tarea con un modelo mediano, no un asistente universal.
Plan concreto para esta semana: instala Ollama, descarga gemma3:4b, levanta el script de este artículo y dale acceso a dos o tres compañeros. Con su uso real durante unos días sabrás si toca subir de modelo, añadir RAG o dejarlo como herramienta de nicho en un canal interno.