Aller au contenu principal

Implémentation du flux RAG

Mis à jour le 30 juillet 2026

Après avoir compris le flux RAG conceptuellement, passons à l’implémentation concrète. Nous allons construire un exemple complet qui démontre comment découper du texte, générer des embeddings, les stocker dans une base vectorielle et effectuer des recherches par similarité.

Les cinq étapes de l’implémentation

Ces cinq étapes se répartissent en deux temps qu’il faut bien distinguer, parce qu’ils ne se produisent pas au même moment. Les trois premières — découper, vectoriser, stocker — constituent l’indexation : elle a lieu une fois, en amont, quand vos documents changent. Les deux dernières constituent la recherche : elles s’exécutent à chaque question posée.

Confondre les deux mène à l’erreur de conception la plus coûteuse en RAG — recalculer les embeddings du corpus à chaque requête, ce qui multiplie la facture et la latence par le nombre de documents.

  1. Découper le texte par sections
  2. Générer les embeddings pour chaque chunk
  3. Créer un store vectoriel et y ajouter chaque embedding
  4. Générer un embedding pour la question de l’utilisateur
  5. Chercher dans le store les chunks les plus pertinents

Étape 1 : Découpage du texte

On charge le document et on le découpe en sections :

# Charger le document
with open("./report.md", "r") as f:
    text = f.read()

# Decouper par sections (reutilise la fonction vue précédemment)
chunks = chunk_by_section(text)
chunks[2]  # Vérifier le contenu d'un chunk (ici la table des matières)

On réutilise la fonction chunk_by_section de la leçon précédente pour diviser le document en sections logiques.

Étape 2 : Génération des embeddings

On crée les embeddings pour tous les chunks en une seule fois :

# Générer les embeddings pour tous les chunks d'un coup
embeddings = generate_embedding(chunks)

La fonction d’embedding a été adaptée pour accepter aussi bien une chaîne unique qu’une liste de chaînes, ce qui rend le traitement par lots plus efficace.

Étape 3 : Stockage dans la base vectorielle

On crée notre store vectoriel et on le remplit avec les embeddings et le texte associé :

# Créer le store vectoriel
store = VectorIndex()

# Ajouter chaque embedding avec son contenu textuel original
for embedding, chunk in zip(embeddings, chunks):
    store.add_vector(embedding, {"content": chunk})

Remarque importante : on stocke à la fois l’embedding et le texte original. C’est essentiel car lors de la recherche, on a besoin de récupérer le texte réel, pas juste les valeurs numériques de l’embedding.

Pourquoi stocker le texte original ?

La question mérite d’être posée explicitement, car l’oubli est fréquent et se paie cher. Un embedding est un vecteur de nombres : il permet de mesurer une proximité, il ne permet pas de reconstituer le texte dont il provient. L’opération n’est pas réversible. Si vous ne stockez que les vecteurs, votre recherche saura dire « le chunk n° 7 est le plus proche » et vous n’aurez aucun moyen de savoir ce qu’il contient. On a besoin du texte qui a servi à générer ces embeddings. C’est pourquoi on associe le chunk de texte original (ou au minimum une référence vers celui-ci) à chaque embedding dans la base.

Étape 4 : Traitement des requêtes utilisateur

Quand un utilisateur pose une question, on génère un embedding pour sa requête :

# Convertir la question en embedding
user_embedding = generate_embedding(
    "Qu'a fait le département ingénierie logicielle l'année dernière ?"
)

Étape 5 : Recherche du contenu pertinent

On interroge le store vectoriel pour trouver les chunks les plus similaires :

# Chercher les 2 chunks les plus proches
results = store.search(user_embedding, 2)

# Afficher les résultats avec leur score de distance
for doc, distance in results:
    print(distance, "\n", doc["content"][0:200], "\n")

Cette recherche retourne les deux chunks les plus pertinents avec leurs scores de similarité (distances cosinus).

Comprendre les résultats

Pour notre requête sur le département ingénierie logicielle, on obtient :

ChunkDistance cosinus
Section 2 : Ingénierie logicielle0.71 (meilleure correspondance)
Section Méthodologie0.72 (deuxième correspondance)

Attention au sens de lecture, qui est contre-intuitif et source d’erreurs de tri : on manipule ici une distance, pas un score de pertinence. Plus le nombre est petit, plus le chunk est proche de la question. Un sort() naïf en ordre décroissant, réflexe habituel quand on classe des scores, renverrait donc exactement les passages les moins pertinents. La Section 2 est donc le résultat le plus pertinent pour notre requête.

Et ensuite ?

Cette implémentation fonctionne bien pour les cas basiques, mais il existe des scénarios où elle ne donne pas les résultats attendus. Dans les leçons suivantes, nous explorerons des améliorations pour rendre notre système RAG plus robuste et précis.

Le point clé à retenir : la RAG consiste fondamentalement à convertir du texte en nombres (embeddings), à stocker ces nombres efficacement, puis à utiliser la similarité mathématique pour trouver le contenu pertinent quand les utilisateurs posent des questions.

Exercice : Implémentez votre propre pipeline RAG

En utilisant les extraits de code de cette leçon, construisez un pipeline RAG complet :

  1. Prenez un document Markdown de votre choix (un README, un article de blog, etc.)
  2. Découpez-le en chunks par sections
  3. Générez les embeddings avec VoyageAI
  4. Stockez-les dans un VectorIndex
  5. Posez 3 questions différentes et observez les résultats

Bonus : Comparez les résultats en changeant le nombre de chunks retournés (1, 3, 5). À quel moment les résultats deviennent-ils moins pertinents ?