IA-Ingeniería-MLOps · llamafile-llm-ejecutable-unico

LLM On-Premise con Llamafile

Despliegue de Mistral-7B como ejecutable autocontenido para LegalSafe AI — sin Docker, sin Python, sin internet.

Mistral-7B-Instruct Q5_K_M
🖥️Ubuntu 22.04 · 8 GB RAM · CPU only
🔒Air-gapped · RGPD art.9
🔗API compatible OpenAI
Cliente

LegalSafe AI

SaaS B2B de asistencia legal para pymes españolas · 50 clientes corporativos

🔒 RGPD Art.9 — datos sensibles Sin Docker Sin internet en producción FastAPI + Python 3.11 Ubuntu 22.04 ≥8 GB RAM Respuesta < 30s
🚨
Problema actual

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).

Solución con Llamafile

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.

Arquitectura de despliegue
👨‍⚖️
Abogado / Usuario
Navegador corporativo
HTTPS
🐍
FastAPI Backend
legalsafe-api · :8000
localhost:8080/v1
🦙
legalsafe.llamafile
Mistral-7B Q5_K_M + runtime
inferencia
💾
RAM / CPU
~5.2 GB · 8 threads
Todo dentro de la red del cliente — cero tráfico externo
Paso 1 — Empaquetar el llamafile personalizado
bash
# 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
Paso 2 — Integración con FastAPI (LlamafileServer)
python
# 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}
Comparativa de cuantizaciones recomendadas
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
Rendimiento estimado en servidor LegalSafe (8 cores, 8 GB RAM, CPU)
Arranque
~8 s
1ª respuesta
~18 s
RAM usada
6.5 GB
CPU carga
~75%
✓ Respuesta típica: 12–22 s · SLA <30 s CUMPLIDO
Checklist de despliegue air-gapped

Preparación (red con internet — 1 sola vez)

  • Descargar legalsafe.llamafile en máquina con internet SHA-256 verificado contra HuggingFace
  • Transferir via USB cifrado o SCP interno al servidor del cliente No usar herramientas de transferencia en la nube
  • Marcar como ejecutable: chmod +x legalsafe.llamafile En macOS: eliminar quarantine flag si aplica
  • Configurar servicio systemd para autoarranque Reinicio automático en caso de caída

Validación post-despliegue

  • Health check: curl localhost:8080/health Responde {"status":"ok"} en <1s
  • Test consulta legal de prueba (pregunta fija) Verificar respuesta coherente <30s
  • Confirmar con tcpdump que no sale tráfico externo Monitorización 24h antes de producción
  • Revisar logs de FastAPI — sin errores de timeout Ajustar --threads si respuesta >25s
Servicio systemd — autoarranque en Linux
ini · /etc/systemd/system/legalsafe-llm.service
[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
bash
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
Resultado de una consulta real
💬Consulta de prueba — RGPD y datos de empleados

Prompt enviado

"¿Puede una empresa española enviar datos de empleados a un proveedor SaaS en EEUU sin consentimiento explícito bajo el RGPD?"

Respuesta del LLM local

"No sin garantías adecuadas. Según el art. 46 del RGPD, las transferencias internacionales a países sin decisión de adecuación (EEUU tras Schrems II) requieren Cláusulas Contractuales Tipo (CCT) o normas corporativas vinculantes. El consentimiento explícito es válido pero no recomendable como base única para datos laborales. Se recomienda consultar con un DPO certificado."

⏱ Latencia: 16.4s · Tokens: 284 · Modelo: Mistral-7B local · Datos enviados fuera: 0 bytes
Por qué llamafile frente a alternativas
📦vs Ollama

Ollama requiere daemon instalado y privilegios de administrador. Llamafile es un solo archivo: copia + ejecuta. Sin instalador.

🐳vs Docker

Docker está prohibido en clientes IT corporativos de LegalSafe. Llamafile no necesita runtime externo ni privilegios root.

☁️vs OpenAI API

OpenAI envía datos a servidores externos (RGPD inviable). Llamafile es 100% local. Misma API, sin cambios de código.