
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 que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- 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.
Prérequis
Section intitulée « Prérequis »- Python 3.10+.
- Une instance Ollama avec le modèle d'embedding
nomic-embed-text. - Avoir un corpus découpé en chunks à vectoriser.
Qu'est-ce qu'un embedding
Section intitulée « Qu'est-ce qu'un embedding »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.
Générer un embedding
Section intitulée « Générer un embedding »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].embeddingLe 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.
Mesurer la proximité : la similarité cosinus
Section intitulée « Mesurer la proximité : la similarité cosinus »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.
Construire une recherche sémantique
Section intitulée « Construire une recherche sémantique »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é.
Choisir un modèle d'embedding
Section intitulée « Choisir un modèle d'embedding »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.
| Besoin | Piste de modèle |
|---|---|
| Local, multilingue, polyvalent | nomic-embed-text (Ollama) |
| Anglais, rapide, prototypage | all-MiniLM (sentence-transformers) |
| Multilingue, haute qualité | BAAI/bge-m3 (sentence-transformers) |
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause probable | Solution |
|---|---|---|
Connection refused sur le port 11434 | Ollama non démarré | Lancer ollama serve |
model not found | Modèle d'embedding non téléchargé | ollama pull nomic-embed-text |
| Erreur de dimension à l'indexation | Modèle changé sans réindexer | Réindexer tout le corpus avec le nouveau modèle |
| Recherche peu pertinente en français | Modèle non multilingue | Choisir un modèle multilingue |
| Scores de similarité tous proches | Corpus trop homogène ou chunks trop gros | Affiner le chunking en amont |
À retenir
Section intitulée « À retenir »- 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.