Files
llama-link/SPEC.md
T
darroyo 4c9ed3c24b Initial commit: LlamaLink Go rewrite
Complete rewrite from Python/FastAPI to Go/Gin:
- Go backend: auth (API keys + bcrypt), llama.cpp subprocess manager,
  hot-swap multi-model, rate limiting, quota system, webhooks
- Vue 3 SPA admin panel (src/) with Tailwind CSS
- Deployment: Docker multi-stage, docker-compose, nginx, systemd
- GORM/SQLite models: ApiKey, Model, UsageLog, Quota, Webhook
- REST API: /api/v1/admin/* (keys, models, chat, usage, health)
- Embedded frontend via go:embed (build output at web/dist/)

Removed legacy Python artifacts (app/, tests/, pyproject.toml, etc.)
2026-07-30 10:58:55 -04:00

286 lines
9.4 KiB
Markdown

# 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