Tu as passé des heures à entraîner un modèle dans un notebook Jupyter. Il est performant, il est beau, tu es fier. Mais maintenant, comment tu le rends utile ? Comment un autre développeur, une app mobile ou un site web peut-il l'utiliser sans ouvrir ton notebook ? La réponse : une API REST. Et le framework le plus rapide pour ça en Python, c'est FastAPI.
Dans ce tutoriel, on va construire ensemble une API complète qui sert un modèle d'IA. Tu vas apprendre à :
- Créer un endpoint REST qui reçoit des données et renvoie une prédiction
- Gérer la validation automatique des entrées
- Documenter ton API automatiquement
- La rendre prête pour la production
À la fin, tu auras une API qui tourne, accessible depuis n'importe où, et que tu pourras déployer sur un serveur en une commande.
Pourquoi FastAPI plutôt que Flask ?
Si tu as déjà touché à Flask, tu te demandes peut-être pourquoi changer. Voici les trois raisons qui font la différence :
- Validation automatique : FastAPI utilise les types Python natifs (
str,int,bool) et Pydantic pour valider toutes les entrées. Plus besoin d'écrirerequest.json.get("nom")en croisant les doigts. - Documentation auto-générée : Swagger UI et ReDoc sont inclus sans une ligne de configuration. Tes endpoints sont documentés en temps réel.
- Performances : FastAPI est basé sur Starlette et est aussi rapide que Node.js ou Go. Pour un modèle d'IA où la latence compte, c'est crucial.
Étape 1 : Installation de l'environnement
Ouvre ton terminal. On va créer un dossier propre et un environnement virtuel.
# Créer le dossier du projet
mkdir mon-api-ia && cd mon-api-ia
# Créer un environnement virtuel
python3 -m venv venv
# Activer l'environnement
source venv/bin/activate # Linux/Mac
# ou sur Windows : venv\Scripts\activate
# Installer FastAPI et ses dépendances
pip install fastapi uvicorn sentence-transformers numpy💡 Ce que tu dois voir : Une série de packages qui s'installent. L'installation prend environ 30 secondes. Vérifie que tout est ok avec :
python -c "import fastapi; print(f'FastAPI {fastapi.__version__} installé ✓')"Étape 2 : Ton premier endpoint — Hello World
Avant de brancher un modèle, voyons la base. Crée un fichier main.py :
from fastapi import FastAPI
# Créer l'application
app = FastAPI(
title="Mon API IA",
description="API pour servir mon modèle d'intelligence artificielle",
version="1.0.0"
)
@app.get("/")
def racine():
"""Endpoint racine — vérifie que l'API tourne."""
return {
"message": "🚀 API IA opérationnelle !",
"status": "ok",
"version": "1.0.0"
}
@app.get("/health")
def sante():
"""Endpoint de santé pour le monitoring."""
return {"status": "healthy"}Lance le serveur :
uvicorn main:app --reload --host 0.0.0.0 --port 8000💡 Ce que tu dois voir :
INFO: Uvicorn running on http://0.0.0.0:8000
INFO: Started reloader process
INFO: Started server processOuvre maintenant ton navigateur et va sur http://localhost:8000/docs. Tu vois une interface Swagger complète, générée automatiquement ! Teste l'endpoint / directement depuis cette page en cliquant sur "Try it out" puis "Execute".
🎉 Félicitations ! Tu viens de créer ta première API documentée automatiquement. Et on n'a pas écrit une seule ligne de documentation manuelle.
Étape 3 : Brancher un vrai modèle d'IA
Maintenant, passons aux choses sérieuses. On va intégrer un modèle de sentence-transformers qui convertit du texte en vecteurs (embeddings). C'est un modèle très utilisé pour la recherche sémantique, le clustering, et les recommandations.
Crée un fichier modele.py qui gère le chargement du modèle :
"""
Gestion du modèle d'IA.
On le charge UNE SEULE FOIS au démarrage (pas à chaque requête).
"""
from sentence_transformers import SentenceTransformer
# Chargement lazy — le modèle est téléchargé uniquement quand on l'appelle
_modele = None
def charger_modele() -> SentenceTransformer:
"""Charge le modèle une seule fois (cache)."""
global _modele
if _modele is None:
print("📦 Téléchargement du modèle... (premier appel uniquement)")
# all-MiniLM-L6-v2 : petit, rapide, efficace pour commencer
_modele = SentenceTransformer("all-MiniLM-L6-v2")
print("✅ Modèle chargé !")
return _modele
def generer_embedding(texte: str) -> list[float]:
"""Convertit un texte en vecteur de 384 dimensions."""
modele = charger_modele()
vecteur = modele.encode(texte, normalize_embeddings=True)
return vecteur.tolist()Maintenant, ajoute les endpoints dans main.py. Remplace le contenu précédent par :
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from modele import generer_embedding
app = FastAPI(
title="API d'Embeddings IA",
description="Transforme du texte en vecteurs sémantiques via Sentence Transformers",
version="1.0.0"
)
# ==================== SCHÉMAS (Validation automatique) ====================
class RequeteTexte(BaseModel):
"""Ce que l'utilisateur envoie."""
texte: str = Field(
...,
min_length=1,
max_length=2000,
description="Le texte à vectoriser",
example="L'intelligence artificielle transforme le monde."
)
class ReponseEmbedding(BaseModel):
"""Ce que l'API renvoie."""
embedding: list[float] = Field(description="Vecteur de 384 dimensions")
dimensions: int = Field(description="Nombre de dimensions du vecteur")
texte_original: str = Field(description="Texte source reçu")
# ==================== ENDPOINTS ====================
@app.get("/")
def racine():
return {
"message": "🚀 API d'Embeddings IA opérationnelle",
"endpoints": {
"docs": "/docs",
"embedding": "/embedding (POST)",
"batch": "/embedding/batch (POST)"
}
}
@app.get("/health")
def sante():
return {"status": "healthy"}
@app.post("/embedding", response_model=ReponseEmbedding)
def creer_embedding(requete: RequeteTexte):
"""
Convertit un texte unique en vecteur d'embedding.
- **texte** : Le texte à vectoriser (1 à 2000 caractères)
"""
try:
vecteur = generer_embedding(requete.texte)
return ReponseEmbedding(
embedding=vecteur,
dimensions=len(vecteur),
texte_original=requete.texte
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
class RequeteBatch(BaseModel):
"""Requête par lot — plusieurs textes."""
textes: list[str] = Field(
...,
min_length=1,
max_length=100,
description="Liste de textes à vectoriser (max 100)",
example=["Bonjour", "Hello world", "L'IA c'est génial"]
)
class ReponseBatch(BaseModel):
embeddings: list[list[float]]
nombre_textes: int
dimensions: int
@app.post("/embedding/batch", response_model=ReponseBatch)
def creer_embeddings_batch(requete: RequeteBatch):
"""
Convertit plusieurs textes en une seule requête.
- **textes** : Liste de 1 à 100 textes
"""
try:
vecteurs = [generer_embedding(t) for t in requete.textes]
return ReponseBatch(
embeddings=vecteurs,
nombre_textes=len(vecteurs),
dimensions=len(vecteurs[0])
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))💡 Ce que tu dois comprendre :
BaseModelde Pydantic définit la forme exacte des données attendues. Si l'utilisateur envoie un nombre à la place d'un texte, FastAPI rejette automatiquement avec un message clair.response_modelindique à FastAPI quoi documenter dans Swagger comme réponse.- Le modèle n'est chargé qu'une seule fois grâce au pattern de cache dans
modele.py. Sans ça, il serait rechargé à chaque requête.
Relance le serveur (il devrait se recharger automatiquement avec --reload) :
uvicorn main:app --reload --host 0.0.0.0 --port 8000💡 Ce que tu dois voir : Au premier appel POST, le terminal affiche "📦 Téléchargement du modèle..." (environ 15-20 secondes la première fois). Les appels suivants sont instantanés.
Étape 4 : Tester ton API
Test avec Swagger UI
Va sur http://localhost:8000/docs, clique sur le endpoint POST /embedding, puis "Try it out". Colle ce JSON :
{
"texte": "FastAPI est le framework le plus rapide pour servir des modèles d'IA en Python."
}Clique "Execute". Tu devrais voir une réponse avec un vecteur de 384 nombres. C'est ton embedding !
Test avec curl
curl -X POST http://localhost:8000/embedding \
-H "Content-Type: application/json" \
-d '{"texte": "Bonjour le monde de l'\''IA !"}'💡 Ce que tu dois voir : Un JSON avec embedding, dimensions: 384, et texte_original.
Test avec Python
import requests
reponse = requests.post(
"http://localhost:8000/embedding",
json={"texte": "Mon premier appel API réussi !"}
)
data = reponse.json()
print(f"Dimensions : {data['dimensions']}")
print(f"Premières valeurs : {data['embedding'][:5]}...")Étape 5 : Validation automatique — magique
Essaie d'envoyer une requête invalide pour voir la validation automatique en action :
# Envoi d'un nombre au lieu d'un texte
curl -X POST http://localhost:8000/embedding \
-H "Content-Type: application/json" \
-d '{"texte": 123}'💡 Ce que tu dois voir : Une erreur 422 avec un message détaillé expliquant exactement ce qui ne va pas. FastAPI ne laisse rien passer — et tout ça sans une ligne de validation manuelle.
Étape 6 : Ajouter un middleware de logging
Pour avoir une API digne de la production, ajoutons un middleware qui logue chaque requête. Ajoute en haut de main.py après les imports :
import time
from fastapi import Request
# ... après app = FastAPI(...) ...
@app.middleware("http")
async def log_requetes(request: Request, call_next):
"""Middleware qui mesure le temps de chaque requête."""
debut = time.time()
reponse = await call_next(request)
duree = time.time() - debut
print(f"📊 {request.method} {request.url.path} → {reponse.status_code} ({duree:.3f}s)")
return reponseRelance et fais quelques requêtes. Tu verras dans le terminal :
📊 GET /docs → 200 (0.002s)
📊 POST /embedding → 200 (0.015s)C'est un middleware de logging basique, mais tu peux le faire évoluer vers du monitoring Prometheus, du rate limiting, ou de l'authentification.
Pièges classiques
1. Recharger le modèle à chaque requête
C'est l'erreur la plus courante. Si tu mets model = SentenceTransformer(...) directement dans ton endpoint, le modèle sera rechargé à chaque appel → mémoire saturée, latence de 20 secondes par requête. Solution : utilise le pattern de cache comme dans modele.py.
2. Oublier le `response_model`
Sans response_model, Swagger ne sait pas quoi afficher comme schéma de réponse. Ta doc sera vide côté réponse. Mets toujours le bon type.
3. Bloquer le serveur avec des traitements lourds
Si ton endpoint fait un traitement de 5 secondes, il bloque toute autre requête. Solution : utilise async def et BackgroundTasks, ou passe par une queue (Celery, Redis) pour les traitements longs.
4. Démarrer sur le port 80 sans droits
Sur Linux, les ports < 1024 nécessitent root. Utilise --port 8000 (ou un autre port > 1024) pour le développement.
5. Oublier de figer les dépendances
Quand tu déploies, assure-toi d'avoir un requirements.txt exact :
pip freeze > requirements.txtPour aller plus loin
1. Ajoute l'authentification par token
FastAPI intègre OAuth2 et JWT nativement. En 10 lignes, tu peux protéger tes endpoints avec un simple token API :
from fastapi import Security
from fastapi.security import APIKeyHeader
api_key_header = APIKeyHeader(name="X-API-Key")
def verifier_cle(api_key: str = Security(api_key_header)):
if api_key != "ma-cle-secrete": # à remplacer par une vraie vérification
raise HTTPException(status_code=403, detail="Clé API invalide")
return api_key
# Puis ajoute dependencies=[Security(verifier_cle)] sur ton endpoint2. Passe à un vrai serveur de production
Uvicorn avec --reload est parfait pour le dev, mais en production utilise :
gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:appÇa lance 4 workers qui gèrent les requêtes en parallèle — indispensable pour servir plusieurs utilisateurs simultanément.
3. Déploie sur Hugging Face Spaces (gratuit)
Crée un fichier Dockerfile :
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]Pousse sur un Space Hugging Face et ton API est en ligne gratuitement en 5 minutes.
💡 Idée d'illustration : Un diagramme montrant le flux complet — requête HTTP → FastAPI → validation Pydantic → modèle Sentence Transformers → embedding → réponse JSON. Avec un petit robot qui passe du texte au vecteur, style « de la phrase au vecteur magique ».
Voilà, tu as une API d'IA professionnelle et documentée ! En 20 minutes, tu es passé d'un notebook statique à un endpoint REST que n'importe qui peut appeler. C'est la différence entre un projet perso et un produit qui peut servir des utilisateurs réels. La prochaine fois que quelqu'un te demande « comment j'utilise ton modèle ? », tu lui enverras juste une URL Swagger.

