
Proyecto IA Monterroso (XI): un límite de peticiones, para cerrar la robustez mínima
En la entrada anterior añadimos un filtro de entrada básico. Hoy toca la segunda mitad de esa misma tarea: un límite de peticiones por minuto (lo que se suele llamar rate limiting). Con esto se cierra el bloque de “robustez mínima” que nos habíamos marcado antes de pensar siquiera en abrir MonteIA más allá de un único ordenador.
Qué vamos a hacer
Limitar cuántas veces se puede llamar a /preguntar por minuto desde la misma dirección IP, usando la librería slowapi (una integración de rate limiting para FastAPI). Si se supera el límite, la API responde con un error controlado en vez de seguir aceptando peticiones sin freno.
Por qué esto es necesario, no solo “bonito de tener”
Cada pregunta que llega a MonteIA es cara: en ROCKY, sin GPU, generar una respuesta tarda mucho más que en un servicio comercial con hardware dedicado. Sin ningún límite, cualquiera de estas situaciones puede dejar la API bloqueada para todo el mundo:
- Un fallo en el frontend que reenvía la misma pregunta en bucle.
- Una pestaña del navegador olvidada recargando sola.
- Varias personas preguntando a la vez sin que nada las coordine.
Ninguna de estas requiere mala intención. Es sencillamente lo que pasa cuando una API no tiene ningún tope, y es exactamente el tipo de problema que hay que resolver antes de abrir el acceso a más de un ordenador (que es a donde apunta el bloque siguiente del proyecto), no después de que ya haya dado problemas.
Cómo funciona
slowapi asocia un contador de peticiones a cada dirección IP y decide, en cada llamada, si esa IP sigue dentro de su cuota. El límite elegido es 20 peticiones por minuto por IP:
- Generoso para una persona preguntando con normalidad — incluso escribiendo rápido, es difícil acercarse a esa cifra.
- Bajo para un bucle descontrolado o un abuso deliberado.
Es un primer número razonable, no uno medido con tráfico real de un instituto entero; si con el uso normal resulta molesto o insuficiente, se cambia en una sola línea.
Cuando se supera el límite, la API devuelve un error 429 (“demasiadas peticiones”) con un mensaje amable, y la interfaz lo muestra igual que cualquier otro error — de nuevo, sin haber tenido que tocar el frontend, porque el manejo de errores ya estaba preparado desde el streaming.
Instalación
Hace falta una librería nueva:
pip install slowapi
Y añadirla a requirements.txt:
fastapi
uvicorn[standard]
requests
pypdf
rank-bm25
slowapi
Código
Cambios en ia-monterroso-api/main.py. En las importaciones de arriba:
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse
from pydantic import BaseModel
import requests
from slowapi import Limiter
from slowapi.errors import RateLimitExceeded
from slowapi.util import get_remote_address
Justo después de crear la app:
app = FastAPI(title="MonteIA - API")
# --- Límite de peticiones (rate limiting) ---
#
# Segunda pieza de la robustez mínima antes de plantearse abrir MonteIA a
# la red del centro (la primera fue el filtro de entrada). Sin esto, un
# bucle por error en el frontend, una pestaña olvidada recargando sola, o
# varias personas preguntando a la vez, pueden encadenar peticiones al
# modelo sin ningún freno — y cada petición es cara: en ROCKY, sin GPU,
# genera texto mucho más despacio que un servicio comercial. Un límite por
# IP evita que eso colapse la API o deje a todo el mundo esperando.
#
# 20 peticiones por minuto es un límite generoso para una persona
# preguntando con normalidad (incluso escribiendo rápido, es raro superar
# eso) y bajo para un bucle descontrolado. Es un primer número razonable,
# no uno medido con tráfico real; si en el uso normal resulta molesto o
# insuficiente, se ajusta aquí, en un solo sitio.
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
@app.exception_handler(RateLimitExceeded)
def manejador_limite_peticiones(request: Request, exc: RateLimitExceeded):
return JSONResponse(
status_code=429,
content={
"detail": "Demasiadas preguntas seguidas. Espera un momento y vuelve a intentarlo."
},
)
Y el endpoint se decora con el límite, añadiendo request: Request como parámetro (lo necesita slowapi para identificar de dónde viene la petición):
@app.post("/preguntar")
@limiter.limit("20/minute")
def preguntar(request: Request, datos: PeticionChat):
if not datos.mensajes:
raise HTTPException(status_code=400, detail="No se ha enviado ningún mensaje.")
# ... el resto de la función sigue exactamente igual que antes
Cómo se comprobó
Antes de instalarlo, se probó con un cliente de pruebas que dispara 25 peticiones seguidas contra la API (sin depender de que LM Studio esté encendido, para aislar solo el comportamiento del límite): las primeras 20 llegan hasta el punto donde intentan hablar con el modelo, y de la 21 en adelante la API responde directamente con el error 429, sin gastar ningún recurso extra. También se comprobó que el filtro de entrada de la entrada anterior sigue funcionando exactamente igual — las dos protecciones conviven sin pisarse.
Ya en la interfaz real:
- Preguntar con normalidad → debe responder igual que siempre, sin notarse nada distinto.
- Enviar muchas preguntas muy rápido, una detrás de otra → a partir de la número 21 en el mismo minuto, debe aparecer una burbuja de error con “Demasiadas preguntas seguidas. Espera un momento y vuelve a intentarlo.”
Solo hace falta instalar slowapi y reiniciar la API para que el cambio esté activo.
Qué queda pendiente
Con esto se cierra el bloque de robustez mínima (filtro de entrada + límite de peticiones) que nos habíamos propuesto antes de abrir MonteIA a la red del centro. El siguiente paso natural, cuando toque, es precisamente eso: que la API escuche en la red local (0.0.0.0 en vez de solo localhost) para que otros ordenadores o móviles del centro puedan hablar con MonteIA — pero eso ya es una decisión más grande, que hay que tomar con calma y no en la misma tarde que el resto de esta entrada.

Etiqueta:AIDARAC, fastapi, ia monterroso, MonteIA, montesteam, rate limiting, robustez, slowapi

