MÓDULO 15 · MLOPS: PIPELINES CON ZENML · 25 MIN

Del notebook al DAG

Un notebook que entrena un modelo y un pipeline que entrena el mismo modelo hacen lo mismo una vez y cosas muy distintas la segunda. La diferencia no es la herramienta: es que el pipeline te obliga a declarar las fronteras — qué entra y qué sale de cada etapa. Todo lo demás que aporta MLOps —caché, linaje, portabilidad, reproducibilidad— se apoya en esas fronteras y no existe sin ellas.
01 — El problema
Por qué el notebook no sobrevive al traspaso

El diagnóstico del material es certero y merece leerse entero: celdas ejecutadas en desorden, variables globales que se sobrescriben, y datos cargados de rutas locales con nombres como data_final_v3_revisado.csv. Nada de eso falla mientras la persona que lo escribió esté delante — y todo falla en cuanto no lo está.

Lo que oculta un notebookCuándo aparece
El orden real en que hay que ejecutar las cosasAl reiniciar el kernel
De dónde salieron los datosAl intentar reproducir el resultado
Qué versiones de librerías se usaronEn la máquina de otra persona
Qué pasos manuales hubo entre celdasSiempre demasiado tarde

La frase que resume el módulo entero: si no puedes volver a entrenar tu modelo exactamente igual dos meses después, no tienes un producto de software, tienes un experimento desechable.

02 — Las tres piezas
Step, pipeline y stack
01

Step

Una unidad lógica: cargar, validar, entrenar, evaluar. Entradas y salidas declaradas y tipadas.

una responsabilidad, ni más ni menos
02

Pipeline

El plano que conecta los steps. Define el flujo de datos, no la lógica.

solo conecta: nada de procesamiento aquí
03

Stack

Dónde y cómo se ejecuta: orquestador, almacén de artifacts, desplegador.

infraestructura, fuera del código Python

La separación entre el segundo y el tercero es la idea que hace portátil todo lo demás: el pipeline dice qué pasa, el stack dice dónde. Cambiar de portátil a la nube es cambiar el stack, no el código.

// el pipeline conecta; si tiene lógica dentro, es un step disfrazado
03 — El contrato
Los tipos no son decoración
@step
def cargador_de_datos() -> pd.DataFrame:
    ...

@step
def entrenador(df: pd.DataFrame) -> LinearRegression:
    ...

@pipeline
def entrenamiento():
    datos = cargador_de_datos()
    modelo = entrenador(datos)

Esas anotaciones de tipo hacen tres cosas que en un script normal no harían nada:

Qué habilitaPor qué
Detectar desajustes entre pasosSi un step devuelve algo que el siguiente no acepta, falla al construir el grafo y no a mitad del entrenamiento
Serializar correctamente cada salidaEl framework elige cómo guardar un DataFrame, un modelo de scikit-learn o un array según su tipo
Mostrar cada artifact en el panelSabe qué visualizador aplicar

Y de ahí sale la restricción que más sorprende al empezar: todo lo que sale de un step tiene que poder guardarse. Una conexión abierta a una base de datos, un cliente de API o un objeto con estado no viajan entre pasos. Si un step necesita una conexión, la abre dentro y devuelve datos.

Auditoría Antes de copiar código de este material conviene saber que usa dos generaciones de la API de ZenML: seis unidades importan from zenml import step, pipeline y la unidad de «ZenML Avanzado» importa from zenml.steps import step, Output y from zenml.pipelines import pipeline, que es la forma anterior. No hay una sola línea que avise de que una sustituye a la otra. Es el mismo patrón que este curso ya tiene fichado con LangChain en el Módulo 10: un material a medio actualizar es peor que uno viejo, porque no se sabe de qué parte fiarse. ¿Cuál de las dos rutas funciona sin avisos en tu versión instalada? Es una línea de pip show zenml y un import de prueba.
Para hacer
Partir algo tuyo en steps

Coge un script o notebook tuyo de ML y, sin escribir código todavía, dibuja sus fronteras: qué bloques hay, qué entra y qué sale de cada uno, y con qué tipo. No busques la partición perfecta; busca los sitios donde no sabrías decir qué entra o qué sale.

Esos son los que importan: cada frontera que no puedes describir es una dependencia oculta —una variable global, una ruta local, un paso manual— y es exactamente lo que hará fallar el código en la máquina de otra persona.