Manejo de Excepciones y Respuestas de Error

Lectura
15 min~5 min lectura

Concepto clave

En APIs de producción para machine learning, el manejo de excepciones no es solo capturar errores, sino crear una experiencia robusta para los consumidores de tu API. Piensa en un restaurante de alta cocina: cuando un cliente pide un plato que no está disponible, el chef no se limita a decir "no hay", sino que ofrece alternativas, explica por qué y mantiene la calidad del servicio. Así debe funcionar tu API.

FastAPI proporciona herramientas como HTTPException y exception handlers para gestionar errores de forma estructurada. La clave está en diferenciar entre errores del cliente (código 4xx) y errores del servidor (código 5xx). En ML, esto incluye validación de datos de entrada, fallos en la inferencia del modelo, o problemas de conectividad con bases de datos.

Cómo funciona en la práctica

Imagina que tienes un endpoint que recibe datos para predecir el precio de una casa. Los pasos para implementar manejo de excepciones son:

  1. Definir excepciones personalizadas para errores específicos de tu dominio (ej: ModeloNoCargadoError).
  2. Usar HTTPException para errores HTTP estándar, como datos inválidos.
  3. Registrar exception handlers para capturar excepciones no controladas y devolver respuestas consistentes.
  4. Incluir detalles útiles en las respuestas de error, como un código de error interno y un mensaje claro.

Código en acción

Antes: Un endpoint básico sin manejo de errores.

from fastapi import FastAPI
import numpy as np

app = FastAPI()

# Modelo simulado
def predict_price(features: list):
    # Simula un error si features está vacío
    if not features:
        raise ValueError("Features no pueden estar vacías")
    return np.mean(features) * 1000

@app.post("/predict")
async def predict(features: list):
    prediction = predict_price(features)
    return {"prediction": prediction}

Después: Con manejo avanzado de excepciones.

from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from pydantic import BaseModel
import numpy as np

app = FastAPI()

# Excepción personalizada
class ModelInferenceError(Exception):
    def __init__(self, detail: str):
        self.detail = detail

# Modelo de datos de entrada
class PredictionRequest(BaseModel):
    features: list[float]

# Handler para excepciones personalizadas
@app.exception_handler(ModelInferenceError)
async def model_inference_error_handler(request, exc):
    return JSONResponse(
        status_code=500,
        content={
            "error": "MODEL_INFERENCE_FAILED",
            "message": exc.detail,
            "suggestion": "Verifica los datos de entrada o contacta al soporte."
        }
    )

# Handler para excepciones generales
@app.exception_handler(Exception)
async def general_exception_handler(request, exc):
    return JSONResponse(
        status_code=500,
        content={
            "error": "INTERNAL_SERVER_ERROR",
            "message": "Ocurrió un error inesperado.",
            "detail": str(exc)
        }
    )

# Modelo simulado con manejo de errores
def predict_price(features: list[float]):
    if not features:
        raise HTTPException(status_code=400, detail="La lista de features no puede estar vacía.")
    if len(features) != 5:
        raise HTTPException(status_code=400, detail="Se requieren exactamente 5 features.")
    try:
        # Simula un error en la inferencia
        if sum(features) > 100:
            raise ModelInferenceError("Suma de features excede el límite del modelo.")
        return np.mean(features) * 1000
    except Exception as e:
        raise ModelInferenceError(f"Error en inferencia: {str(e)}")

@app.post("/predict")
async def predict(request: PredictionRequest):
    prediction = predict_price(request.features)
    return {"prediction": prediction, "status": "success"}

Errores comunes

  • Devolver errores genéricos: Usar solo "Internal Server Error" sin detalles. Solución: Incluye códigos de error específicos y mensajes accionables.
  • No validar datos de entrada en profundidad: Confiar solo en Pydantic para validaciones básicas. Solución: Añade validaciones de dominio (ej: rangos de valores para features de ML).
  • Exponer detalles internos en producción: Mostrar trazas de error completas al cliente. Solución: Usa exception handlers para sanitizar respuestas en entornos de producción.
  • Ignorar errores asíncronos: No manejar excepciones en tareas en segundo plano. Solución: Implementa logging y notificaciones para errores en procesos async.
  • No probar escenarios de error: Solo probar el camino feliz. Solución: Crea tests unitarios que simulen fallos de modelo o datos inválidos.

Checklist de dominio

  1. He definido excepciones personalizadas para errores específicos de mi modelo de ML.
  2. Uso HTTPException con códigos de estado apropiados (400 para errores del cliente, 500 para errores del servidor).
  3. He registrado exception handlers para todas las excepciones no controladas.
  4. Las respuestas de error incluyen un código de error, mensaje claro y sugerencia cuando es posible.
  5. He implementado validación de datos de entrada más allá de Pydantic (ej: rangos, formatos específicos para ML).
  6. En producción, los errores se registran en un sistema de monitoreo (ej: Sentry, Loggly).
  7. Tengo tests que cubren escenarios de error comunes (datos inválidos, fallos de modelo).

Implementa un sistema de manejo de errores para un API de clasificación de imágenes

En este ejercicio, crearás un endpoint de FastAPI para un modelo de clasificación de imágenes con manejo robusto de excepciones. Sigue estos pasos:

  1. Crea un proyecto FastAPI con un endpoint POST /classify que reciba una imagen en base64.
  2. Define una excepción personalizada ImageProcessingError para errores en el procesamiento de imágenes.
  3. Implementa validaciones: verifica que el string base64 no esté vacío y que tenga un formato válido (usa una expresión regular simple).
  4. Simula un modelo de ML que pueda fallar: si la imagen es "demasiado grande" (simulado por longitud del string > 1000), lanza ImageProcessingError.
  5. Registra un exception handler para ImageProcessingError que devuelva un JSON con status_code 422, código de error "IMAGE_PROCESSING_FAILED", y un mensaje útil.
  6. Añade un handler general para Exception que devuelva un error 500 sin exponer detalles internos en producción (simula con una variable de entorno).
  7. Prueba tu API con casos de error: string vacío, formato inválido, imagen "demasiado grande".
Pistas
  • Usa el módulo base64 de Python para validar formatos básicos.
  • Para simular el entorno de producción, puedes usar una variable como DEBUG=False.
  • Recuerda que los handlers de excepción se registran con @app.exception_handler.

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.