Aller au contenu principal

Depannage - Developpement d'API

Depannage Reference

Comment utiliser ce guide

Chaque probleme suit la meme structure :

SectionDescription
SymptomeCe que vous voyez (message d'erreur, comportement)
CausePourquoi cela arrive
SolutionCorrection etape par etape
PreventionComment l'eviter a l'avenir

Probleme 1 : Erreurs de chargement du modele

FileNotFoundError: Model file not found

Symptome :

FileNotFoundError: [Errno 2] No such file or directory: 'models/model_v1.joblib'

L'API demarre mais plante immediatement ou entre en mode degrade.

Cause :

Le chemin du fichier de modele est relatif au repertoire de travail actuel (ou vous lancez uvicorn ou python), pas relatif au fichier Python. Si vous demarrez le serveur depuis un autre repertoire, le chemin est casse.

Solution :

Utilisez un chemin absolu ou un chemin relatif au script :

from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent
MODEL_PATH = BASE_DIR / "models" / "model_v1.joblib"

ml_service.load_model(str(MODEL_PATH))

Prevention :

  • Utilisez toujours pathlib.Path avec __file__ pour construire les chemins
  • Definissez le chemin du modele via une variable d'environnement : MODEL_PATH=./models/model_v1.joblib
  • Journalisez le chemin resolu au demarrage pour le debogage

ModuleNotFoundError: No module named 'sklearn'

Symptome :

ModuleNotFoundError: No module named 'sklearn'

Survient lors du chargement d'un modele serialise avec scikit-learn.

Cause :

L'environnement ou vous executez l'API n'a pas scikit-learn installe, ou a une version differente de celle utilisee pour entrainer le modele.

Solution :

pip install scikit-learn

En cas de non-correspondance de version :

pip install scikit-learn==1.3.2  # correspondre a la version de l'environnement d'entrainement

Prevention :

  • Fixez les versions exactes dans requirements.txt
  • Utilisez le meme environnement virtuel pour l'entrainement et le service
  • Envisagez le format ONNX pour une serialisation independante du framework

UnpicklingError ou ValueError lors du chargement du modele

Symptome :

_pickle.UnpicklingError: invalid load key, '\x00'
ValueError: unsupported pickle protocol: 5

Cause :

  • Le modele a ete serialise avec une version differente de Python/scikit-learn
  • Le fichier est corrompu ou tronque
  • Mauvais fichier (pas un fichier pickle/joblib valide)

Solution :

  1. Verifiez que le fichier est un fichier joblib valide :
import joblib
model = joblib.load("models/model_v1.joblib")
print(type(model))
  1. Verifiez la compatibilite de la version Python :
python --version  # doit correspondre a l'environnement d'entrainement
  1. Re-serialisez le modele si les versions ne correspondent pas.

Prevention :

  • Documentez les versions de Python et scikit-learn a cote de chaque fichier de modele
  • Utilisez un registre de modeles qui suit les metadonnees

Probleme 2 : Erreurs CORS

Access to fetch has been blocked by CORS policy

Symptome :

La console du navigateur affiche :

Access to fetch at 'http://localhost:8000/api/v1/predict'
from origin 'http://localhost:3000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

L'API fonctionne bien avec curl mais echoue depuis un navigateur.

Cause :

Les navigateurs appliquent la politique de meme origine (Same-Origin Policy). Quand votre frontend (localhost:3000) appelle votre API (localhost:8000), le navigateur bloque la requete sauf si l'API autorise explicitement les requetes cross-origin.

Solution — FastAPI :

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_methods=["GET", "POST"],
allow_headers=["*"],
)

Solution — Flask :

from flask_cors import CORS

CORS(app, origins=["http://localhost:3000"])

Prevention :

  • Configurez toujours CORS au debut de votre projet
  • Testez depuis un navigateur tot, pas seulement avec curl
  • N'utilisez jamais allow_origins=["*"] en production

Le preflight CORS (OPTIONS) echoue

Symptome :

Vous voyez une requete OPTIONS avec une erreur 405 ou 500 dans l'onglet reseau du navigateur, suivie par la vraie requete qui n'est jamais envoyee.

Cause :

Le navigateur envoie une requete OPTIONS preflight avant les requetes POST avec des en-tetes personnalises. Si votre serveur ne gere pas OPTIONS, le preflight echoue et la requete reelle est bloquee.

Solution :

Le middleware CORS gere cela automatiquement. Assurez-vous qu'il est ajoute avant vos routes :

# FastAPI — ajouter le middleware d'abord
app.add_middleware(CORSMiddleware, ...)

# Ensuite definir les routes
@app.post("/predict")
def predict():
...

Probleme 3 : Erreurs de validation (422)

FastAPI retourne 422 pour des donnees apparemment valides

Symptome :

{
"detail": [
{
"loc": ["body", "age"],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}

Mais vous envoyez "age": "35" qui semble correct.

Cause :

Pydantic en mode strict ne convertit pas les chaines en entiers. "35" (chaine) n'est pas la meme chose que 35 (entier) en JSON.

Solution :

Envoyez les bons types JSON :

# Incorrect — age est une chaine
curl -d '{"age": "35", ...}'

# Correct — age est un entier
curl -d '{"age": 35, ...}'

Ou activez la coercion dans Pydantic :

class PredictionInput(BaseModel):
model_config = {"coerce_numbers_to_str": False, "strict": False}
age: int = Field(...)

Prevention :

  • Validez toujours vos payloads JSON (utilisez un validateur JSON)
  • Documentez clairement les types attendus dans la documentation de votre API
  • Testez avec le Swagger UI qui applique les types corrects

En-tete Content-Type manquant

Symptome :

Flask retourne None depuis request.get_json(), ou FastAPI retourne une erreur 422.

Cause :

Le client n'a pas defini Content-Type: application/json.

Solution :

Incluez toujours l'en-tete :

curl -X POST http://localhost:8000/api/v1/predict \
-H "Content-Type: application/json" \
-d '{"age": 35, ...}'

Probleme 4 : Fuites memoire et utilisation elevee de la memoire

La memoire augmente au fil du temps

Symptome :

L'utilisation memoire de l'API (RSS) augmente regulierement sur des heures/jours jusqu'a ce que le processus soit tue par le systeme d'exploitation ou le runtime du conteneur.

Cause :

Causes courantes dans les API ML :

  1. Accumulation de predictions en memoire (listes de logs qui ne sont jamais videes)
  2. Creation de nouvelles instances de modele par requete au lieu de reutiliser
  3. Grands tableaux temporaires non collectes par le ramasse-miettes
  4. References circulaires dans les objets personnalises

Solution :

  1. Assurez-vous que le modele est charge une seule fois et reutilise :
# Mauvais — charge le modele a chaque requete
@app.post("/predict")
def predict():
model = joblib.load("model.joblib") # fuite memoire !
...

# Bon — charger une fois, reutiliser
ml_service = MLService()
ml_service.load_model("model.joblib")

@app.post("/predict")
def predict():
result = ml_service.predict(...) # reutilise le modele charge
...
  1. N'accumulez pas de donnees dans des listes globales :
# Mauvais
prediction_log = []

@app.post("/predict")
def predict():
prediction_log.append(result) # grossit indefiniment !
  1. Surveillez la memoire :
import psutil
import os

@app.get("/debug/memory")
def memory():
process = psutil.Process(os.getpid())
return {"memory_mb": process.memory_info().rss / 1024 / 1024}

Prevention :

  • Surveillez l'utilisation memoire en production (Prometheus, CloudWatch)
  • Definissez des limites memoire dans votre conteneur/gestionnaire de processus
  • Utilisez un service de journalisation dedie au lieu de listes en memoire

Probleme 5 : Predictions lentes

Haute latence sur l'endpoint de prediction

Symptome :

Les predictions prennent 500ms–5s au lieu des 10–50ms attendues.

Cause :

Solution :

  1. Diagnostiquer — ajoutez du chronometrage a votre endpoint :
import time

@app.post("/predict")
def predict(data: PredictionInput):
t0 = time.perf_counter()

t1 = time.perf_counter()
features = preprocess(data)
preprocess_ms = (time.perf_counter() - t1) * 1000

t2 = time.perf_counter()
result = model.predict(features)
predict_ms = (time.perf_counter() - t2) * 1000

total_ms = (time.perf_counter() - t0) * 1000

return {
"result": result,
"timing": {
"preprocess_ms": preprocess_ms,
"predict_ms": predict_ms,
"total_ms": total_ms,
}
}
  1. Chargement du modele : Charger une seule fois au demarrage (voir Probleme 4)

  2. Blocage async : Utilisez def (sync) pour l'inference CPU-bound dans FastAPI :

# Mauvais — bloque la boucle d'evenements
@app.post("/predict")
async def predict(data: PredictionInput):
result = model.predict(...) # CPU-bound, bloquant !

# Bon — FastAPI execute dans le pool de threads
@app.post("/predict")
def predict(data: PredictionInput):
result = model.predict(...) # execute dans le pool de threads
  1. Optimisation du modele : Envisagez des modeles plus legers (arbre de decision vs grand ensemble)

Prevention :

  • Ajoutez des en-tetes de temps de reponse (X-Response-Time-Ms)
  • Definissez des budgets de latence (ex. p95 < 100ms)
  • Profilez avant d'optimiser

Probleme 6 : Erreurs 422 avec des entrees imbriquees/complexes

Pydantic echoue sur les objets imbriques

Symptome :

{
"detail": [{"loc": ["body"], "msg": "value is not a valid dict"}]
}

Cause :

Le client envoie les donnees dans un format inattendu (ex. encode en formulaire au lieu de JSON, ou enveloppe les donnees dans une couche supplementaire).

Solution :

Verifiez ce que le client envoie reellement :

@app.post("/debug")
async def debug(request: Request):
body = await request.body()
return {
"content_type": request.headers.get("content-type"),
"body_raw": body.decode(),
"body_size": len(body),
}

Corrections courantes :

  • Assurez-vous que Content-Type: application/json est defini
  • Ne double-enveloppez pas : &#123;"data": &#123;"age": 35&#125;&#125; quand le schema attend &#123;"age": 35&#125;
  • Verifiez les caracteres BOM dans le corps de la requete

Probleme 7 : Problemes de deploiement

uvicorn refuse les connexions depuis d'autres machines

Symptome :

L'API fonctionne sur localhost mais pas quand on y accede depuis une autre machine ou un conteneur.

Cause :

uvicorn se lie a 127.0.0.1 (localhost uniquement) par defaut.

Solution :

Liez a toutes les interfaces :

uvicorn app.main:app --host 0.0.0.0 --port 8000

OSError: [Errno 98] Address already in use

Symptome :

Impossible de demarrer le serveur car le port est occupe.

Solution :

# Trouver le processus utilisant le port
# Linux/macOS
lsof -i :8000

# Windows
netstat -ano | findstr :8000

# Le tuer
kill <PID> # Linux/macOS
taskkill /PID <PID> /F # Windows

Ou utilisez un port different :

uvicorn app.main:app --port 8001

Workers multiples et chargement du modele

Symptome :

Quand vous executez avec plusieurs workers (uvicorn --workers 4), chaque worker charge le modele separement, causant une utilisation memoire elevee.

Cause :

Chaque worker uvicorn est un processus separe. Le modele est charge dans chacun d'eux.

Solution :

Pour les petits modeles, c'est acceptable. Pour les gros modeles :

  1. Utilisez moins de workers
  2. Utilisez un serveur de modeles (TensorFlow Serving, Triton)
  3. Utilisez la memoire partagee ou des fichiers mappe en memoire
# 4 workers = 4x la memoire du modele
uvicorn app.main:app --workers 4

# Envisagez : 1 worker avec async est-il suffisant ?
uvicorn app.main:app --workers 1

Reference rapide : Code d'erreur → Correction

Code d'erreurCause couranteCorrection rapide
400JSON malformeVerifiez la syntaxe JSON, ajoutez Content-Type: application/json
404Mauvais chemin URLVerifiez l'URL de l'endpoint, cherchez les fautes de frappe
405Mauvaise methode HTTPUtilisez POST pas GET pour /predict
422Echec de validationVerifiez que les types de donnees correspondent au schema, verifiez les champs obligatoires
500Exception non gereeVerifiez les logs du serveur, ajoutez try/except dans le gestionnaire de route
503Modele non chargeVerifiez le chemin du fichier de modele, verifiez les logs de demarrage

Liste de verification du debogage

Quand votre API ne fonctionne pas, suivez cette liste de verification systematique :

  1. Verifiez les logs du serveur — le message d'erreur y est generalement
  2. Verifiez l'URL de l'endpointhttp://, numero de port, chemin
  3. Verifiez la methode HTTPPOST /predict, pas GET /predict
  4. Verifiez l'en-tete Content-Typeapplication/json
  5. Validez votre JSON — utilisez un validateur/linter JSON
  6. Testez d'abord avec curl — elimine les problemes de navigateur/CORS
  7. Verifiez le fichier de modele — existe-t-il au chemin attendu ?
  8. Verifiez les dependancespip list | grep scikit-learn
  9. Essayez le Swagger UI/docs dans FastAPI
  10. Lisez la trace d'erreur complete — defilez vers le haut dans le terminal
Quand tout le reste echoue

Ajoutez un endpoint de debogage qui retourne les informations brutes de la requete :

@app.post("/debug")
async def debug(request: Request):
body = await request.body()
return {
"method": request.method,
"url": str(request.url),
"headers": dict(request.headers),
"body": body.decode("utf-8", errors="replace"),
}

Cela vous dit exactement ce que le serveur recoit, eliminant les suppositions.