# LlamaLink — Especificación Técnica (Living Document) > Este documento se actualiza conforme evoluciona la implementación. Última actualización: implementación inicial. ## 1. Descripción General **LlamaLink** es un servidor middleware/gateway que permite ejecutar modelos de lenguaje (LLMs) de forma local usando **llama.cpp**, exponiéndolos de manera segura a través de una API REST propia protegida con **API keys**, permitiendo así el acceso remoto controlado a la inferencia. ### Objetivos principales - Ejecutar modelos GGUF localmente mediante llama.cpp. - Exponer un endpoint HTTP compatible con el estándar OpenAI (`/v1/chat/completions`). - Autenticar y autorizar clientes remotos mediante API keys. - Controlar uso, límites y seguridad del acceso a los modelos. --- ## 2. Arquitectura ``` Cliente remoto (con API key) │ ▼ ┌─────────────────────┐ │ LlamaLink API │ ← Gateway (FastAPI) │ - Autenticación │ │ - Rate limiting │ │ - Enrutamiento │ │ - Logs y métricas │ └──────────┬───────────┘ │ (proxy HTTP) ▼ ┌─────────────────────┐ │ llama.cpp │ ← Motor de inferencia │ (llama-server) │ └─────────────────────┘ ``` ### Integración con llama.cpp - **Opción A** (implementada): LlamaLink actúa como gateway/proxy delante del servidor HTTP nativo de llama.cpp (`llama-server`). - **Opción B**: Uso de `llama-cpp-python` embebido (futuro). ### Multi-modelo - Se soporta la configuración de múltiples modelos en la base de datos. - Solo **un modelo** está activo/cargado en llama-server en un momento dado. - Si llega una request para un modelo diferente al activo: 1. Se rechaza con HTTP 503 + `Retry-After` mientras dura el swap 2. El admin puede disparar el swap manualmente vía `POST /v1/models/{name}/load` 3. Ocurre automáticamente en lazy mode si `MANAGE_LLAMA_SERVER=true` - El swap es secuencial (un `asyncio.Lock` evita swaps concurrentes). --- ## 3. Stack Tecnológico | Componente | Tecnología | Justificación | |---|---|---| | Motor de inferencia | **llama.cpp** (C++) | Ya compilado y optimizado, no requiere reescritura | | API Gateway | **Python 3.11+ / FastAPI** | Desarrollo rápido, ecosistema maduro, buena documentación automática (OpenAPI/Swagger) | | Servidor ASGI | **Uvicorn** | Estándar para FastAPI, soporta async y alto rendimiento | | Base de datos | **SQLite** (MVP) → **PostgreSQL** (producción) | Escalable según necesidad | | ORM | **SQLAlchemy 2.0** (async) | Manejo de modelos y migraciones | | Autenticación | API Keys con hash **SHA-256** | Nunca se almacenan en texto plano | | Rate limiting | **slowapi** (memoria o Redis) | Control de abuso por IP | | Reverse proxy / TLS | **Nginx** o **Caddy** | HTTPS obligatorio en producción | | Contenedores | **Docker + docker-compose** | Despliegue reproducible | --- ## 4. Estructura de Carpetas ``` llamalink/ ├── app/ │ ├── main.py # Punto de entrada FastAPI + lifespan │ ├── config.py # Configuración pydantic-settings │ ├── __init__.py │ ├── core/ │ │ ├── __init__.py │ │ ├── security.py # Hash/verify API keys (SHA-256) │ │ ├── errors.py # JSON error handlers estandarizados │ │ └── logging.py # Structured logging │ ├── db/ │ │ ├── __init__.py │ │ ├── base.py # SQLAlchemy declarative base │ │ ├── session.py # Engine + session factory │ │ └── models.py # ORM models │ ├── schemas/ │ │ ├── __init__.py │ │ ├── common.py # ErrorResponse, HealthResponse │ │ ├── chat.py # OpenAI-compat request/response │ │ └── keys.py # Key management schemas │ ├── auth/ │ │ ├── __init__.py │ │ ├── service.py # CRUD keys, hashing │ │ └── dependencies.py # Bearer auth FastAPI deps │ ├── routers/ │ │ ├── __init__.py │ │ ├── chat.py # /v1/chat/completions │ │ ├── keys.py # /v1/keys CRUD │ │ └── health.py # /health, /ready │ └── services/ │ ├── __init__.py │ ├── llama_proxy.py # httpx async client → llama-server │ ├── rate_limiter.py # slowapi setup │ ├── usage_tracker.py # Token counting + DB logs │ ├── quota.py # Chequeo de cuota mensual │ ├── webhook.py # Notificaciones al superar cuota │ ├── model_manager.py # Subprocess lifecycle + state machine │ └── models_registry.py # Mapa de modelos configurados ├── migrations/ # Alembic ├── deploy/ # Nginx, Caddy, systemd units ├── docker/ # Dockerfiles ├── scripts/ # CLI tools ├── tests/ ├── pyproject.toml ├── .env.example └── README.md ``` --- ## 5. Endpoints Implementados | Método | Endpoint | Descripción | Auth | |---|---|---|---| | POST | `/v1/chat/completions` | Enviar prompt y recibir respuesta del modelo | Sí | | POST | `/v1/keys` | Crear nueva API key | Sí (admin) | | GET | `/v1/keys` | Listar API keys existentes | Sí (admin) | | DELETE | `/v1/keys/{key_id}` | Revocar una API key | Sí (admin) | | GET | `/health` | Verificar estado del servicio | No | | GET | `/ready` | Verificar listo para servir | No | ### Pendiente de implementar (Fase 4+) | Método | Endpoint | Descripción | |---|---|---| | GET | `/v1/models` | Listar modelos configurados | | GET | `/v1/models/active` | Mostrar cuál está cargado | | POST | `/v1/models/{name}/load` | Cargar/swap a un modelo | | GET | `/v1/usage/{key_id}` | Métricas de uso de una key | | GET/DELETE | `/admin/*` | Panel web de administración | --- ## 6. Variables de Entorno ```env LLAMALINK_ENV=development LLAMALINK_HOST=0.0.0.0 LLAMALINK_PORT=8000 LLAMALINK_SECRET_KEY=changeme DATABASE_URL=sqlite+aiosqlite:///./llamalink.db MANAGE_LLAMA_SERVER=true LLAMA_SERVER_BIN=llama-server LLAMA_SERVER_HOST=127.0.0.1 LLAMA_SERVER_PORT=8080 LLAMA_SERVER_STARTUP_TIMEOUT=120 MODEL_SWAP_COOLDOWN=2 LLAMA_SERVER_STOP_TIMEOUT=10 RATE_LIMIT_PER_MINUTE=60 RATE_LIMIT_STORAGE=memory LOG_LEVEL=INFO LOG_FORMAT=json ADMIN_API_KEY=changeme ``` --- ## 7. Modelo de Datos ### ApiKey - `id`, `name`, `hashed_key`, `key_prefix` - `scopes`: CSV de permisos (`chat,models,usage,admin`) - `is_active`, `is_admin`, `created_at`, `last_used_at`, `owner_label` ### Model - `id`, `name`, `model_path`, `alias` - `ctx_size`, `n_gpu_layers`, `extra_args` (JSON) - `is_default`, `is_enabled`, `is_active`, `loaded_at` ### UsageLog - `api_key_id`, `model_name`, `endpoint` - `prompt_tokens`, `completion_tokens`, `total_tokens` - `latency_ms`, `status`, `ip_address`, `user_agent` - `error_message`, `created_at` ### Quota - `api_key_id`, `model_scope` (None = global) - `period_start`, `period_end` - `tokens_limit`, `tokens_used` ### Webhook - `api_key_id`, `url`, `event`, `secret` (HMAC) - `is_active`, `created_at` --- ## 8. Estados del ModelManager ``` STOPPED ──load()──> LOADING ──success──> READY ↑ │ │ fail │ ↓ └──<─────── FAILED FAILED │ └──swap()──> SWAPPING ──success──> READY ``` --- ## 9. Formato de Errores Todos los errores siguen este formato: ```json { "error": { "code": "error_code_string", "message": "Descripción legible", "retry_after_seconds": 30 } } ``` Códigos: `invalid_api_key`, `api_key_revoked`, `rate_limit_exceeded`, `model_not_found`, `model_loading`, `quota_exceeded`, `upstream_error`, `internal_error`, `validation_error`, `unauthorized` --- ## 10. Roadmap de Implementación ### ✅ Fase 0 — Esqueleto - pyproject.toml, config, main, db, .env.example, .gitignore ### ✅ Fase 1 — MVP - Auth (hash, service, deps) - llama_proxy (httpx async, streaming) - /v1/chat/completions (non-stream + stream) - /v1/keys CRUD - /health, /ready - Error handlers estandarizados - Quota service (stub) - Rate limiter (stub) ### 🔄 Fase 2 — Endurecimiento - [ ] Rate limiting (slowapi) - [ ] Usage tracker middleware - [ ] Logging estructurado JSON ### 📋 Fase 3 — Docker + Producción - [ ] docker-compose.yml - [ ] Dockerfile.api, Dockerfile.llama - [ ] Nginx.conf, Caddyfile - [ ] systemd units ### 📋 Fase 4 — Multi-modelo + Streaming - [ ] model_manager (subprocess lifecycle, state machine) - [ ] streaming en /v1/chat/completions - [ ] /v1/models CRUD - [ ] Lazy swap + admin trigger - [ ] 503 + Retry-After durante swap ### 📋 Fase 5 — Cuotas + Webhooks - [ ] Quota enforcement middleware - [ ] Webhook delivery con HMAC - [ ] Background task para notificaciones ### 📋 Fase 6 — Panel Admin Web - [ ] Jinja2 templates + htmx - [ ] /admin/* routes - [ ] Crear/revocar keys desde UI - [ ] Ver métricas y logs ### 📋 Fase 7 — Polish - [ ] Validación estricta de inputs - [ ] Auto-revoke en actividad sospechosa - [ ] Cobertura de tests ≥ 80% - [ ] ruff + mypy limpios