Context Engineering — Section 4 : Embeddings
Encodage sémantique pour la recherche par le sens : choix modèle · dimension & distance · normalisation · index ANN · namespaces · coût/latence · monitoring.
4. Embeddings — Guide opérationnel
De la théorie à la prod : matrice de choix, schémas, figures, snippets, checklists & playbook.
Sommaire — Panorama · Modèles · Dimension & distance · Normalisation · Index ANN · Namespaces · Coûts/latence · Monitoring · Pitfalls · Snippets · Schémas · Playbook
- A. Panorama
- B. Modèles (matrice de choix)
- C. Dimension & distance
- D. Normalisation
- E. Index ANN & réglages
- F. Namespaces & migrations
- G. Coûts & latence
- H. Monitoring & QA
- I. Anti-patterns
- J. Snippets (Python/SQL)
- K. Schémas JSON
- L. Playbook déploiement
But
Projeter des textes dans un espace vectoriel où la proximité ≈ similarité sémantique. Supporte paraphrases, synonymes, et langues multiples.
Enjeux
Qualité de rappel/précision, coût & mémoire de l’index, latence, versionning sûr, explainability (citations).
Pipeline
Chunking → Encodage → Index ANN → Hybride (BM25+vecteur) → RRF → (Re-ranking).
[Texte] ──► [Chunking] ─► [Encoder f_θ] ─► v∈ℝᵈ ─► [ANN Index] ─► {top-k}
└── métadonnées (lang, section, version, tenant, access, permalink)
Figure 1 — Taille d’index vs dimension
Toujours loguer model_id, dim, normalize, namespace pour chaque lot d’indexation.
Matrice de choix (exemples)
| Famille | Dim | Forces | Limites | Use-cases typiques |
|---|---|---|---|---|
| Multilingue (FR/EN) | 512 | Polyglotte, rapide, index compact | Rappel moindre vs 768+ | FAQ, support nivelé, help-desk |
| Bi-encodeur FR/EN | 768 | Bon compromis précision/mémoire | Stockage ↑ | Docs internes, RAG généraliste |
| Domain-tuned (code, juridique) | 768–1024 | Signal fort sur domaine | Coût de fine-tune / licensing | Compliance, runbooks SRE, lecture de code |
Évaluer sur ton gold set (100–300 Q/R réelles) : relevance@k, MRR, nDCG, p95(latence).
Figure 2 — Qualité vs coût (indicatif)
Dimension
384/512 = compact & rapide · 768/1024 = rappel ↑ mais index ↑.
Distance
Cosine (si vecteurs normalisés), Dot (non normalisés), L2 (moins courant pour texte).
Règle
Ne mélange jamais des colonnes normalisées et non normalisées dans la même base.
Taille index ≈ N_chunks × dim × 4o (float32) → float16 / PQ pour réduire ×2 / ×4 (tester le rappel !)
Figure 3 — Rappel@10 vs dim (corpus interne)
Si domain drift futur (nouveaux sujets), préfère 768 (marge de rappel) + index HNSW.
Cosine
Utiliser des vecteurs L2-normalisés (||v||=1). Score borné ∈[-1,1].
Dot product
Plus rapide, scores non bornés ; comparer uniquement au sein du même modèle.
import numpy as np
def l2_normalize(V: np.ndarray) -> np.ndarray:
n = np.linalg.norm(V, axis=1, keepdims=True); n[n==0]=1
return V / nChanger la normalisation = nouveau namespace (sinon des scores incohérents casseront votre ranking).
Comparatif rapide
| Moteur | Mémoire | Latence | Qualité | Notes |
|---|---|---|---|---|
| HNSW | ↑↑ | ms | ↑↑ | Parfait pour top-k faible ; tuning M, efSearch |
| IVF-PQ | ↓ | ms~dizaines | ↑ (si bien tuné) | Compact ; choisir nlist/nprobe |
| Flat (brut) | ↑↑↑ | lent | référence | Pour éval/ablation uniquement |
Paramètres clés (ex.) : - M (degrés du graphe) : 16–48 → mémoire vs qualité - efConstruction : 100–400 → temps de build vs rappel - efSearch : 50–200 → latence vs rappel
Fixer des **SLAs** : p95(query) < 120ms ; ajuster efSearch jusqu’au point d’équilibre.
IVF-PQ : - nlist (nb centroides) : ~√N - nprobe (centroides scannés) : 4–16 (latence vs rappel) - m (nb sous-vecteurs PQ) : dépend de dim (ex. 64 pour dim=768)
Augmenter nprobe améliore le rappel mais peut doubler la latence.
La quantization (float16, Product-Quantization) réduit fortement l’empreinte mémoire.
Toujours valider l’impact rappel@k sur le **gold set** avant full-prod.
Namespace
kb-2025.10-embedX-768-cosine (date, modèle, dim, distance)Dual-write
Encoder en parallèle dans “old” & “new” puis router 10→25→50→100 % du trafic.
Rollback
Simple : re-router vers l’ancien namespace si dégradation.
{
"namespace": "kb-2025.10-embedX-768-cosine",
"model": "embedX",
"dim": 768, "normalize": true,
"created_at": "2025-10-01T10:00:00Z",
"notes": "migration depuis 512-dot"
}Documenter une model card interne (jeu d’éval, métriques, hyper-params, risques).
Coût total
C_total ≈ C_encode + C_stockage + C_queryBatching
Encodage par lots 64–256, parallélisme par files.
Cache
Cache requêtes & résultats top-k ; invalider par TTL ou par doc_id “touché”.
Figure 4 — Latence vs efSearch (HNSW)
Surveiller aussi la **fragmentation d’index** (ré-build périodique si besoin).
- Relevance@k, MRR, nDCG par domaine/langue.
- Freshness (âge moyen des items) · Coverage (répartition tags/sections).
- Latence p50/p95 + taux d’erreurs (timeouts).
- Index health : taille, fragmentation, load, rebuild time.
Tracer model_id, namespace, query hash, scores → RCA rapide.
- Mélanger des vecteurs de modèles/normalisations différents dans un même namespace.
- Indexer du HTML bruité (menus/pieds) → “garbage in, garbage out”.
- Chunks trop gros (précision↓) ou trop petits (bruit↑).
- Oublier les métadonnées (lang, section, version, access).
- Re-encoder “en place” un grand corpus (pas de rollback possible).
Pas de migration sans dual-write + A/B + rollback.
# Encodage batché + normalisation optionnelle
import numpy as np
def encode_corpus(chunks, model, norm=True, batch=128):
out=[]
for i in range(0,len(chunks),batch):
X = model.embed(chunks[i:i+batch]) # (B,d)
if norm:
X = X/np.linalg.norm(X,axis=1,keepdims=True)
out.append(X)
return np.vstack(out)# Recherche cosine (vecteurs normalisés)
def cosine_search(q_vec, index, top=50):
q = q_vec/np.linalg.norm(q_vec)
ids, scores = index.similarity_search(q, top)
return list(zip(ids, scores))-- Vue BM25 FR pour hybride (lexical + vecteur)
CREATE MATERIALIZED VIEW embed_bm25 AS
SELECT doc_id, title,
to_tsvector('french', coalesce(title,'')||' '||coalesce(summary,'')||' '||coalesce(content,'')) AS tsv,
metadata->>'lang' AS lang,
metadata->>'section' AS section,
metadata->>'permalink' AS permalink
FROM chunks;{
"id":"conf:123#05",
"vector":[0.12,-0.03,...],
"metadata":{
"namespace":"kb-2025.10-embedX-768-cosine",
"doc_id":"conf:123",
"section":"Procédure",
"lang":"fr","version":"v2025.3",
"permalink":"https://kb/123#05",
"access":["role:sre","dept:ops"]
}
}Rendre le permalink cliquable côté UX pour afficher la preuve.
- Échantillonner 10–20 % du corpus, créer un gold set (100–300 Q/R réelles).
- Comparer 2 modèles (512 vs 768) + 2 index (HNSW vs IVF-PQ).
- Choisir un gagnant par domaine ; définir le namespace cible.
- Encoder en batchs + bâtir l’index ; fixer SLA (p95 <= 120ms).
- Dual-write & A/B routing (10→25→50→100 %).
- Basculer en prod ; surveiller metrics & coûts ; plan de rollback prêt.
Documenter décisions, risques, paramètres et résultats d’A/B dans un README versionné.
Context Engineering · Embeddings
