Pipeline CT/CD
Continuous Training y Continuous Deployment con Step Functions, evaluación cross-fold y gate de promoción.
Por qué CT/CD aquí es distinto
SoundGuard no usa un modelo entrenable tradicional (no hay gradient descent, no hay epochs, no hay loss curves). El "modelo" es la combinación de tres elementos:
- Embeddings: vectores de 1024 dims generados por Bedrock Nova.
- Índice vectorial: S3 Vectors con embeddings + metadata.
- Clasificador: k-NN con un valor fijo de
k.
Reentrenar = reconstruir el índice con nuevos datos o distintos parámetros. Desplegar = promover un índice validado a producción.
Esto no es menos válido que un pipeline de deep learning. Es un patrón distinto: instance-based learning con model store vectorial.
State machine
S3 Trigger (nuevo clip en soundguard-dev-audio)
│
▼
[Step Functions: soundguard-dev-ct-pipeline]
│
├─[Lambda: detect_new_clips] ¿qué falta en el índice?
│
├─[Lambda: embed_batch] Bedrock Nova → S3 Vectors staging
│
├─[Lambda: evaluate_and_decide] evaluación cross-fold contra producción
│
└─[Choice: accuracy ≥ 0.90?]
├── YES ─▶ promote_index.py swap atómico de alias
└── NO ─▶ SNS alert + rollback stagingLambdas del pipeline
| Lambda | Responsabilidad | Código |
|---|---|---|
detect_new_clips | Lista S3, hace diff contra el índice y devuelve los faltantes. | src/pipeline/detect_new_clips.py |
embed_batch | Para cada clip, llama Bedrock y hace put_vectors en staging. | src/pipeline/embed_batch.py |
evaluate_and_decide | Corre evaluación cross-fold y decide promover o rechazar. | src/pipeline/evaluate_and_decide.py |
promote_index | Swap atómico del alias soundguard-dev-production. | src/pipeline/promote_index.py |
CD Gate
El gate compara el índice de staging contra el índice de producción usando un fold distinto del que se entrenó:
| Escenario | Accuracy | F1 macro | Decisión |
|---|---|---|---|
| LOO: fold 2 vs fold 2 | 89.00% | 0.8888 | REJECT (evaluación interna del fold) |
| Cross-fold: fold 2 vs fold 1 | 90.00% | 0.8949 | PASS (queries staging vs índice prod) |
Cambio clave: el CD gate usa evaluación cross-fold (evaluate_cross_fold.py) en lugar de LOO. Esto simula el escenario real: "¿cómo se clasifican los nuevos clips usando el índice de producción existente?"
Por qué funciona: el índice de producción (fold 1) tiene 400 vectores diversos que representan bien las clases. Los clips de fold 2 encuentran mejores vecinos allí que entre ellos mismos.
Promoción atómica
def promote(staging_index: str, production_alias: str, threshold: float = 0.90):
metrics = evaluate(staging_index)
if metrics["accuracy"] >= threshold:
swap_alias(production_alias, staging_index)
log_to_mlflow(metrics, stage="production")
return "promoted"
log_to_mlflow(metrics, stage="rejected")
return "rejected"El swap del alias es atómico (cambia el env var VECTOR_INDEX de la Lambda). No hay redeploy. Rollback toma segundos: revertir el env var.
Experiment tracking con MLflow
| Tipo | Valor |
|---|---|
| Hyperparameter | k ∈ 11 |
| Dataset | fold 1, fold 2, fold 1+2 |
| Embedding config | dim=1024, purpose=CLASSIFICATION |
| Métricas | accuracy, F1 macro, F1 weighted, F1 por clase |
| Artefactos | confusion_matrix.png, metrics.json |
Comando local:
for k in 1 3 5 7 9 11; do
python -m src.evaluation.evaluate_knn --fold 1 --k "$k" --mlflow
done
mlflow ui --backend-store-uri "file://$(pwd)/mlruns"Decisiones arquitectónicas
| Pregunta | Decisión | Por qué |
|---|---|---|
| MLflow o W&B | MLflow | Open-source, corre local, sin signup. |
| Step Functions o Airflow/Prefect | Step Functions | Nativo AWS, sin scheduler propio. |
| Sobrescribir índice o staging | Staging | A/B testing y rollback instantáneo. |
| Apuntar Lambda a nombre fijo o alias | Alias | Swap atómico, sin redeploy. |
Riesgos conocidos
| Riesgo | Mitigación |
|---|---|
| Throttling de Bedrock en pipeline paralelo | Rate limit en embed_batch, batch size pequeño, retries con backoff. |
| Costo del grid search | ~$12 USD por 11 corridas × 400 clips. Cubierto por créditos académicos. |
| Step Functions sin trigger nativo de S3 | S3 Event Notification → Lambda → StartExecution API. |