Implémenter le flux RAG
Sur cette page
Passons à la pratique : une base vectorielle minimale, écrite à la main, puis les étapes du flux RAG sur un vrai document, un rapport report.md découpé en sections.
Une base vectorielle minimale
Le cours officiel fournit une classe VectorIndex dans son notebook. En voici une version courte, qui suffit pour apprendre :
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"]), document)
def search(self, query, k=1):
if isinstance(query, str):
query = self.embed_fn(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]
search renvoie des couples (document, distance cosinus), du plus proche au plus lointain. add_document et la recherche par texte serviront aux leçons suivantes ; embed_fn est la fonction generate_embedding de la leçon 36. Une vraie base vectorielle fait la même chose en beaucoup plus rapide, avec des index qui évitent de comparer la question à chaque vecteur.
Les étapes, dans le code
1. Découper par sections.
with open("report.md", encoding="utf-8") as f:
text = f.read()
chunks = chunk_by_section(text)
2. Vectoriser chaque morceau. Titan traite un texte par appel : cette étape fait autant d’appels que de morceaux, et prend un peu de temps.
embeddings = [generate_embedding(chunk) for chunk in chunks]
3. Stocker vecteurs et textes.
store = VectorIndex(generate_embedding)
for embedding, chunk in zip(embeddings, chunks):
store.add_vector(embedding, {"content": chunk})
C’est le point essentiel de l’étape : on stocke l’embedding et le texte. Une liste de nombres seule ne servirait à rien ; c’est le texte qui ira dans le prompt.
4 et 5. Vectoriser la question et chercher.
question = "Qu'a fait le département d'ingénierie logicielle l'année dernière ?"
results = store.search(generate_embedding(question), 2)
for doc, distance in results:
print(round(distance, 2), doc["content"][:200], "\n")
Dans la démonstration du cours officiel, la section d’ingénierie logicielle arrive en tête avec une distance de 0,71, suivie de la section de méthodologie à 0,72. L’écart est mince : la recherche vectorielle seule n’est pas toujours tranchée, ce que les leçons suivantes amélioreront.
6. Construire le prompt.
context = "\n\n".join(doc["content"] for doc, _ in results)
messages = []
add_user_message(messages, f"""Réponds à la question à partir des extraits du rapport.
<extraits>
{context}
</extraits>
<question>
{question}
</question>""")
print(chat(messages)["text"])
chat est la version de la section Tool Use, qui renvoie un dictionnaire. Les extraits passent avant la question, comme le conseille la leçon 20.
À vous de jouer
Votre premier RAG. Prenez un document Markdown de votre choix, avec des titres de niveau 2. Indexez-le, puis posez trois questions : une dont la réponse tient dans une section, une qui demande deux sections, une qui n’est pas dans le document. Regardez les distances et les réponses : que fait Claude quand les extraits ne contiennent pas la réponse ?
Sources officielles : Implementing the RAG flow (Claude Academy) · Amazon Titan Embeddings Text (documentation AWS)
Points clés à retenir
- Une base vectorielle stocke des vecteurs et le texte qui va avec, puis renvoie les plus proches d’une question.
- Les étapes : découper, vectoriser, stocker, puis vectoriser la question, chercher, construire le prompt.
- Avec Titan, un appel par morceau : l’indexation prend du temps.
- Gardez toujours le texte avec son vecteur : c’est lui qui va dans le prompt.
- Une distance proche entre deux sections signale une recherche peu tranchée.