Despliegue de Mistral-7B como ejecutable autocontenido para LegalSafe AI — sin Docker, sin Python, sin internet.
SaaS B2B de asistencia legal para pymes españolas · 50 clientes corporativos
La API de OpenAI envía consultas legales a servidores externos. Clientes del sector salud y finanzas rechazan el servicio por RGPD. Riesgo de multa: hasta 4% facturación global (art. 83 RGPD).
Un único archivo ejecutable de ~5 GB que el cliente descarga una sola vez. Doble clic = servidor LLM local con API OpenAI-compatible. Cero dependencias. Cero datos salen del servidor.
# 1. Descargar el runtime llamafile (solo hace falta una vez) wget https://github.com/Mozilla-Ocho/llamafile/releases/latest/download/llamafile chmod +x llamafile # 2. Descargar Mistral-7B-Instruct Q5_K_M desde HuggingFace (~4.8 GB) wget https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF/resolve/main/mistral-7b-instruct-v0.2.Q5_K_M.gguf # 3. Empaquetar con system prompt legal personalizado ./llamafile \ --model mistral-7b-instruct-v0.2.Q5_K_M.gguf \ --system-prompt "Eres un asistente legal experto en derecho español (mercantil, laboral y RGPD). Responde de forma concisa, cita artículos cuando sea relevante y siempre indica que el usuario debe consultar a un abogado para decisiones definitivas." \ --create legalsafe.llamafile # 4. Resultado: un único binario de ~5 GB ls -lh legalsafe.llamafile # → -rwxr-xr-x 1 ubuntu ubuntu 4.8G jun 14 10:22 legalsafe.llamafile # 5. Arrancar el servidor en el servidor del cliente (sin internet) ./legalsafe.llamafile \ --host 127.0.0.1 \ --port 8080 \ --ctx-size 4096 \ --threads 8 \ --gpu none # forzar CPU (política IT sin GPU) # Listo en ~8s. UI en http://localhost:8080 · API en http://localhost:8080/v1
# legalsafe_ai/llm/llamafile_client.py import subprocess, requests, time, logging from contextlib import asynccontextmanager from fastapi import FastAPI class LlamafileServer: """Gestiona legalsafe.llamafile como subprocess del backend FastAPI.""" def __init__(self, model_path: str = "./legalsafe.llamafile", port: int = 8080): self.model_path = model_path self.port = port self.process = None self.base_url = f"http://127.0.0.1:{port}" def start(self): logging.info("Arrancando legalsafe.llamafile en puerto %d...", self.port) self.process = subprocess.Popen( [self.model_path, "--host", "127.0.0.1", "--port", str(self.port), "--ctx-size", "4096", "--threads", "8", "--gpu", "none"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) for attempt in range(30): try: requests.get(f"{self.base_url}/health", timeout=2) logging.info("llamafile listo tras %ds", attempt) return except Exception: time.sleep(1) raise RuntimeError("llamafile no arrancó en 30s") def chat(self, prompt: str, max_tokens: int = 512) -> str: r = requests.post( f"{self.base_url}/v1/chat/completions", json={ "model": "local", "messages": [{"role": "user", "content": prompt}], "max_tokens": max_tokens, "temperature": 0.2, # baja temperatura para respuestas legales precisas }, timeout=28 # SLA: respuesta < 30s ) r.raise_for_status() return r.json()["choices"][0]["message"]["content"] def stop(self): if self.process: self.process.terminate(); self.process.wait() # Lifespan de FastAPI para gestionar el ciclo de vida del LLM llm = LlamafileServer() @asynccontextmanager async def lifespan(app: FastAPI): llm.start() yield llm.stop() app = FastAPI(lifespan=lifespan) @app.post("/api/consulta-legal") async def consulta_legal(pregunta: str): respuesta = llm.chat(pregunta) return {"respuesta": respuesta, "modelo": "Mistral-7B local", "externo": False}
| Cuantización | Tamaño archivo | RAM necesaria | Tokens/s (CPU 8t) | Calidad | Para LegalSafe |
|---|---|---|---|---|---|
Q8_0 |
7.7 GB | ~9.5 GB | ~6 tok/s | ≈ FP16 (máxima) | RAM insuficiente |
Q5_K_M |
4.8 GB | ~6.5 GB | ~10 tok/s | Excelente (~99%) | ✓ Recomendada |
Q4_K_M |
4.1 GB | ~5.5 GB | ~13 tok/s | Buena (~97%) | Alternativa válida |
Q3_K_M |
3.3 GB | ~4.5 GB | ~17 tok/s | Aceptable (~94%) | Solo si RAM < 6 GB |
Preparación (red con internet — 1 sola vez)
legalsafe.llamafile en máquina con internet
SHA-256 verificado contra HuggingFace
chmod +x legalsafe.llamafile
En macOS: eliminar quarantine flag si aplica
Validación post-despliegue
curl localhost:8080/health
Responde {"status":"ok"} en <1s
tcpdump que no sale tráfico externo
Monitorización 24h antes de producción
[Unit] Description=LegalSafe AI — Llamafile LLM Server (Mistral-7B) After=network.target [Service] User=legalsafe WorkingDirectory=/opt/legalsafe ExecStart=/opt/legalsafe/legalsafe.llamafile --host 127.0.0.1 --port 8080 --ctx-size 4096 --threads 8 --gpu none Restart=on-failure RestartSec=5 StandardOutput=journal StandardError=journal # Restricciones de seguridad adicionales NoNewPrivileges=yes PrivateTmp=yes ProtectSystem=strict ReadWritePaths=/opt/legalsafe [Install] WantedBy=multi-user.target
sudo systemctl daemon-reload sudo systemctl enable legalsafe-llm.service sudo systemctl start legalsafe-llm.service sudo systemctl status legalsafe-llm.service # ● legalsafe-llm.service - LegalSafe AI — Llamafile LLM Server (Mistral-7B) # Loaded: loaded (/etc/systemd/system/legalsafe-llm.service; enabled) # Active: active (running) since Sat 2026-06-14 10:22:14 CEST; 2min ago
Prompt enviado
Respuesta del LLM local
Ollama requiere daemon instalado y privilegios de administrador. Llamafile es un solo archivo: copia + ejecuta. Sin instalador.
Docker está prohibido en clientes IT corporativos de LegalSafe. Llamafile no necesita runtime externo ni privilegios root.
OpenAI envía datos a servidores externos (RGPD inviable). Llamafile es 100% local. Misma API, sin cambios de código.