Aller au contenu
Développement medium

Embeddings : transformer du texte en vecteurs

10 min de lecture

logo python

Un RAG ne cherche pas par mots-clés, mais par sens : la question « comment garder les données d'un conteneur ? » doit retrouver un document qui parle de volumes Docker, sans aucun mot en commun. La brique qui rend cela possible est l'embedding : un modèle transforme chaque texte en vecteur de nombres, de sorte que deux textes au sens proche aient des vecteurs proches. Ce guide montre comment générer des embeddings avec un modèle servi par Ollama, mesurer leur proximité avec la similarité cosinus, construire une recherche sémantique, et choisir un modèle. Public visé : développeur qui veut comprendre le cœur du retrieval d'un RAG.

  • Ce qu'est un embedding et pourquoi il rend la recherche sémantique possible.
  • Générer un vecteur à partir d'un texte avec Ollama.
  • Mesurer la proximité de deux vecteurs avec la similarité cosinus.
  • Construire une recherche sémantique sur un petit corpus.
  • Choisir un modèle d'embedding selon la langue et le besoin.
  • Python 3.10+.
  • Une instance Ollama avec le modèle d'embedding nomic-embed-text.
  • Avoir un corpus découpé en chunks à vectoriser.

Un ordinateur ne « comprend » pas un texte — il manipule des nombres. Un embedding est le pont : un modèle, entraîné sur d'énormes quantités de texte, transforme un passage en un vecteur — une liste de quelques centaines de nombres.

La propriété qui rend ce vecteur utile : il encode le sens. Deux textes qui parlent de la même chose, même avec des mots différents, produisent des vecteurs proches dans l'espace. Deux textes sans rapport produisent des vecteurs éloignés. « Sauvegarder un volume » et « conserver les données d'un conteneur » se retrouvent voisins, alors qu'aucun mot ne les relie.

C'est tout le mécanisme du RAG : on vectorise les documents une fois, à l'indexation ; à chaque question, on vectorise la question et on cherche les vecteurs les plus proches. La recherche par sens remplace la recherche par mot exact.

Ollama sert des modèles d'embedding comme il sert des modèles de chat. On l'interroge via son endpoint compatible OpenAI.

from openai import OpenAI
CLIENT = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
MODELE = "nomic-embed-text"
def vectoriser(texte: str) -> list[float]:
"""Transforme un texte en vecteur (embedding)."""
reponse = CLIENT.embeddings.create(model=MODELE, input=texte)
return reponse.data[0].embedding

Le modèle nomic-embed-text produit un vecteur de 768 nombres. Cette dimension est une caractéristique du modèle — elle est toujours la même, quel que soit le texte d'entrée, court ou long. Elle compte pour la suite : la base vectorielle qui stockera ces vecteurs devra être configurée avec exactement cette dimension.

Reste à mesurer si deux vecteurs sont « proches ». La mesure standard est la similarité cosinus : elle regarde l'angle entre deux vecteurs, en ignorant leur longueur.

import math
def similarite_cosinus(a: list[float], b: list[float]) -> float:
"""Mesure la proximité de deux vecteurs — 1 identique, 0 sans rapport."""
produit = sum(x * y for x, y in zip(a, b))
norme_a = math.sqrt(sum(x * x for x in a))
norme_b = math.sqrt(sum(y * y for y in b))
if norme_a == 0 or norme_b == 0:
return 0.0
return produit / (norme_a * norme_b)

Le résultat se lit simplement. Une valeur de 1 signifie deux vecteurs identiques en direction — des textes au sens très proche. 0 signifie aucun rapport. -1, des sens opposés. En pratique, sur des textes réels, on compare des scores : un chunk à 0,68 est plus pertinent qu'un chunk à 0,41.

Ce calcul est de l'arithmétique pure — produit scalaire divisé par le produit des normes. Aucun modèle n'intervient ici : c'est déterministe, et c'est testable seul.

Avec ces deux briques — vectoriser et comparer —, une recherche sémantique tient en quelques lignes. On indexe le corpus une fois, puis on classe les documents par similarité avec la question.

def indexer(textes: list[str]) -> list[dict]:
"""Vectorise une liste de textes en documents prêts pour la recherche."""
return [{"texte": t, "vecteur": vectoriser(t)} for t in textes]
def rechercher(question: str, corpus: list[dict], k: int = 3) -> list[dict]:
"""Classe les documents du corpus par similarité avec la question."""
v_question = vectoriser(question)
classes = sorted(
corpus,
key=lambda doc: similarite_cosinus(v_question, doc["vecteur"]),
reverse=True,
)
return classes[:k]

Sur un corpus de quelques phrases — volumes Docker, Kubernetes, Python —, la question « comment garder les données d'un conteneur ? » remonte les bons documents :

Question : Comment garder les données d'un conteneur ?
[0.687] Pour sauvegarder un volume, on lance un conteneur temporaire.
[0.630] Un volume Docker conserve les données après la suppression...

La phrase sur Python est restée loin derrière : elle n'a aucun rapport de sens, malgré quelques mots français communs. C'est exactement le comportement recherché.

Le modèle d'embedding est un choix structurant : il décide de la qualité du retrieval et impose sa dimension. Trois critères le guident.

La langue d'abord. Un modèle entraîné surtout sur de l'anglais traite mal le français. Pour un corpus francophone, il faut un modèle multilingue explicite — c'est non négociable.

La taille ensuite. Un modèle plus gros capte des nuances plus fines, mais il est plus lent et produit des vecteurs plus longs — donc un index plus lourd. Pour la plupart des corpus de documentation, un modèle de taille moyenne suffit largement.

L'hébergement enfin. Un modèle servi par Ollama — comme nomic-embed-text — tourne en local, sans clé d'API ni donnée envoyée à un tiers. C'est cohérent avec une formation qui privilégie l'auto-hébergement.

BesoinPiste de modèle
Local, multilingue, polyvalentnomic-embed-text (Ollama)
Anglais, rapide, prototypageall-MiniLM (sentence-transformers)
Multilingue, haute qualitéBAAI/bge-m3 (sentence-transformers)
SymptômeCause probableSolution
Connection refused sur le port 11434Ollama non démarréLancer ollama serve
model not foundModèle d'embedding non téléchargéollama pull nomic-embed-text
Erreur de dimension à l'indexationModèle changé sans réindexerRéindexer tout le corpus avec le nouveau modèle
Recherche peu pertinente en françaisModèle non multilingueChoisir un modèle multilingue
Scores de similarité tous prochesCorpus trop homogène ou chunks trop grosAffiner le chunking en amont
  • Un embedding transforme un texte en vecteur qui encode son sens.
  • Deux textes proches par le sens ont des vecteurs proches, même sans mot commun.
  • La similarité cosinus mesure cette proximité — un calcul pur, déterministe.
  • Une recherche sémantique vectorise la question et classe le corpus par similarité.
  • La dimension du vecteur est fixée par le modèle et doit concorder avec la base vectorielle.
  • Le même modèle vectorise question et documents ; en changer impose de réindexer.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn