Servicio central para tareas de ML/AI. Cada funcionalidad vive en su propio módulo dentro de modules/.
mtia/
├── api.py # Orquestador FastAPI (incluye routers de módulos)
├── modules/
│ └── stopreason/
│ ├── pipeline.py # Pipeline ML (extract, train, predict CLI)
│ ├── router.py # Endpoints FastAPI (POST /predict, GET /health)
│ ├── models/cic/ # Artefactos del modelo entrenado
│ └── data/ # Datos de entrenamiento extraídos
├── Dockerfile
└── pyproject.toml
| Método | Ruta | Descripción |
|---|---|---|
| GET | /health |
Chequeo de salud del servicio |
| GET | /stopreason/health |
Salud del módulo stop-reason |
| POST | /stopreason/predict |
Predecir top-k razones de detención |
POST /stopreason/predict
{
"dev_id": 1079,
"duration": 60.0,
"top_k": 3,
"user_id": null,
"timestamp": "2026-01-29T14:52:27"
}Cuando una máquina se detiene en la línea de producción, el sistema predice la razón de detención más probable usando un clasificador LightGBM entrenado con datos históricos de detenciones. El pipeline tiene tres pasos: extract → train → predict.
Extrae eventos históricos de detención desde PostgreSQL (j_s_reg, j_device, j_cod_state, j_line) y construye un vector de features por cada evento de detención:
- Temporales: hora, día de la semana, mes
- Contexto del dispositivo: ID del dispositivo, operador, duración de la detención
- Historial: 2 razones de detención anteriores, tiempo desde la última detención
- Topología de línea: posición en la línea, largo de la línea, y estado de 4 vecinos (2 aguas arriba, 2 aguas abajo) en una ventana de 5 minutos
Las razones de detención con menos de --min-samples ocurrencias (por defecto 10) se filtran. Genera un archivo Parquet.
docker compose exec mtia stopreason extract --client-id 31 --output /app/modules/stopreason/data/cic.parquet --verboseEntrena un clasificador LightGBM multi-clase. Compara dos modelos: solo-dispositivo (9 features) vs dispositivo+línea (25 features con contexto de vecinos), y persiste el modelo dispositivo+línea (mejor accuracy top-3).
Artefactos guardados: model.joblib, label_encoder.joblib, metadata.json.
Resultados CIC: ~85% top-1, ~95% top-3, ~97% top-5 (69 clases).
docker compose exec mtia stopreason train --data /app/modules/stopreason/data/cic.parquet --output /app/modules/stopreason/models/cic --verboseConsulta la base de datos en tiempo real para obtener contexto (info del dispositivo, detenciones recientes, estado de vecinos), arma el mismo vector de 25 features usado durante el entrenamiento, y retorna las top-k predicciones con scores de confianza y descripciones de razones de detención.
Disponible como comando CLI y como endpoint POST /stopreason/predict.
docker compose exec mtia stopreason predict --model-dir /app/modules/stopreason/models/cic --dev-id 1079 --verbose- Crear
modules/newmodule/con__init__.py,pipeline.py,router.py - En
api.py, agregar:from modules.newmodule import router+app.include_router(router) - Agregar
"modules.newmodule"apackagesenpyproject.toml
RAG local que responde preguntas de operadores sobre los manuales de un paso productivo en mtworkflows. Todo corre local: Ollama nativo macOS (GPU Metal)
- Qdrant + mtia + pestaña "Documentos" en el frontend.
mtworkflows (Vue) ──JWT── mtia (FastAPI) ──┬── Ollama (host, Metal) ← embeddings + LLM
└── Qdrant (docker) ← vectores por cliente
Modelos por defecto (MacBook Pro M4 24GB):
| Rol | Modelo | Memoria aprox. |
|---|---|---|
| LLM | gemma4:e4b |
~8 GB |
| Embeddings | bge-m3 (1024d) |
~1 GB |
Alternativas: gemma4:e2b (más rápido, menos calidad), gemma4:26b MoE
(más calidad, justo en memoria).
-
Instalar Ollama nativo — https://ollama.com/download/mac Ollama corre como servicio launchd en
localhost:11434. -
Descargar modelos (desde el host, no desde el contenedor):
invoke mtia.pull-models # o manualmente: ollama pull gemma4:e4b bge-m3 -
Levantar el stack RAG (Qdrant + mtia con las nuevas variables):
invoke mtia.up
-
Verificar salud:
invoke mtia.health # Debe mostrar OK en mtia, qdrant, ollama, y los dos modelos descargados.
invoke mtia.ingest --client cyt --blueprint empaque \
--path /ruta/a/manual-empaque.pdfEsto copia el PDF a mtia/documents/cyt/empaque/ (gitignored) y lo indexa.
Re-ingestar el mismo PDF reemplaza los fragmentos anteriores (idempotente).
Para reindexar todo un cliente (borra la colección en Qdrant y reingesta desde disco):
invoke mtia.reindex --client cytTodos requieren Authorization: JWT <token> con el mismo token que emite
el servicio api (pyramid_jwt, HS512, JWT_SECRET compartido vía .env).
El claim client del token debe coincidir con el parámetro client.
| Método | Ruta | Uso |
|---|---|---|
| GET | /rag/documents?client=&blueprint_code= |
Listar docs de un paso |
| POST | /rag/ingest (multipart: client, blueprint_code, file) |
Subir e indexar |
| DELETE | /rag/documents?client=&blueprint_code=&source= |
Borrar doc + vectores |
| POST | /rag/chat?client=&blueprint_code=&question=&top_k= |
SSE stream de respuesta |
El stream de /rag/chat emite tres tipos de eventos:
citations— lista[{source, page, chunk_idx, score}], una sola vez al inicio.token—{text: "..."}por cada chunk del LLM.done—{}al final.
En mtworkflows, la pestaña se agrega automáticamente al abrir una orden. El
blueprint_code se deriva del nombre del paso con slugify()
(composables/useRag.js): "Control de calidad" → control_de_calidad.
El proxy Vite /mtia/* → http://mtia:8008 evita problemas de CORS.
Si ningún fragmento supera MIN_RETRIEVAL_SCORE (0.30 por defecto, similitud
coseno en bge-m3), el endpoint emite:
"No encontré información relevante en los documentos."
En lugar de llamar al LLM y arriesgar alucinaciones. Ajustar en
modules/rag/router.py si se valida contra un corpus real.
mtia/documents/<client>/<blueprint_code>/<archivo>.pdf
El árbol completo está en .gitignore — los documentos contienen IP del cliente.
docker compose exec mtia python -m pytest modules/rag/tests/Los tests de test_ollama.py se saltean automáticamente si Ollama no está
corriendo en el host. Los demás usan Qdrant real y Ollama stub.
| Síntoma | Causa / solución |
|---|---|
invoke mtia.health → FAIL ollama |
Ollama no está corriendo en el host. brew services start ollama |
ollama CLI missing |
Falta instalar Ollama nativo (no corre en Docker — se pierde Metal) |
401 Unauthorized en /rag/* |
Header Authorization: JWT <token> ausente o JWT_SECRET distinto entre api y mtia |
403 Forbidden |
El client pedido no coincide con el claim client del JWT |
| Respuesta dice "No encontré información relevante" | Normal si el corpus no cubre la pregunta. Ajustar threshold o agregar docs |
gemma4:e4b no responde bien en español técnico |
Probar gemma4:26b (ver tabla de modelos arriba) y reindexar no es necesario |
| Embeddings muy lentos | Primera llamada a Ollama carga el modelo (cold start, ~5-10s). Tirar un ollama run bge-m3 para pre-cargar |