Ce que vous saurez faire
A la fin de ce tutoriel, vous comprendrez ce qu’est un embedding (representation vectorielle d’un texte) et vous saurez stocker des milliers de documents dans une base vectorielle, soit Pinecone (cloud) soit Qdrant (auto-hebergee). Vous construirez la fondation technique d’un moteur de recherche semantique pour votre PME senegalaise : trouver des produits par sens et non par mots-cles, deduplicer une base clients, ou recommander des articles similaires. Coute Pinecone : 0 FCFA pour 100 000 vecteurs. Coute Qdrant : gratuit en local sur un VPS 6000 FCFA par mois.
Etape 1 : Comprendre les embeddings en 30 secondes
Un embedding transforme un texte en liste de nombres (par exemple 384 ou 1536 chiffres). Deux textes au sens proche ont des vecteurs proches dans l’espace mathematique. « Acheter un velo a Dakar » et « Achat bicyclette region Dakar » donnent des vecteurs presque identiques, alors qu’ils n’ont aucun mot commun. C’est ce qui permet la recherche semantique, bien superieure aux mots-cles classiques.
Etape 2 : Choisir entre Pinecone et Qdrant
Pinecone : SaaS americain, 0 administration, plan gratuit jusqu’a 100 000 vecteurs. Ideal pour demarrer. Qdrant : open-source, vous l’hebergez sur votre VPS, donnees 100 pour cent en Afrique, scalable a des millions de vecteurs sans surcout. Pour ce tutoriel on traite les deux, vous gardez le code que vous voulez.
Etape 3 : Installer les dependances Python
python -m venv venv
source venv/bin/activate # ou venv\Scripts\activate sur Windows
pip install pinecone-client==5.0.1
pip install qdrant-client==1.11.1
pip install sentence-transformers==3.0.1
pip install python-dotenv==1.0.1
Etape 4 : Generer un embedding test
Le modele all-MiniLM-L6-v2 est leger (90 Mo) et rapide. Il produit des vecteurs de 384 dimensions :
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
texte = "Panneau solaire 200W garantie 24 mois"
vecteur = model.encode(texte)
print(f"Taille du vecteur : {len(vecteur)}")
print(f"Premiers chiffres : {vecteur[:5]}")
Pour un meilleur support du francais, utilisez paraphrase-multilingual-mpnet-base-v2 (768 dimensions, 1,1 Go).
Etape 5 : Preparer un jeu de donnees realiste
Imaginons un catalogue de 8 produits d’une PME a Dakar :
produits = [
{"id": 1, "nom": "Panneau solaire 200W monocristallin", "prix_fcfa": 145000},
{"id": 2, "nom": "Onduleur hybride 3kVA avec batterie lithium", "prix_fcfa": 850000},
{"id": 3, "nom": "Regulateur de charge MPPT 60A", "prix_fcfa": 95000},
{"id": 4, "nom": "Lampe LED solaire portable 10W", "prix_fcfa": 12500},
{"id": 5, "nom": "Cable solaire 6mm rouge 100m", "prix_fcfa": 75000},
{"id": 6, "nom": "Batterie gel 200Ah 12V deep cycle", "prix_fcfa": 320000},
{"id": 7, "nom": "Pompe solaire immergee 1HP", "prix_fcfa": 425000},
{"id": 8, "nom": "Kit eclairage maison 4 pieces", "prix_fcfa": 185000}
]
Etape 6 : Vectoriser tout le catalogue
noms = [p["nom"] for p in produits]
vecteurs = model.encode(noms, show_progress_bar=True)
print(f"{len(vecteurs)} vecteurs generes")
Pour 10 000 produits, comptez 2 minutes sur un PC standard.
Etape 7 : Option A – Pousser dans Pinecone
Creez un compte sur pinecone.io, generez une cle API. Puis :
from pinecone import Pinecone, ServerlessSpec
import os
pc = Pinecone(api_key=os.environ["PINECONE_API_KEY"])
pc.create_index(
name="catalogue-pme",
dimension=384,
metric="cosine",
spec=ServerlessSpec(cloud="aws", region="us-east-1")
)
index = pc.Index("catalogue-pme")
vecteurs_a_inserer = [
(str(p["id"]), v.tolist(), {"nom": p["nom"], "prix": p["prix_fcfa"]})
for p, v in zip(produits, vecteurs)
]
index.upsert(vectors=vecteurs_a_inserer)
print("Index Pinecone rempli")
Etape 8 : Option B – Lancer Qdrant en local avec Docker
docker run -d -p 6333:6333 -p 6334:6334 \
-v $(pwd)/qdrant_storage:/qdrant/storage \
--name qdrant qdrant/qdrant
L’interface web tourne sur http://localhost:6333/dashboard pour visualiser vos collections.
Etape 9 : Pousser dans Qdrant
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
client = QdrantClient(host="localhost", port=6333)
client.recreate_collection(
collection_name="catalogue_pme",
vectors_config=VectorParams(size=384, distance=Distance.COSINE)
)
points = [
PointStruct(
id=p["id"],
vector=v.tolist(),
payload={"nom": p["nom"], "prix": p["prix_fcfa"]}
)
for p, v in zip(produits, vecteurs)
]
client.upsert(collection_name="catalogue_pme", points=points)
print("Collection Qdrant remplie")
Etape 10 : Lancer une recherche semantique
Un client cherche « lumiere autonome pour panne ELEC ». Aucun mot ne matche directement les noms de produits, mais le sens oui :
requete = "lumiere autonome pour panne electrique"
v_requete = model.encode(requete).tolist()
# Avec Qdrant
resultats = client.search(
collection_name="catalogue_pme",
query_vector=v_requete,
limit=3
)
for r in resultats:
print(f"{r.score:.3f} - {r.payload['nom']} - {r.payload['prix']} FCFA")
Resultat attendu en tete : la lampe LED solaire portable et le kit eclairage maison.
Etape 11 : Filtrer par metadonnees
On cherche un produit similaire mais avec un budget maximum :
from qdrant_client.models import Filter, FieldCondition, Range
resultats = client.search(
collection_name="catalogue_pme",
query_vector=v_requete,
query_filter=Filter(
must=[FieldCondition(key="prix", range=Range(lte=100000))]
),
limit=5
)
Tres utile pour un site e-commerce : « Articles similaires a moins de 100 000 FCFA ».
Etape 12 : Mettre a jour un produit
Quand un nom change ou un prix evolue, on re-upsert avec le meme ID :
nouveau_nom = "Panneau solaire 200W monocristallin Tier 1 garantie 25 ans"
nouveau_v = model.encode(nouveau_nom).tolist()
client.upsert(
collection_name="catalogue_pme",
points=[PointStruct(
id=1,
vector=nouveau_v,
payload={"nom": nouveau_nom, "prix": 155000}
)]
)
Etape 13 : Sauvegarder et restaurer Qdrant
Pour la production en Afrique avec coupures electriques frequentes :
# Backup
client.create_snapshot(collection_name="catalogue_pme")
# La snapshot est dans ./qdrant_storage/collections/catalogue_pme/snapshots/
# A copier sur un disque externe ou Wasabi (S3 africain)
# Restauration
client.recover_snapshot(
collection_name="catalogue_pme",
location="file:///chemin/vers/snapshot.snapshot"
)
Etape 14 : Comparer les couts mensuels
Pinecone Starter : 0 FCFA jusqu’a 100 000 vecteurs et 5 Go. Au-dela, plan Standard a 70 USD soit 42 000 FCFA. Qdrant auto-heberge : VPS Hetzner CX22 a 4 EUR soit 2700 FCFA pour 80 Go disque, supporte 1 million de vecteurs facilement. Pour une PME en croissance, Qdrant gagne vite.
Erreurs
Erreur 1 : Mismatch de dimensions. Si votre modele genere 384 dimensions, l’index doit etre cree avec dimension=384. Sinon erreur a l’insertion.
Erreur 2 : Mauvais choix de metrique. Pour les embeddings de phrases, utilisez COSINE et non EUCLIDEAN. Resultats 30 pour cent meilleurs.
Erreur 3 : Tout regenerer a chaque ajout. Vectorisez seulement les nouveaux produits, pas tout le catalogue.
Erreur 4 : Modele anglais sur du francais. Le modele all-MiniLM-L6-v2 est purement anglais. Pour du francais wolofise ou des termes locaux, prenez paraphrase-multilingual-mpnet-base-v2.
Erreur 5 : Oublier les metadonnees. Sans payload, vous obtenez des IDs sans contexte. Stockez toujours nom, prix, categorie, URL.
Checklist
- Choix architecture : Pinecone (rapide a demarrer) ou Qdrant (souverainete des donnees)
- Modele d’embedding multilingue installe en local
- Dimension du vecteur identique entre modele et index
- Metrique cosine configuree
- Cle API Pinecone dans .env si Pinecone choisi
- Conteneur Docker Qdrant lance et persistant si Qdrant choisi
- Au moins 100 documents tests inseres avec metadonnees completes
- Recherche semantique testee avec 10 requetes formulees differemment
- Filtres par prix, categorie, stock fonctionnels
- Procedure de mise a jour d’un point documentee
- Snapshots automatiques planifies (cron quotidien)
- Sauvegarde externe sur S3 ou disque hors site
- Cout mensuel projete a 1 an valide par la direction
- Plan de migration entre Pinecone et Qdrant prepare au cas ou