Image for post Tu Primera API de Inferencia con FastAPI: Sirviendo un Modelo Sencillo y Probándolo

Tu Primera API de Inferencia con FastAPI: Sirviendo un Modelo Sencillo y Probándolo


Una guía paso a paso para desarrolladores junior-mid

Contexto del Problema: ¿Por Qué Necesitas una API para tu Modelo de ML?

Has entrenado un modelo de Machine Learning increíble. ¡Felicidades! Pero, ¿cómo lo usas en una aplicación real? Un modelo por sí solo es como un cerebro sin cuerpo. Necesitas una forma de interactuar con él, de enviarle datos y recibir sus predicciones. Aquí es donde entran en juego las APIs (Interfaces de Programación de Aplicaciones).

Una API te permite exponer la funcionalidad de tu modelo a otras aplicaciones (una web, una app móvil, otro servicio backend) de una manera estandarizada. En el mundo del Machine Learning, esto se conoce como servir modelos o inferencia en producción. FastAPI es una herramienta moderna y potente que hace este proceso sorprendentemente sencillo y eficiente.

Imagina que tienes un modelo que predice el precio de una casa. No quieres que cada aplicación que necesite esa predicción tenga que cargar el modelo, preprocesar los datos y ejecutar la inferencia. En su lugar, puedes crear una API que reciba los detalles de la casa (metros cuadrados, número de habitaciones, etc.) y devuelva el precio predicho. ¡Mucho más limpio y escalable!

Conceptos Clave: Desglosando los Fundamentos

Antes de sumergirnos en el código, entendamos algunos términos esenciales:

  • API (Application Programming Interface): Un conjunto de reglas y definiciones que permiten que diferentes programas de software se comuniquen entre sí. Piensa en ella como un menú de restaurante: te dice qué puedes pedir y qué esperar a cambio.
  • REST (Representational State Transfer): Un estilo arquitectónico para diseñar APIs web. Se basa en el uso de métodos HTTP (GET, POST, PUT, DELETE) para realizar operaciones sobre recursos. Para la inferencia de modelos, normalmente usaremos POST para enviar datos y recibir una predicción.
  • FastAPI: Un framework web moderno y de alto rendimiento para construir APIs con Python. Es conocido por su velocidad (gracias a Starlette y Pydantic), su facilidad de uso y su generación automática de documentación interactiva (Swagger UI / ReDoc).
  • Uvicorn: Un servidor web ASGI (Asynchronous Server Gateway Interface) que permite a FastAPI ejecutar aplicaciones asíncronas de Python. Es lo que realmente 'sirve' tu API al mundo.
  • Pydantic: Una librería de Python que permite definir modelos de datos usando type hints. FastAPI la usa para validar automáticamente los datos de entrada y salida de tus endpoints, lo que reduce drásticamente los errores y mejora la robustez de tu API.
  • Endpoint: Una URL específica dentro de tu API que realiza una función particular. Por ejemplo, /predict podría ser el endpoint para obtener predicciones.

Mini Glosario Rápido

  • Request: La solicitud que un cliente envía a tu API.
  • Response: La respuesta que tu API envía de vuelta al cliente.
  • JSON (JavaScript Object Notation): Un formato ligero de intercambio de datos, muy común para enviar y recibir datos en APIs web.

Implementación Paso a Paso: Construyendo Nuestra API

Vamos a construir una API sencilla que sirva un modelo de regresión lineal. Este modelo predecirá un valor basándose en una única característica de entrada.

Paso 1: Preparación del Entorno

Primero, crea un nuevo directorio para tu proyecto y un entorno virtual. Luego, instala las librerías necesarias:

mkdir fastapi-ml-api
cd fastapi-ml-api
python -m venv venv
source venv/bin/activate  # En Windows: venv\Scripts\activate
pip install fastapi uvicorn scikit-learn pandas joblib

Aquí están las versiones de las librerías que usaremos (pueden variar ligeramente, pero el código debería funcionar):

# requirements.txt
fastapi==0.111.0
uvicorn==0.30.1
scikit-learn==1.5.0
pandas==2.2.2
joblib==1.4.2

Paso 2: Entrenar y Guardar un Modelo Sencillo

Crearemos un script Python para entrenar un modelo de regresión lineal muy básico y guardarlo en un archivo. Este modelo simulará la predicción de un 'rendimiento' basado en una 'inversión'.

Crea un archivo llamado train_model.py:

# train_model.py
import pandas as pd
from sklearn.linear_model import LinearRegression
import joblib
import os

print("Entrenando modelo...")

# Datos de ejemplo: Inversión vs. Rendimiento
data = {
    'inversion': [10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
    'rendimiento': [12, 25, 33, 42, 55, 61, 78, 85, 92, 105]
}
df = pd.DataFrame(data)

X = df[['inversion']]
y = df['rendimiento']

# Crear y entrenar el modelo de regresión lineal
model = LinearRegression()
model.fit(X, y)

# Guardar el modelo entrenado
model_path = 'modelo_regresion.joblib'
joblib.dump(model, model_path)

print(f"Modelo entrenado y guardado en '{model_path}'")
print(f"Coeficiente: {model.coef_[0]:.2f}")
print(f"Intercepto: {model.intercept_:.2f}")

# Prueba rápida del modelo
ejemplo_inversion = pd.DataFrame({'inversion': [75]})
prediccion = model.predict(ejemplo_inversion)[0]
print(f"Predicción para inversión de 75: {prediccion:.2f}")

Ejecuta este script desde tu terminal:

python train_model.py

Esto creará un archivo modelo_regresion.joblib en tu directorio.

Paso 3: Crear la API con FastAPI

Ahora, crearemos el archivo principal de nuestra API. Llama a este archivo main.py:

# main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import joblib
import os

# 1. Cargar el modelo entrenado
MODEL_PATH = 'modelo_regresion.joblib'

try:
    model = joblib.load(MODEL_PATH)
    print(f"Modelo cargado exitosamente desde {MODEL_PATH}")
except FileNotFoundError:
    print(f"Error: El archivo del modelo '{MODEL_PATH}' no se encontró.")
    print("Asegúrate de ejecutar 'python train_model.py' primero.")
    exit(1) # Salir si el modelo no se puede cargar
except Exception as e:
    print(f"Error al cargar el modelo: {e}")
    exit(1)

# 2. Inicializar la aplicación FastAPI
app = FastAPI(
    title="API de Inferencia de Rendimiento",
    description="Una API simple para predecir rendimiento basado en inversión.",
    version="1.0.0"
)

# 3. Definir el modelo de datos para la entrada (usando Pydantic)
class InputData(BaseModel):
    inversion: float

# 4. Definir el endpoint de predicción
@app.post("/predict")
async def predict(data: InputData):
    """
    Realiza una predicción de rendimiento basada en la inversión.

    - **inversion**: El monto de la inversión (número flotante).
    """
    try:
        # Convertir la entrada a un formato que el modelo espera (DataFrame)
        input_df = pd.DataFrame({'inversion': [data.inversion]})
        
        # Realizar la predicción
        prediction = model.predict(input_df)[0]
        
        # Devolver la predicción como un diccionario JSON
        return {"inversion": data.inversion, "rendimiento_predicho": round(float(prediction), 2)}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error interno del servidor: {e}")

# 5. (Opcional) Añadir un endpoint de salud
@app.get("/health")
async def health_check():
    return {"status": "ok", "message": "API funcionando correctamente"}

Explicación del código crítico:

  • from pydantic import BaseModel: Importamos BaseModel para definir la estructura de nuestros datos de entrada.
  • class InputData(BaseModel): inversion: float: Esto crea un esquema de datos. FastAPI, con la ayuda de Pydantic, validará automáticamente que cualquier JSON que reciba en el endpoint /predict tenga un campo llamado inversion y que sea un número flotante. Si no cumple, FastAPI devolverá un error 422 (Unprocessable Entity) automáticamente.
  • @app.post("/predict"): Este es un decorador de FastAPI que asocia la función predict con la ruta /predict y el método HTTP POST.
  • async def predict(data: InputData):: Definimos una función asíncrona (async def) que recibirá un objeto data del tipo InputData que acabamos de definir. FastAPI se encarga de deserializar el JSON de la solicitud en este objeto InputData.
  • model.predict(input_df): Aquí es donde nuestro modelo de ML hace la magia, tomando los datos de entrada y generando una predicción.
  • return {"inversion": data.inversion, "rendimiento_predicho": round(float(prediction), 2)}: La función devuelve un diccionario Python, que FastAPI convierte automáticamente en una respuesta JSON.
  • @app.get("/health"): Un endpoint simple para verificar si la API está viva y respondiendo. Útil para monitoreo.

Paso 4: Ejecutar la API

Desde tu terminal, en el directorio fastapi-ml-api, ejecuta:

uvicorn main:app --reload

Verás una salida similar a esta:

INFO:     Will watch for changes in these directories: ['/path/to/your/fastapi-ml-api']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [PID] using WatchFiles
INFO:     Started server process [PID]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

Esto significa que tu API está corriendo en http://127.0.0.1:8000. La opción --reload es muy útil durante el desarrollo, ya que reinicia el servidor automáticamente cada vez que guardas cambios en tus archivos.

Paso 5: Probar la API

Puedes probar tu API de varias maneras. La más sencilla es usando la documentación interactiva que FastAPI genera automáticamente. Abre tu navegador y ve a http://127.0.0.1:8000/docs.

Documentación interactiva de FastAPI (Swagger UI) Captura de pantalla de Swagger UI de FastAPI

(Nota: La imagen es un ejemplo conceptual, no se genera automáticamente. Al visitar /docs verás la interfaz real.)

Haz clic en /predict, luego en 'Try it out', introduce un valor para inversion (ej. 75.0) y haz clic en 'Execute'. Verás la respuesta de tu API.

Alternativamente, puedes usar curl desde tu terminal:

curl -X 'POST' \
  'http://127.0.0.1:8000/predict' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{"inversion": 75.0}'

O un script Python usando la librería requests:

# test_api.py
import requests
import json

url = "http://127.0.0.1:8000/predict"
headers = {"Content-Type": "application/json"}

# Prueba 1: Inversión válida
data_valid = {"inversion": 75.0}
print(f"Enviando: {data_valid}")
response = requests.post(url, headers=headers, data=json.dumps(data_valid))
print(f"Respuesta (200 OK): {response.status_code} - {response.json()}")

# Prueba 2: Otra inversión válida
data_another = {"inversion": 120.0}
print(f"Enviando: {data_another}")
response = requests.post(url, headers=headers, data=json.dumps(data_another))
print(f"Respuesta (200 OK): {response.status_code} - {response.json()}")

# Prueba 3: Entrada inválida (string en lugar de float)
data_invalid = {"inversion": "cien"}
print(f"Enviando: {data_invalid}")
response = requests.post(url, headers=headers, data=json.dumps(data_invalid))
print(f"Respuesta (422 Unprocessable Entity): {response.status_code} - {response.json()}")

Ejecuta python test_api.py para ver los resultados.

Mini Proyecto / Aplicación Sencilla: Añadiendo un Toque de Producción

Para hacer nuestra API un poco más robusta y lista para un entorno de desarrollo, podemos añadir un logging básico y considerar cómo se manejarían las variables de entorno.

Modifica main.py para incluir logging:

# main.py (fragmento modificado)
import logging
# ... otras importaciones ...

# Configuración básica de logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# ... (resto del código de carga del modelo y FastAPI app)

# 4. Definir el endpoint de predicción
@app.post("/predict")
async def predict(data: InputData):
    """
    Realiza una predicción de rendimiento basada en la inversión.

    - **inversion**: El monto de la inversión (número flotante).
    """
    logger.info(f"Recibida solicitud de predicción para inversión: {data.inversion}")
    try:
        input_df = pd.DataFrame({'inversion': [data.inversion]})
        prediction = model.predict(input_df)[0]
        logger.info(f"Predicción calculada: {prediction:.2f}")
        return {"inversion": data.inversion, "rendimiento_predicho": round(float(prediction), 2)}
    except Exception as e:
        logger.error(f"Error al procesar la predicción: {e}")
        raise HTTPException(status_code=500, detail=f"Error interno del servidor: {e}")

# ... (resto del código)

Ahora, cuando hagas solicitudes, verás mensajes informativos en la consola de Uvicorn, lo cual es crucial para depurar y monitorear tu aplicación en un entorno real.

Para variables de entorno (por ejemplo, si el path del modelo cambiara o tuvieras claves API), usarías librerías como python-dotenv. Por simplicidad, lo omitimos aquí, pero es una buena práctica para producción.

Errores Comunes y Depuración

Como desarrollador, te encontrarás con errores. Aquí algunos comunes al servir modelos con FastAPI:

  • ModuleNotFoundError: Significa que te falta una librería. Asegúrate de haber ejecutado pip install -r requirements.txt o de haber instalado todas las dependencias manualmente.
  • Errores de validación de Pydantic (código 422): Si envías un JSON con un formato incorrecto (ej. un string donde se espera un número), FastAPI te lo dirá. Revisa la estructura de tu InputData y el JSON que envías.
  • Modelo no encontrado: Si modelo_regresion.joblib no está en el mismo directorio que main.py, o si el nombre es incorrecto, la API no podrá cargarlo y fallará al iniciar. Asegúrate de que train_model.py se ejecutó correctamente y el archivo existe.
  • Puerto ya en uso: Si intentas ejecutar Uvicorn y otro proceso ya está usando el puerto 8000, obtendrás un error. Puedes cambiar el puerto con uvicorn main:app --port 8001.
  • Errores internos del modelo: Si tu modelo de ML falla durante la predicción (ej. por datos de entrada inesperados), el bloque try-except en el endpoint /predict capturará el error y devolverá un HTTPException 500. Revisa los logs para más detalles.

La documentación interactiva (/docs) y los mensajes de error detallados de FastAPI son tus mejores amigos para la depuración.

Aprendizaje Futuro: ¿Qué Estudiar Después?

Has dado un gran paso al servir tu primer modelo. Aquí hay algunas áreas para profundizar:

  • FastAPI Avanzado: Explora características como autenticación (OAuth2), dependencias, routers, y manejo de errores más sofisticado.
  • Contenedores con Docker: Empaquetar tu aplicación FastAPI y tu modelo en un contenedor Docker es el siguiente paso para la reproducibilidad y el despliegue.
  • Despliegue en la Nube: Aprende a desplegar tu API en plataformas como Render, Heroku, AWS EC2, Google Cloud Run o Azure App Service.
  • Monitoreo y Observabilidad: Implementa herramientas para monitorear el rendimiento de tu API y del modelo en producción (ej. Prometheus, Grafana, Evidently AI).
  • Versionado de Modelos y Datos: Herramientas como MLflow o DVC te ayudan a gestionar diferentes versiones de tus modelos y los datos con los que fueron entrenados.
  • Asincronía en Python: Profundiza en asyncio para entender mejor cómo FastAPI maneja las operaciones concurrentes.

¡Sigue construyendo y experimentando! La mejor manera de aprender es haciendo.