Implémenter le flux RAG
Sur cette page
Le flux RAG est compris ; place au code. Cette leçon le met en œuvre sur un vrai document : découper un rapport, vectoriser ses sections, les ranger dans un index vectoriel, puis chercher les sections les plus proches d’une question.
Deux temps à ne pas confondre
Les cinq étapes se répartissent en deux temps. Les trois premières (découper, vectoriser, stocker) forment l’indexation : elle a lieu une fois, et de nouveau seulement quand les documents changent. Les deux dernières (vectoriser la question, chercher) forment la recherche : elles s’exécutent à chaque question. Recalculer les embeddings du corpus à chaque question est l’erreur de conception la plus coûteuse d’un système RAG.
Un index vectoriel minimal
Le cours officiel fournit sa classe VectorIndex dans un fichier à part. En voici une version complète et courte, en Python pur :
import math
def cosine_similarity(a, b):
dot = sum(x * y for x, y in zip(a, b))
return dot / (math.sqrt(sum(x * x for x in a)) * math.sqrt(sum(y * y for y in b)))
class VectorIndex:
def __init__(self, embed_fn=None):
self.embed_fn = embed_fn
self.vectors = []
self.documents = []
def add_vector(self, vector, document):
self.vectors.append(vector)
self.documents.append(document)
def add_document(self, document):
self.add_vector(self.embed_fn(document["content"], input_type="document"), document)
def search(self, query, k=1):
if isinstance(query, str):
query = self.embed_fn(query, input_type="query")
results = [(doc, 1 - cosine_similarity(query, vector))
for doc, vector in zip(self.documents, self.vectors)]
results.sort(key=lambda pair: pair[1]) # distance croissante : le plus proche d'abord
return results[:k]
L’index garde chaque vecteur avec son document. search accepte un vecteur, ou un texte qu’il vectorise lui-même si on lui a donné une fonction d’embedding ; la leçon 40 se servira de cette seconde forme. Pour quelques milliers de morceaux, ce calcul en Python pur suffit. Au-delà, on passe à NumPy, puis à une vraie base vectorielle, qui sait chercher les plus proches voisins sans tout comparer.
Étape 1 : découper
with open("report.md", encoding="utf-8") as f:
text = f.read()
chunks = chunk_by_section(text)
chunks[2] # pour vérifier un morceau, ici la table des matières
Étape 2 : vectoriser tous les morceaux
embeddings = generate_embedding(chunks, input_type="document")
Une seule ligne pour toute la liste : generate_embedding, écrite à la leçon 36, accepte un texte ou une liste, et envoie une requête par morceau.
Étape 3 : stocker vecteurs et textes
store = VectorIndex(generate_embedding)
for embedding, chunk in zip(embeddings, chunks):
store.add_vector(embedding, {"content": chunk})
On stocke le texte avec le vecteur, et ce n’est pas un détail : un embedding permet de mesurer une proximité, pas de retrouver le texte dont il vient. Sans le texte, la recherche saurait dire « le morceau n° 7 est le plus proche » sans pouvoir dire ce qu’il contient. Une base réelle stocke au moins une référence vers le texte.
Étapes 4 et 5 : vectoriser la question et chercher
question_vector = generate_embedding(
"Qu'a fait le département d'ingénierie logicielle l'année dernière ?", input_type="query"
)
results = store.search(question_vector, 2)
for doc, distance in results:
print(round(distance, 2), doc["content"][:200], "\n")
Lire les résultats
Dans la démonstration du cours officiel, sur son rapport d’exemple, la section 2 (ingénierie logicielle) arrive en tête avec une distance de 0,71, devant la section de méthodologie à 0,72. Deux points à retenir :
- on lit une distance : plus elle est petite, plus le morceau est proche. Un tri décroissant, réflexe habituel pour des scores, renverrait les morceaux les moins pertinents ;
- l’écart entre les deux premiers est faible. La recherche sémantique seule n’est pas toujours tranchée, et les leçons suivantes l’améliorent : recherche lexicale, fusion des classements, reclassement.
À vous de jouer
Un premier pipeline. Prenez un document Markdown de votre choix, découpez-le par section, vectorisez les morceaux avec input_type="document", rangez-les dans un VectorIndex, puis posez trois questions. Comparez les résultats avec k égal à 1, 3 et 5, et notez à partir de quand les morceaux renvoyés cessent d’être utiles.
Sources officielles : Implementing the RAG flow (Claude Academy) · Embeddings (documentation Claude : recherche par produit scalaire)
Points clés à retenir
- Indexation une fois (découper, vectoriser, stocker), recherche à chaque question (vectoriser la question, chercher).
- On stocke le texte avec son vecteur : un embedding ne permet pas de retrouver le texte.
- Les morceaux se vectorisent avec
input_type="document", la question avec"query". - Une distance cosinus se trie par ordre croissant : la plus petite est la meilleure.
- La recherche sémantique seule laisse parfois des écarts faibles : la suite de la section l’améliore.