En bref

  • Les embeddings vectoriels transforment du texte en vecteurs numériques denses capturant la sémantique — ils alimentent la recherche sémantique, le RAG et la classification.
  • Python offre un écosystème mature : OpenAI text-embedding-3-large, Sentence Transformers open source, FAISS et ChromaDB pour le stockage vectoriel.
  • Un pipeline RAG complet peut être implémenté en moins de 100 lignes de Python pour répondre à des questions sur des documents internes avec citations sourcées.

Qu'est-ce qu'un embedding vectoriel ?

Un embedding vectoriel est une représentation numérique d'un objet (texte, image, audio) sous forme d'un vecteur de nombres réels dans un espace à haute dimension (typiquement 768 à 3072 dimensions). La propriété fondamentale : deux objets sémantiquement similaires produisent des vecteurs proches mesurables via la similarité cosinus.

Concrètement, "chien" et "canin" auront des vecteurs proches, "chien" et "voiture" seront éloignés. Cette propriété permet la recherche sémantique (retrouver des documents sans correspondance exacte), la déduplication, la classification et surtout les architectures RAG qui donnent à un LLM accès à votre base de connaissance privée.

Les embeddings existent depuis Word2Vec (Google, 2013) et GloVe (Stanford, 2014). Ce qui a changé depuis 2022 : la qualité des modèles transformers et l'émergence des bases de données vectorielles permettant de chercher parmi des millions d'embeddings en millisecondes.

Les modèles d'embedding les plus performants en 2026

Le benchmark MTEB (Massive Text Embedding Benchmark) est la référence pour comparer les modèles en 2026.

Modèle Dimensions Score MTEB Contexte max Prix / 1M tokens
text-embedding-3-large (OpenAI) 3072 64.6 8191 tokens $0.13
text-embedding-3-small (OpenAI) 1536 62.3 8191 tokens $0.02
voyage-3-large (Anthropic) 1024 67.1 16000 tokens $0.06
bge-m3 (BAAI, open source) 1024 62.8 8192 tokens Gratuit
nomic-embed-text-v1.5 (open source) 768 62.4 8192 tokens Gratuit
all-MiniLM-L6-v2 (Sentence Transformers) 384 56.3 256 tokens Gratuit

Recommandation pratique : pour des projets d'entreprise en production avec budget, utilisez voyage-3-large (meilleur MTEB) ou text-embedding-3-large. Pour des projets open source locaux avec données sensibles, bge-m3 ou nomic-embed-text-v1.5 offrent d'excellents résultats sans coût d'API et sans envoyer les données à l'extérieur.

Installation et premiers pas en Python

Commençons par installer les dépendances et générer nos premiers embeddings avec l'API OpenAI et Sentence Transformers.

pip install openai sentence-transformers numpy scikit-learn chromadb faiss-cpu

Avec l'API OpenAI :

from openai import OpenAI
import numpy as np

client = OpenAI(api_key="sk-...")

def get_embedding(text, model="text-embedding-3-small"):
    response = client.embeddings.create(input=[text.replace("\n"," ")], model=model)
    return response.data[0].embedding

def cosine_similarity(v1, v2):
    a, b = np.array(v1), np.array(v2)
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

v1 = get_embedding("comment sécuriser Active Directory ?")
v2 = get_embedding("méthodes pour protéger un contrôleur de domaine Windows")
v3 = get_embedding("recette de tarte aux fraises")

print(f"Sim AD/Windows: {cosine_similarity(v1, v2):.3f}")  # ~0.87
print(f"Sim AD/tarte:   {cosine_similarity(v1, v3):.3f}")  # ~0.15

Avec Sentence Transformers (100% local) :

from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity

model = SentenceTransformer('all-MiniLM-L6-v2')  # ~90MB, téléchargement automatique

sentences = [
    "La gestion des patchs Microsoft est critique pour NIS 2",
    "Le Patch Tuesday corrige des vulnérabilités Windows chaque mois",
    "Les recettes de cuisine françaises sont délicieuses",
]
embeddings = model.encode(sentences)
sims = cosine_similarity(embeddings)
print(f"Sim[patchs/Patch Tuesday]: {sims[0,1]:.3f}")  # ~0.78
print(f"Sim[patchs/cuisine]:       {sims[0,2]:.3f}")  # ~0.05

Bases de données vectorielles : ChromaDB, FAISS et Weaviate

Stocker quelques embeddings dans un dictionnaire Python suffit pour des prototypes. Pour des applications en production avec des millions de documents, il faut une base de données vectorielle qui offre une recherche ANN (Approximate Nearest Neighbor) en millisecondes.

ChromaDB — la solution la plus simple pour des projets Python :

import chromadb
from chromadb.utils import embedding_functions

client = chromadb.PersistentClient(path="./chroma_db")
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
    api_key="sk-...", model_name="text-embedding-3-small"
)
collection = client.get_or_create_collection("articles", embedding_function=openai_ef)

# Indexer des documents
documents = [
    "Le Patch Tuesday juillet 2026 corrige 622 CVE dont 2 zero-days.",
    "L'audit Active Directory identifie les chemins d'attaque Kerberos.",
    "NIS 2 impose une notification d'incident dans les 24h.",
]
collection.add(
    documents=documents,
    ids=["doc_0", "doc_1", "doc_2"],
    metadatas=[{"date": "2026-07"}] * 3
)

# Recherche sémantique
results = collection.query(query_texts=["vulnérabilités Windows juillet"], n_results=2)
for doc, dist in zip(results['documents'][0], results['distances'][0]):
    print(f"[score={1-dist:.3f}] {doc[:80]}")

FAISS — la référence pour les performances sur des millions de vecteurs :

import faiss
import numpy as np

dim = 1536  # text-embedding-3-small
index = faiss.IndexFlatIP(dim)  # Inner Product = cosine si vecteurs normalisés

def normalize(vecs):
    norms = np.linalg.norm(vecs, axis=1, keepdims=True)
    return vecs / norms

# Ajouter des vecteurs (après les avoir générés par batch)
# index.add(normalize(embeddings_matrix))

# Sauvegarder / charger
faiss.write_index(index, "articles.index")
index = faiss.read_index("articles.index")

# Recherche
query_vec = normalize(np.array([get_embedding("vulnérabilité zero-day")], dtype='float32'))
distances, indices = index.search(query_vec, k=5)
print("Top-5 indices:", indices[0])
Base vectorielle Cas d'usage Points forts Limites
ChromaDB Prototypage, petits projets Simple, Python natif, persistance disque Moins performant à grande échelle
FAISS 100M+ vecteurs, in-memory Ultra-rapide, GPU support, Facebook Pas de persistance native, pas de filtres
Qdrant Production, filtres avancés Rust, REST/gRPC, payload filtering Ops supplémentaires
Weaviate Multimodal, GraphQL Auto-vectorization, modules intégrés Configuration complexe
Pgvector PostgreSQL existant Extension PG, pas de nouveau service Moins rapide que solutions dédiées

Implémenter un pipeline RAG complet en Python

Le RAG (Retrieval-Augmented Generation) est l'application la plus impactante des embeddings pour les entreprises. Il donne à un LLM accès à vos documents internes sans les inclure entièrement dans le prompt ni fine-tuner le modèle.

Le pipeline RAG en 4 étapes :

  1. Indexation : découper les documents en chunks, générer les embeddings, stocker dans une base vectorielle
  2. Requête : encoder la question de l'utilisateur en embedding
  3. Retrieval : trouver les N chunks les plus similaires
  4. Génération : injecter les chunks dans le prompt LLM avec la question
from openai import OpenAI
import chromadb
from chromadb.utils import embedding_functions

client = OpenAI(api_key="sk-...")

openai_ef = embedding_functions.OpenAIEmbeddingFunction(api_key="sk-...", model_name="text-embedding-3-small")
chroma = chromadb.PersistentClient(path="./rag_db")
collection = chroma.get_or_create_collection("knowledge_base", embedding_function=openai_ef)

def chunk_text(text, chunk_size=500, overlap=50):
    words = text.split()
    chunks = []
    for i in range(0, len(words), chunk_size - overlap):
        chunk = " ".join(words[i:i+chunk_size])
        if chunk:
            chunks.append(chunk)
    return chunks

def index_document(doc_id, text, metadata=None):
    chunks = chunk_text(text)
    collection.upsert(
        documents=chunks,
        ids=[f"{doc_id}_chunk_{i}" for i in range(len(chunks))],
        metadatas=[metadata or {} for _ in chunks]
    )
    return len(chunks)

def rag_query(question, n_docs=3):
    results = collection.query(query_texts=[question], n_results=n_docs)
    context = "\n\n---\n\n".join(results['documents'][0])
    
    prompt = f"""Tu es un expert cybersécurité. Réponds en français UNIQUEMENT d'après le contexte.
Si la réponse n'y est pas, dis clairement "Je ne sais pas".

CONTEXTE :
{context}

QUESTION : {question}
RÉPONSE :"""
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0.1,
        max_tokens=500
    )
    return response.choices[0].message.content

answer = rag_query("Quels zero-days corrige le Patch Tuesday juillet 2026 ?")
print(answer)

Chunking avancé : stratégies pour améliorer la qualité du RAG

La qualité d'un pipeline RAG dépend à 60% de la stratégie de découpage. Un mauvais chunking produit des résultats incohérents.

Chunking récursif par caractère (défaut LangChain) :

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000, chunk_overlap=200,
    separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = splitter.split_text(long_document)

Parent Document Retriever — petits chunks pour la recherche, grands documents pour le contexte :

from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter

vectorstore = Chroma(embedding_function=OpenAIEmbeddings())
retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=InMemoryStore(),
    child_splitter=RecursiveCharacterTextSplitter(chunk_size=200),
    parent_splitter=RecursiveCharacterTextSplitter(chunk_size=2000),
)

Évaluation et métriques des pipelines RAG avec RAGAS

Mesurer la qualité d'un système RAG est indispensable avant déploiement. RAGAS offre une évaluation automatisée sur 4 dimensions :

from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision, context_recall
from datasets import Dataset

test_data = {
    "question": ["Quels zero-days corrige le Patch Tuesday juillet 2026 ?"],
    "answer": [rag_query("Quels zero-days corrige le Patch Tuesday juillet 2026 ?")],
    "contexts": [["CVE-2026-50656 NEGOEX wormable...", "CVE-2026-42817 CLFS EoP..."]],
    "ground_truth": ["Deux zero-days : CVE-2026-50656 (NEGOEX) et CVE-2026-42817 (CLFS)."]
}

result = evaluate(Dataset.from_dict(test_data),
    metrics=[faithfulness, answer_relevancy, context_precision, context_recall])
print(result)
# faithfulness: 0.95 | answer_relevancy: 0.87 | context_precision: 0.91 | context_recall: 0.83

Règle des 80/80 : viser 80% sur chaque métrique RAGAS. En dessous de 70% sur faithfulness, les hallucinations deviennent inacceptables pour une application d'entreprise.

Cas d'usage concrets en entreprise

Les embeddings et le RAG ont des applications directes et à fort ROI :

1. Base de connaissance interne : indexer procédures, politiques IT et documentation technique dans ChromaDB. Les employés interrogent en langage naturel et obtiennent des réponses sourcées, sans chercher dans un Confluence ou SharePoint désorganisé.

2. Support client automatisé : indexer les tickets résolus et la documentation produit. Un LLM équipé de RAG peut résoudre 60-70% des tickets de premier niveau avec une traçabilité complète.

3. Analyse de conformité : indexer les exigences réglementaires (NIS 2, ISO 27001, DORA) et interroger la base pour vérifier si une mesure de sécurité couvre une exigence spécifique.

4. Clustering et déduplication : comparer les embeddings de tickets support pour détecter les problèmes récurrents, ou de CVEs pour regrouper les vulnérabilités similaires.

from sklearn.cluster import KMeans
from sentence_transformers import SentenceTransformer
from collections import defaultdict

model = SentenceTransformer('all-MiniLM-L6-v2')
articles = ["article 1...", "article 2...", "..."]
embeddings = model.encode(articles)

kmeans = KMeans(n_clusters=10, random_state=42, n_init=10)
labels = kmeans.fit_predict(embeddings)

clusters = defaultdict(list)
for i, label in enumerate(labels):
    clusters[label].append(articles[i][:100])

for cid, texts in sorted(clusters.items()):
    print(f"Theme {cid} ({len(texts)} docs):")
    for t in texts[:2]:
        print(f"  - {t}")

Sécurité et confidentialité des embeddings en entreprise

Avant de déployer un système RAG sur des données sensibles, plusieurs questions de sécurité doivent être adressées. L'envoi de documents internes à une API cloud (OpenAI, Cohere) expose potentiellement des données confidentielles. Les options pour données sensibles :

Option 1 — Embeddings locaux : bge-m3, nomic-embed-text-v1.5 ou all-mpnet-base-v2 via Sentence Transformers. Les données ne quittent jamais votre infrastructure. Idéal pour données RH, médicales, juridiques ou classifiées.

Option 2 — Azure OpenAI Service : les données restent dans votre tenant Azure, avec accords RGPD et data processing agreements. OpenAI ne peut pas accéder aux données via Azure OpenAI.

Option 3 — Anthropic Claude + Amazon Bedrock : même principe, données isolées dans votre compte AWS.

Autre risque souvent négligé : l'inversion d'embeddings. Des recherches académiques (2023-2024) ont montré qu'il est possible de reconstruire partiellement le texte original à partir de ses embeddings, notamment pour des textes courts et répétitifs. Pour des données ultra-sensibles, ne stocker que les embeddings de chunks suffisamment longs (200+ mots) réduit ce risque.

Quelle est la différence entre embeddings et TF-IDF ?

TF-IDF est une représentation sparse basée sur les fréquences de mots : "voiture" et "automobile" sont totalement différents pour TF-IDF car ce ne sont pas les mêmes tokens. Un embedding dense capture la sémantique : ces deux mots auront des vecteurs proches. En pratique, TF-IDF reste utile pour la recherche par mots-clés exacts (noms propres, codes CVE), tandis que les embeddings excellent pour la recherche sémantique. Les moteurs hybrides (Elasticsearch, Weaviate en mode hybrid) combinent les deux pour un maximum de précision et de rappel.

Quelle taille de chunk choisir pour le RAG ?

La taille optimale dépend du cas d'usage. Pour du texte dense (documentation technique), 500 à 1000 tokens par chunk avec 10-20% d'overlap est un bon point de départ. Des chunks trop petits (50-100 tokens) manquent de contexte ; trop grands (2000+ tokens), ils diluent le signal sémantique. Pour OpenAI (8191 tokens max par embedding), indexer avec des chunks de 500 tokens et récupérer les 3 chunks les plus proches injecte 1500 tokens de contexte dans le prompt — laissant de la place pour la question et la réponse dans la limite de contexte du LLM.

Comment éviter les hallucinations dans un pipeline RAG ?

Plusieurs techniques : (1) Citation des sources obligatoire dans le prompt — "cite le passage exact qui justifie ta réponse" ; (2) Température basse (0.0-0.1) pour réduire la créativité ; (3) Prompt de garde-fou "réponds UNIQUEMENT d'après le contexte, dis 'Je ne sais pas' si la réponse n'y est pas" ; (4) Vérification post-génération avec un LLM juge (LLM-as-judge via RAGAS faithfulness). Sur la métrique faithfulness de RAGAS, viser 85% minimum pour une application d'entreprise sensible.

Peut-on utiliser des embeddings open source à la place d'OpenAI ?

Oui, et c'est souvent la meilleure option pour la confidentialité et le coût. bge-m3 (BAAI), nomic-embed-text-v1.5 et e5-mistral-7b-instruct (Microsoft) sont comparables voire supérieurs à text-embedding-3-small sur le benchmark MTEB. Ils s'exécutent localement via sentence-transformers sur un GPU modeste (NVIDIA RTX 3060 suffit pour des inférences batch) ou via Ollama pour un déploiement serveur simple. Pour une PME avec données sensibles, les embeddings locaux éliminent le risque de fuite via les APIs tierces et réduisent le coût d'exploitation à zéro (hors infrastructure).

Comment mettre à jour une base d'embeddings quand les documents changent ?

La mise à jour incrémentale est clé. Maintenir un hash SHA-256 de chaque document indexé. À chaque run du pipeline, comparer les hashs pour identifier les nouveaux documents, modifiés ou supprimés. ChromaDB et Weaviate supportent les opérations upsert (maj ou insertion) et delete par ID. Pour des documents modifiés, supprimer tous les chunks de l'ancien document (par doc_id) avant de réindexer. Un job cron quotidien ou déclenché par webhook (publication d'un nouvel article, modification d'une procédure) maintient la base à jour automatiquement sans réindexer l'intégralité du corpus.

Points clés — Embeddings vectoriels et RAG Python 2026

  • Modèle embedding : voyage-3-large (meilleur MTEB, 67.1) pour production avec budget ; bge-m3 ou nomic-embed-text-v1.5 pour du 100% local et confidentiel sans coût d'API.
  • Chunking optimal : 500-1000 tokens avec 10-20% d'overlap et découpage par unités logiques (paragraphes) plutôt que taille fixe arbitraire.
  • Base vectorielle : ChromaDB pour prototyper, Qdrant ou Weaviate pour la production, Pgvector si PostgreSQL est déjà en place et que l'échelle le permet.
  • Anti-hallucinations RAG : température 0.1, prompt de garde-fou, citations obligatoires et validation RAGAS faithfulness ≥ 80% avant mise en production.
  • Données sensibles : privilégier les embeddings locaux (bge-m3, sentence-transformers) ou Azure OpenAI / Bedrock pour éviter l'envoi de données confidentielles vers des APIs cloud tierces.

Pour approfondir les architectures IA d'entreprise, consultez notre guide des agents IA 2026 et notre article sur la sécurité des LLMs en entreprise. Pour le benchmarking des modèles, voir notre benchmark LLM juillet 2026.