Ir al contenido principal

Tutorial: Servidor IA con Sanic

Tutorial: Servidor IA con Sanic (normalizador)

Guía completa para crear, ejecutar y desplegar un microservicio IA construido con Sanic y Google Generative AI. Código listo para copiar/pegar.


1. Crear entorno y proyecto

mkdir normalizador
cd normalizador
python3 -m venv venv
source venv/bin/activate   # En Windows: venv\Scripts\activate

2. Instalar dependencias

pip install sanic google-generativeai python-dateutil
# Opcional (para Swagger / sanic-ext):
pip install "sanic[ext]"

3. Estructura de carpetas

Crear la siguiente estructura:

normalizador/
 ├── venv/
 ├── app/
 │   ├── __init__.py
 │   ├── app.py
 │   ├── config.py
 │   ├── services.py
 │   ├── normalizador_funcion.py
 │   ├── google_keys.json
 └── requirements.txt  (opcional)

4. Código principal: app/app.py

from sanic import Sanic, response
from sanic.request import Request
from sanic.response import json, text
from app.services import analyze_question_with_ai, generate_rag_response, get_gemma_response
import time

app = Sanic("normalizador")

# CORS
@app.middleware("response")
async def cors_headers(request, resp):
    resp.headers["Access-Control-Allow-Origin"] = "*"
    resp.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
    resp.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
    return resp

@app.route("/")
async def hello(request: Request):
    return text("Servidor IA con Sanic operativo ✅")

@app.post("/generate/")
async def generate_text_with_gemma(request: Request):
    try:
        data = request.json
        if not data or "prompt" not in data:
            return json({"error": "Falta el campo 'prompt'"}, status=400)

        prompt = data["prompt"]
        gemma_output = get_gemma_response(prompt)
        return json({"response": gemma_output})
    except Exception as e:
        return json({"error": f"Error al generar contenido con Gemma: {str(e)}"}, status=500)

@app.post("/generate_rag")
async def generate_response(request: Request):
    try:
        data = request.json or {}
        if "user_query" not in data:
            return json({
                "error": "Se requiere 'user_query' en el JSON",
                "ejemplo": {
                    "user_query": "¿Cuántos días de vacaciones tengo?",
                    "context": "Opcional",
                    "datos": "Opcional"
                }
            }, status=400)

        user_query = data["user_query"]
        context = data.get("context", "")
        datos = data.get("datos", "")

        rag_response = generate_rag_response(user_query, context, datos)
        return json({"response": rag_response})
    except Exception as e:
        return json({"error": f"Error interno: {str(e)}"}, status=500)

@app.post("/analyze_question/")
async def analyze_question(request: Request):
    try:
        data = request.json or {}
        if "question" not in data:
            return json({"error": "El campo 'question' es requerido"}, status=400)

        user_question = data["question"].strip()
        if not user_question:
            return json({"error": "La pregunta no puede estar vacía"}, status=400)

        analysis_result = analyze_question_with_ai(user_question)
        return json(analysis_result, ensure_ascii=False)
    except Exception as e:
        return json({"error": f"Error interno al procesar la pregunta: {str(e)}"}, status=500)

@app.route("/health")
async def health_check(request: Request):
    return json({"status": "ok"})

@app.route("/ping")
async def ping(request: Request):
    start = time.time()
    end = time.time()
    return json({
        "message": "pong",
        "response_time_ms": round((end - start) * 1000, 2)
    })

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000, workers=2, access_log=False)

5. Configuración de IA: app/config.py

Usá tu código actual; aquí un ejemplo mínimo para cargar claves:

import json
from pathlib import Path
import google.generativeai as genai
import random

def load_api_keys():
    try:
        with open(Path(__file__).parent / "google_keys.json") as f:
            return json.load(f).get("GOOGLE_API_KEYS", [])
    except:
        return []

class Settings:
    def __init__(self):
        self.google_api_keys = load_api_keys()
        self.gemma_model_name = "gemini-2.5-flash-lite"
        self.gemma_temperature = 0
        self.gemma_top_p = 1.0
        self.gemma_top_k = 40
        self.gemma_max_output_tokens = 2048

    def get_random_key(self):
        if not self.google_api_keys:
            raise ValueError("No hay claves API configuradas")
        selected_key = random.choice(self.google_api_keys)
        genai.configure(api_key=selected_key)
        return selected_key

settings = Settings()

Archivo app/google_keys.json ejemplo:

{
  "GOOGLE_API_KEYS": [
    "TU_API_KEY_AQUI"
  ]
}

6. Normalizador: app/normalizador_funcion.py

def normalizar_nombre(nombre: str) -> str:
    grupos = {
        "asistente_nombre": ["quien_soy", "identidad"],
        "saludo": ["saludo", "hola", "buen_dia"],
        "obtener_facturacion": [
            "obtener_facturacion_anual",
            "facturacion_anual",
            "obtener_facturacion_por_anio",
            "obtener_facturacion_por_año",
        ],
        "clientes_mas_compras": [
            "obtener_clientes_mas_compras",
            "obtener_clientes_mas_compradores",
            "obtener_clientes_top_compras",
            "obtener_clientes_con_mas_compras",
            "obtener_clientes_mas_compraron",
            "obtener_clientes_top_compradores",
            "listar_compradores_top",
            "obtener_compradores_top"
        ],
    }

    for funcion_canonica, sinonimos in grupos.items():
        if nombre in sinonimos:
            return funcion_canonica
    return nombre

7. Servicios IA: app/services.py

Pegá tu código existente. Asegurate que el import del normalizador apunte así:

from app.normalizador_funcion import normalizar_nombre
from app.config import settings
import google.generativeai as genai
# ... resto de tus funciones (get_gemma_response, generate_rag_response, analyze_question_with_ai, ...)

8. Ejecutar el servidor

Desarrollo:

python app/app.py

Producción (CLI de Sanic):

sanic app.app:app --host=0.0.0.0 --port=8000 --workers=2

9. Probar la API

curl -X POST http://localhost:8000/generate/ \
     -H "Content-Type: application/json" \
     -d '{"prompt":"Hola, ¿quién eres?"}'

10. Desplegar en Ubuntu con Nginx

Instalar dependencias

sudo apt update
sudo apt install python3 python3-venv nginx -y

Copiar proyecto y crear entorno

sudo mkdir /opt/normalizador
sudo chown $USER:$USER /opt/normalizador
cd /opt/normalizador

# copiar aquí los archivos del proyecto (app/, venv o crear uno)
python3 -m venv venv
source venv/bin/activate
pip install sanic google-generativeai python-dateutil

Probar localmente

sanic app.app:app --host=0.0.0.0 --port=8000

11. Crear servicio systemd

Crear archivo:

sudo nano /etc/systemd/system/normalizador.service

Contenido:

[Unit]
Description=Normalizador Sanic Service
After=network.target

[Service]
User=tu_usuario
WorkingDirectory=/opt/normalizador
Environment="PATH=/opt/normalizador/venv/bin"
ExecStart=/opt/normalizador/venv/bin/sanic app.app:app --workers=2 --host=127.0.0.1 --port=8000
Restart=always

[Install]
WantedBy=multi-user.target

Activar servicio:

sudo systemctl daemon-reload
sudo systemctl enable normalizador
sudo systemctl start normalizador
sudo systemctl status normalizador

12. Configurar Nginx

Crear archivo de sitio:

sudo nano /etc/nginx/sites-available/normalizador

Contenido:

server {
    listen 80;
    server_name _;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Activar y reiniciar:

sudo ln -s /etc/nginx/sites-available/normalizador /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

13. (Opcional) HTTPS con Let's Encrypt

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d tu-dominio.com

14. Endpoint público

Tu API debería estar disponible en:

http://TU_IP/   o   https://TU_DOMINIO/

15. Extras recomendados (breve)

  • Agregar LOG y rotación de logs (systemd/journald + archivo separado si hace falta).
  • Monitoreo: exponer métricas o usar Prometheus (puedes agregar un endpoint /metrics si lo necesitás).
  • Seguridad: limitar CORS en producción, configurar firewall (ufw), y asegurar las keys en variables de entorno o un vault.
  • Pruebas: agregar healthchecks más completas en /health y readiness/liveness para orquestadores.

Comentarios

Entradas populares de este blog

Instalación y Configuración de MySQL 5.7 en Ubuntu 24.04 LTS

Instalar MySQL 5.7 en Ubuntu 24.04 1. Descargar e instalar MySQL Copiar mkdir ~/mysql57 cd ~/mysql57 wget https://cdn.mysql.com/archives/mysql-5.7/mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz tar -zxvf mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz sudo mv mysql-5.7.44-linux-glibc2.12-x86_64 /usr/local/mysql sudo ln -s /usr/local/mysql/bin/mysql /usr/local/bin/mysql 2. Instalar dependencias necesarias IMPORTANTE: Se descargan las versiones nuevas de las librerías y se las vincula con las librerías que necesita MySQL. Copiar sudo apt update # Reemplazo de libaio sudo apt install libaio1t64 # Reemplazo de libtinfo y ncurses sudo apt install libtinfo6 libncurses6 Copiar # Crear los enlaces simbólicos sudo ln -sf /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/libaio.so.1 sudo ln -sf /usr/lib/x86_64-linux-gnu/libtinfo.so.6 /usr/lib/x86_64-linux-gnu/libtinfo.so.5 sudo ln -sf /usr/lib/x86_64-linux-gnu/libncurses.so.6 /usr/lib/x86_64...

Instalar Evolution API en Docker con Redis y PostgreSQL Local

Instalar Evolution API en Docker con Redis y PostgreSQL Local En este tutorial vamos a levantar Evolution API usando Docker , con soporte de Redis para sesiones y PostgreSQL local para almacenar datos de manera persistente y compartida entre varios usuarios. 1. Estructura del proyecto Crea una carpeta para tu proyecto y colócate en ella: mkdir -p ~/docker/evolution-api cd ~/docker/evolution-api 2. Archivo docker-compose.yml Este compose levanta Redis y Evolution API : version: "3.9" services: # ✅ SERVICIO REDIS redis: container_name: evolution_redis image: redis:7-alpine restart: unless-stopped ports: - "6379:6379" volumes: - redis_data:/data command: redis-server --save 60 1 --loglevel warning # ✅ SERVICIO EVOLUTION API evolution-api: container_name: evolution_api image: atendai/evolution-api restart: unless-stopped ports: - "8085:8080" env_file: - .env ...

Instalar Jasper Studio 6.21 para Ubuntu 24.04

Instalar js-studiocomm_6.21.3 en Ubuntu 24.4 Para instalar Jaspersoft Studio en Ubuntu 24.4, sigue estos pasos: 1. Descargar Jasper Studio Descarga la versión js-studiocomm_6.21.3 desde el siguiente enlace: Jaspersoft Studio 6.21.3 2. Crear el directorio de instalación mkdir /opt/jasperstudio 3. Mover el archivo descargado mv /dir_descarga/js-studiocomm_6.21.3_linux_x86_64.tgz /opt/jasperstudio/ cd /opt/jasperstudio 4. Extraer el archivo tar -xvzf js-studiocomm_6.21.3_linux_x86_64.tgz cd js-studiocomm_6.21.3 5. Ejecutar Jaspersoft Studio ./Jaspersoft\ Studio 6. Crear acceso directo en el escritorio Para facilitar el acceso, crea un archivo .desktop en el escritorio: gedit ~/Escritorio/jaspersoft-studio.desktop En el archivo jaspersoft-studio.desktop , agrega lo siguiente: [Desktop Entry] Version=1.0 Ty...