Depannage - Developpement d'API
Comment utiliser ce guide
Chaque probleme suit la meme structure :
| Section | Description |
|---|---|
| Symptome | Ce que vous voyez (message d'erreur, comportement) |
| Cause | Pourquoi cela arrive |
| Solution | Correction etape par etape |
| Prevention | Comment 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.Pathavec__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 :
- Verifiez que le fichier est un fichier joblib valide :
import joblib
model = joblib.load("models/model_v1.joblib")
print(type(model))
- Verifiez la compatibilite de la version Python :
python --version # doit correspondre a l'environnement d'entrainement
- 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 :
- Accumulation de predictions en memoire (listes de logs qui ne sont jamais videes)
- Creation de nouvelles instances de modele par requete au lieu de reutiliser
- Grands tableaux temporaires non collectes par le ramasse-miettes
- References circulaires dans les objets personnalises
Solution :
- 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
...
- N'accumulez pas de donnees dans des listes globales :
# Mauvais
prediction_log = []
@app.post("/predict")
def predict():
prediction_log.append(result) # grossit indefiniment !
- 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 :
- 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,
}
}
-
Chargement du modele : Charger une seule fois au demarrage (voir Probleme 4)
-
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
- 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/jsonest defini - Ne double-enveloppez pas :
{"data": {"age": 35}}quand le schema attend{"age": 35} - 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 :
- Utilisez moins de workers
- Utilisez un serveur de modeles (TensorFlow Serving, Triton)
- 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'erreur | Cause courante | Correction rapide |
|---|---|---|
| 400 | JSON malforme | Verifiez la syntaxe JSON, ajoutez Content-Type: application/json |
| 404 | Mauvais chemin URL | Verifiez l'URL de l'endpoint, cherchez les fautes de frappe |
| 405 | Mauvaise methode HTTP | Utilisez POST pas GET pour /predict |
| 422 | Echec de validation | Verifiez que les types de donnees correspondent au schema, verifiez les champs obligatoires |
| 500 | Exception non geree | Verifiez les logs du serveur, ajoutez try/except dans le gestionnaire de route |
| 503 | Modele non charge | Verifiez 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 :
- Verifiez les logs du serveur — le message d'erreur y est generalement
- Verifiez l'URL de l'endpoint —
http://, numero de port, chemin - Verifiez la methode HTTP —
POST /predict, pasGET /predict - Verifiez l'en-tete Content-Type —
application/json - Validez votre JSON — utilisez un validateur/linter JSON
- Testez d'abord avec curl — elimine les problemes de navigateur/CORS
- Verifiez le fichier de modele — existe-t-il au chemin attendu ?
- Verifiez les dependances —
pip list | grep scikit-learn - Essayez le Swagger UI —
/docsdans FastAPI - Lisez la trace d'erreur complete — defilez vers le haut dans le terminal
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.