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.)
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user