Saltar al contenido
# 🚦 Rate Limiting + Quotas ## 🤔 ¿Qué hago? ¿Cómo lo hago? ¿Y para qué lo hago? **¿Qué hago:** Klaus-proxy implementa un sistema de rate limiting por API key con quotas configurables en tres ventanas: por minuto, por hora y por día. Cuando un tenant excede su cuota, la plataforma retorna un HTTP **429 Too Many Requests** con detalles de uso actual. **¿Cómo lo hago:** - **RateLimitStore**: Abstracción base para almacenar contadores (implementación: MemoryRateLimitStore) - **QuotaManager**: Orquesta las verificaciones de cuota y registro de uso - **Middleware en /v1/messages**: Intercepta requests antes del procesamiento, verifica cuota, rechaza si se excedió - **Endpoints de introspección**: `/quotas/usage` (ver uso actual) y `/quotas/reset` (resetear manual) **¿Para qué lo hago:** - Prevenir abuso de recursos en entornos multi-tenant - Garantizar fair-share entre clientes - Proteger backends upstream (Anthropic, Qdrant) de bombardeos - Proporcionar observabilidad de consumo por tenant --- ## 🏗️ Arquitectura ```mermaid graph TD A["🔐 Client Request<br/>Header: x-api-key"] --> B["FastAPI Middleware"] B --> C["QuotaManager.check_quota"] C --> D{"Quota<br/>Exceeded?"} D -->|Yes| E["429 Too Many Requests"] D -->|No| F["Process Request"] F --> G["QuotaManager.record_usage"] G --> H["Return Response"] E --> I["X-RateLimit-Reset header"] H --> J["X-Cache + X-Provider headers"] K["RateLimitStore<br/>per_minute, per_hour, per_day"] --> C K --> G L["/quotas/usage<br/>GET"] --> M["Return current usage"] N["/quotas/reset<br/>POST"] --> O["Manual reset"] ``` --- ## 📊 Límites por defecto ```python DEFAULT_LIMITS = { "per_minute": 60, # 60 requests/minuto "per_hour": 1000, # 1000 requests/hora "per_day": 10000, # 10000 requests/día } ``` **Configuración personalizada:** Crear `QuotaManager(limits={...})` en main.py. --- ## 🔌 API Endpoints ### GET /quotas/usage Obtiene el uso actual del tenant. **Request:** ```bash curl -H "x-api-key: my-api-key" http://localhost:8000/quotas/usage ``` **Response:** ```json { "api_key": "my-api-key", "usage": { "per_minute": 45, "per_hour": 892, "per_day": 5234 }, "limits": { "per_minute": 60, "per_hour": 1000, "per_day": 10000 }, "timestamp": "2026-09-10T10:30:45.123456+00:00" } ``` ### POST /quotas/reset Resetea la cuota de un tenant. **Request:** ```bash # Resetear solo per_minute curl -X POST \ -H "x-api-key: my-api-key" \ -H "Content-Type: application/json" \ -d '{"window": "per_minute"}' \ http://localhost:8000/quotas/reset # Resetear todas las ventanas curl -X POST \ -H "x-api-key: my-api-key" \ -H "Content-Type: application/json" \ -d '{}' \ http://localhost:8000/quotas/reset ``` **Response:** ```json { "status": "reset", "api_key": "my-api-key", "window": "per_minute" } ``` --- ## ⚙️ Integración en endpoints protegidos El rate limiting se aplica **automáticamente** en estos endpoints: ### Endpoints Message - `/v1/messages` — Llamadas a Anthropic API ### Endpoints Deferred (alto consumo de recursos) - `/knowledge/ingest/file` — Ingest de archivos locales - `/sources/ingest/web` — Web crawling con Crawl4AI - `/sources/ingest/pdf` — Extracción de PDF con OCR **Flujo en cada endpoint:** 1. **Extrae x-api-key** del header (default: "default-tenant") 2. **Verifica cuota** vía `QuotaManager.check_quota(api_key)` 3. **Si excedida:** Retorna 429 + X-RateLimit-Reset header 4. **Si válida:** Continúa procesamiento normal 5. **Al finalizar:** Registra uso vía `QuotaManager.record_usage(api_key)` --- ## 💻 Ejemplo de integración ```python from proxy.rate_limit import get_quota_manager, MemoryRateLimitStore # En main.py lifespan (opcional — se inicializa lazy) # quota_mgr = get_quota_manager() # En endpoint /v1/messages api_key = incoming_headers.get("x-api-key", "default-tenant") quota_mgr = get_quota_manager() # Check antes de procesar quota_check = await quota_mgr.check_quota(api_key) if not quota_check["allowed"]: return JSONResponse( status_code=429, content={"error": quota_check["reason"], "usage": quota_check["usage"]}, headers={"X-RateLimit-Reset": str(reset_at)}, ) # ... procesar request ... # Record al finalizar await quota_mgr.record_usage(api_key) ``` --- ## 🧪 Tests **24 tests** cobriendo: - ✅ Incrementos de contador - ✅ Verificación de límites (per_minute, per_hour, per_day) - ✅ Aislamiento entre API keys - ✅ Aislamiento entre ventanas - ✅ Reset específico y total - ✅ Acceso concurrente - ✅ Límites personalizados - ✅ Edge cases (claves vacías, reset no-existen, etc.) **Ejecutar tests:** ```bash pytest tests/test_rate_limit.py -v ``` --- ## 🔮 Extensiones futuras 1. **Redis backend:** Implementar `RedisRateLimitStore` para multi-proceso 2. **Quotas por recurso:** Diferenciar límites por endpoint (e.g., /v1/messages vs /knowledge/ingest) 3. **Tiered pricing:** Límites variables según plan de suscripción 4. **Metrics export:** Exponer contadores a Prometheus para alertas 5. **Admin panel:** Dashboard para revisar/resetear quotas --- ## 🔐 Notas de seguridad - **x-api-key no es autenticación:** Solo identificador de tenant. Usar autenticación fuerte en /knowledge/ingest. - **Default tenant fallback:** Si no se envía x-api-key, se usa "default-tenant" compartido — configurar si es multi-tenant. - **Clock skew:** Las ventanas de tiempo se resetan basándose en `time.time()` — reloj del servidor debe estar sincronizado. - **Memory store para desarrollo:** MemoryRateLimitStore es suficiente para single-process. Para producción multi-proceso, usar Redis.