| ---
|
| title: MSL Internal API Joint
|
| emoji: 🎻
|
| colorFrom: purple
|
| colorTo: pink
|
| sdk: docker
|
| app_port: 7860
|
| pinned: false
|
| license: other
|
| ---
|
|
|
| # bert-api-v8-joint/
|
|
|
| Backend Python Flask que sirve el **joint intent + slot filling model** entregado por RMIT en Parts 5+6 (PDF Part5_Part6, mayo 2026). Arquitectura distinta a v6/v7-part6: en lugar de un único clasificador `AutoModelForSequenceClassification`, monta una clase custom `JointIntentSlotModel(nn.Module)` con dos heads sobre `AutoModel` — uno para intent, otro para BIO slot labels por token.
|
|
|
| **Origen:** copiado de `D:\XDev.Projects\MEHEARSAL-NLP-CONTROLLER-RMIT\Part_5_6\bert-api\bert-api-01` (versión D — defensiva/híbrida del análisis del doc 40 §3.2) + 4 modificaciones para HF Spaces Docker (ver §"Adaptaciones" más abajo).
|
|
|
| **Despliegue actual:** HuggingFace Space `MuseSceneLab/msl-internal-api-v8-intent` (Docker SDK). Ver doc 40 §5.2.1 + §6 Fase A.1-A.7.
|
|
|
| > **Nota sobre la convención de nombres:** la carpeta local se llama `bert-api-v8-joint/` (refleja la arquitectura técnica — joint intent + slot model). El Space en HF se llama `msl-internal-api-v8-intent` (refleja la convención MSL `vN-{focus}` por consistencia con `msl-internal-api-v7-part6`). El env var `BERT_V8_INTENT_API_URL` y el route `/api/classify-bert-v8-intent` siguen el nombre del Space.
|
|
|
| **Modelo HF:** `MuseSceneLab/part-5-6-model` — 21 intents musicales (sin generic MUTE/SOLO/LOOP en el dataset, aunque INTENT_METADATA los conserva por compat) + slot filling BIO (`O`, `B-instrument`, `I-instrument`, `B-section`, `B-bpm`, `B-bar`, `B-percent`, `B-style`).
|
|
|
| > **Nota sobre el frontmatter YAML del principio:** lo leen los Spaces de HuggingFace para configurar el deploy (título, SDK, puerto). `app_port: 7860` es crítico — sin él HF no sabe qué puerto exponer y el Space queda en "config error". En cualquier otro contexto (lectura local, repo de GitHub) el frontmatter se ignora o se renderiza como bloque inicial.
|
|
|
| **Despliegue futuro:** AWS App Runner cuando se cumplan los disparadores de doc 33 §13.4.
|
|
|
| ---
|
|
|
| ## Estructura
|
|
|
| ```
|
| bert-api/
|
| ├── bert_api.py Flask app. Carga modelo en arranque, expone /classify y /health
|
| ├── requirements.txt flask, flask-cors, transformers, torch, huggingface_hub, python-dotenv
|
| ├── Dockerfile Imagen para HF Spaces. Python 3.11-slim, expone 7860
|
| ├── .dockerignore Excluye venv, __pycache__, README, etc. de la imagen
|
| └── README.md Este archivo
|
| ```
|
|
|
| ---
|
|
|
| ## Endpoints
|
|
|
| | Método | Path | Body | Devuelve |
|
| |---|---|---|---|
|
| | `POST` | `/classify` | `{ "utterance": "mute the guitar" }` | `{ intent_label, locale, target, params_json, confidence, model, slots }` |
|
| | `GET` | `/health` | — | `{ status, model, hf_model, num_intents, num_slots }` |
|
|
|
| ### Diferencias del shape de respuesta vs v6/v7-part6
|
|
|
| | Campo | v6/v7-part6 (single-task) | **v8-joint (este Space)** |
|
| |---|---|---|
|
| | `confidence` | `0-100` (percentage, `round(c*100, 2)`) | **`0-1` (fraction, `round(c, 4)`)** — el frontend debe formatear como `(c * 100).toFixed(2)` antes de mostrar, y el `mapConfidence()` del proxy `/api/classify-bert-joint/route.ts` debe usar umbrales 0.80 / 0.50 (no 80 / 50) |
|
| | `model` | `"DistilBERT"` | `"DistilBERT-Joint-v6"` |
|
| | `slots` | (no existe) | `{ "guitar": "B-instrument", "4": "B-bar" }` — diccionario `palabra → BIO label`. Vacío `{}` si no hay slots |
|
| | `/health` extras | (nada) | `hf_model`, `num_intents`, `num_slots` |
|
|
|
| ---
|
|
|
| ## Variables de entorno
|
|
|
| | Var | Default | Para qué |
|
| |---|---|---|
|
| | `HF_TOKEN` | _(obligatorio)_ | Auth para descargar el modelo privado de HF. En HF Spaces se configura como Secret del Space |
|
| | `BERT_MODEL_NAME` | `MuseSceneLab/part-5-6-model` | Cambiar para forzar otro joint model. **Solo modelos joint compatibles** — un modelo single-task aquí rompería el `model.load_state_dict()` porque la clase `JointIntentSlotModel` espera dos heads (`intent_head`, `slot_head`) y un `label_maps.json` con `id2intent` + `id2slot` |
|
| | `PORT` | `7860` | Puerto. Para gunicorn en el Dockerfile se hardcodea a 7860 en el flag `--bind`. Para Flask local (`python bert_api.py`) se lee de aquí |
|
|
|
| ## Servidor: gunicorn en producción, Flask dev en local
|
|
|
| El Dockerfile lanza el backend con **gunicorn** (WSGI server productivo):
|
|
|
| ```
|
| gunicorn --bind 0.0.0.0:7860 --workers 2 --preload --timeout 120 bert_api:app
|
| ```
|
|
|
| - `--workers 2`: 2 procesos paralelos (CPU Basic free de HF Spaces tiene 2 vCPU).
|
| - `--preload`: carga el modelo una vez antes de fork, los workers comparten memoria via COW. Sin esto, cada worker cargaría su copia y duplicaríamos memoria.
|
| - `--timeout 120`: si una inferencia se cuelga, mata el worker y arranca otro. Default 30s es demasiado bajo para casos límite con torch.
|
|
|
| Para **desarrollo local rápido** (iteración sobre `bert_api.py` sin tener que reconstruir imagen), el bloque `if __name__ == '__main__'` con `app.run()` sigue funcionando — basta con `python bert_api.py`. Esto NO se ejecuta en Spaces porque gunicorn importa `bert_api:app` directamente, sin pasar por `__main__`.
|
|
|
| ---
|
|
|
| ## Desarrollo local
|
|
|
| ```bash
|
| cd bert-api
|
| python -m venv venv
|
| .\venv\Scripts\activate # Windows PowerShell: .\venv\Scripts\Activate.ps1
|
| pip install -r requirements.txt
|
| $env:HF_TOKEN = "hf_..." # tu token con permiso de lectura sobre el modelo
|
| python bert_api.py
|
| ```
|
|
|
| Levanta en `http://localhost:7860`. Probar con:
|
|
|
| ```bash
|
| curl -X POST http://localhost:7860/classify -H "Content-Type: application/json" -d '{"utterance":"mute the guitar"}'
|
| ```
|
|
|
| **Nota:** la primera ejecución descarga el modelo (~500 MB) desde HF a la caché local (`~/.cache/huggingface/`). Ejecuciones posteriores son instantáneas.
|
|
|
| ---
|
|
|
| ## Despliegue como Space (proceso R4-R7 de doc 33)
|
|
|
| 1. Crear Space en `huggingface.co/new-space`:
|
| - Owner: `MuseSceneLab`
|
| - Name: `msl-internal-api-v8-intent`
|
| - SDK: **Docker** (template Blank — usamos nuestro Dockerfile)
|
| - Hardware: CPU Basic (free)
|
| - Visibility: Public _(el contenedor; los pesos del modelo siguen privados)_
|
| - License: `other` (decisión doc 34 §D1 — IP de MSL)
|
|
|
| 2. En el Space, ir a Settings → Repository secrets → añadir `HF_TOKEN` con el token `mehearsal-read` de Jose.
|
|
|
| 3. Subir los 4 archivos vía Web UI (drag & drop en `Files → Add file → Upload files`):
|
| `bert_api.py`, `Dockerfile`, `requirements.txt`, `README.md`.
|
|
|
| 4. HF construye la imagen (~3-5 min). El cold start del Space joint puede ser **más lento que el de v6/v7-part6** porque `snapshot_download()` descarga el modelo entero la primera vez (en lugar del lazy load de `AutoModelForSequenceClassification.from_pretrained()`). Verificar logs en la UI del Space.
|
|
|
| 5. URL pública: `https://musescenelab-msl-internal-api-v8-intent.hf.space/classify`.
|
|
|
| ---
|
|
|
| ## Adaptaciones respecto al código fuente
|
|
|
| Fuente: `Part_5_6/bert-api/bert-api-01` del repo `MEHEARSAL-NLP-CONTROLLER-RMIT` (rama `part_5_6`) — versión **D** del análisis del doc 40 §3.2.
|
|
|
| Cuatro diffs vs ese fuente:
|
|
|
| 1. **Import `os`** al principio del archivo.
|
| 2. **`HF_MODEL_NAME` configurable** vía env var (default `MuseSceneLab/part-5-6-model`).
|
| 3. **Porting de carga LOCAL → carga HF Hub**: el fuente D carga el modelo desde `nlp_v5/best_joint_model/` (directorio local relativo al script). Sustituido por `snapshot_download(repo_id=HF_MODEL_NAME, repo_type="model")` para que HF Spaces descargue del Hub. Patrón tomado de la versión C (`Part_5_6/bert-api/bert_api.py`).
|
| 4. **Puerto y debug** en `app.run()`: puerto leído de env var `PORT` (default 7860, requisito de Spaces), `debug=False` para producción.
|
|
|
| La lógica de `JointIntentSlotModel`, `INTENT_METADATA` (21 intents), `extract_entities()` con `params_json` computado por intent, el intent upgrade hack heredado de v5, y la extracción slot-first + regex fallback queda **intacta**. Esta es la "versión defensiva" (D) decidida en doc 40 §3.2.1 frente a la "versión limpia según PDF Part 6" (C).
|
|
|
| ---
|
|
|
| ## Roadmap
|
|
|
| - **Ahora:** HF Spaces Docker (este folder = código fuente, sin cambios entre dev local y prod Space)
|
| - **Futuro:** migración a AWS App Runner. Ver doc 33 §13. El Dockerfile actual sirve igual sin cambios.
|
|
|