Fermer

janvier 14, 2025

Un guide du débutant sur la génération augmentée par récupération (RAG) —

Un guide du débutant sur la génération augmentée par récupération (RAG) —


Les LLM nous ont permis de traiter de grandes quantités de données textuelles de manière très efficace, fiable et rapide. L’un des cas d’utilisation les plus populaires apparus au cours des deux dernières années est la génération augmentée par récupération (RAG).

RAG nous permet de prendre un certain nombre de documents (de quelques à cent mille), de créer une base de données de connaissances avec les documents, puis de l’interroger et de recevoir des réponses avec des sources pertinentes basées sur les documents.

Au lieu d’avoir à effectuer une recherche manuelle, ce qui prendrait des heures, voire des jours, nous pouvons demander à un LLM de nous rechercher avec seulement quelques secondes de latence.

Basé sur le cloud ou local

Le fonctionnement d’un système RAG comporte deux parties : la base de données de connaissances et le LLM. Considérez le premier comme une bibliothèque et le second comme un commis de bibliothèque très efficace.

La première décision de conception lors de la création d’un tel système est de savoir si vous souhaitez l’héberger dans le cloud ou localement. Les déploiements locaux présentent un avantage en termes de coûts à grande échelle et contribuent également à protéger votre confidentialité. D’un autre côté, le cloud peut offrir de faibles coûts de démarrage et peu ou pas de maintenance.

Dans le but de démontrer clairement les concepts autour de RAG, nous opterons pour un déploiement cloud au cours de ce guide, mais laisserons également des notes sur le passage au local à la fin.

La base de données de connaissances (vecteurs)

La première chose que nous devons faire est donc de créer une base de données de connaissances (techniquement appelée base de données vectorielles). Pour ce faire, nous exécutons les documents via un modèle d’intégration qui créera un vecteur à partir de chacun d’eux. Les modèles d’intégration sont très efficaces pour comprendre le texte et les vecteurs générés auront des documents similaires plus rapprochés dans l’espace vectoriel.

C’est incroyablement pratique, et nous pouvons l’illustrer en traçant les vecteurs de quatre documents d’une organisation hypothétique dans un espace vectoriel 2D :

Comme vous le voyez, les deux documents RH ont été regroupés, et sont loin des autres types de documents. Maintenant, la façon dont cela nous aide est que lorsque nous recevons une question concernant les RH, nous pouvons calculer un vecteur d’intégration pour cette question, qui se retrouvera également proche des deux documents RH.

Et par un simple calcul de distance euclidienne, nous pouvons faire correspondre les documents les plus pertinents à remettre au LLM pour qu’il puisse répondre à la question.

Il existe une vaste gamme d’algorithmes d’intégration parmi lesquels choisir, tous comparé au classement MTEB. Un fait intéressant ici est que de nombreux modèles open source prennent la tête par rapport aux fournisseurs propriétaires comme OpenAI.

Outre le score global, deux autres colonnes à prendre en compte dans ce classement sont la taille du modèle et le nombre maximum de jetons de chaque modèle.

La taille du modèle déterminera la quantité de V(RAM) nécessaire pour charger le modèle en mémoire ainsi que la rapidité des calculs d’intégration. Chaque modèle ne peut intégrer qu’un certain nombre de jetons, de sorte que les fichiers très volumineux devront peut-être être divisés avant d’être intégrés.

Enfin, les modèles ne peuvent intégrer que du texte, donc tous les PDF devront être convertis et les éléments riches comme les images doivent être soit sous-titrés (à l’aide d’un modèle de légende d’image IA), soit supprimés.

Les modèles d’intégration locale open source peuvent être exécuté localement à l’aide de transformateurs. Pour le modèle d’intégration OpenAI, vous aurez besoin d’un Clé API OpenAI à la place.

Voici le code Python pour créer des intégrations à l’aide de l’API OpenAI et d’une simple base de données vectorielles basée sur le système de fichiers Pickle :

import os
from openai import OpenAI
import pickle


openai = OpenAI(
  api_key="your_openai_api_key"
)


directory = "doc1"

embeddings_store = {}

def embed_text(text):
    """Embed text using OpenAI embeddings."""
    response = openai.embeddings.create(
        input=text,
        model="text-embedding-3-large" 
    )
    return response.data[0].embedding

def process_and_store_files(directory):
    """Process .txt files, embed them, and store in-memory."""
    for filename in os.listdir(directory):
        if filename.endswith(".txt"):
            file_path = os.path.join(directory, filename)
            with open(file_path, 'r', encoding='utf-8') as file:
                content = file.read()
                embedding = embed_text(content)
                embeddings_store[filename] = embedding
                print(f"Stored embedding for {filename}")

def save_embeddings_to_file(file_path):
    """Save the embeddings dictionary to a file."""
    with open(file_path, 'wb') as f:
        pickle.dump(embeddings_store, f)
        print(f"Embeddings saved to {file_path}")

def load_embeddings_from_file(file_path):
    """Load embeddings dictionary from a file."""
    with open(file_path, 'rb') as f:
        embeddings_store = pickle.load(f)
        print(f"Embeddings loaded from {file_path}")
        return embeddings_store


process_and_store_files(directory)


save_embeddings_to_file("embeddings_store.pkl")


LLM

Maintenant que nous avons les documents stockés dans la base de données, créons une fonction pour obtenir les 3 documents les plus pertinents en fonction d’une requête :

import numpy as np

def get_top_k_relevant(query, embeddings_store, top_k=3):
    """
    Given a query string and a dictionary of document embeddings,
    return the top_k documents most relevant (lowest Euclidean distance).
    """
    query_embedding = embed_text(query)

    distances = []
    for doc_id, doc_embedding in embeddings_store.items():
        dist = np.linalg.norm(np.array(query_embedding) - np.array(doc_embedding))
        distances.append((doc_id, dist))

    distances.sort(key=lambda x: x[1])

    return distances[:top_k]




Et maintenant que nous avons les documents vient la partie simple, qui incite notre LLM, GPT-4o dans ce cas, à donner une réponse basée sur eux :

from openai import OpenAI


openai = OpenAI(
  api_key="your_openai_api_key"
)














def answer_query_with_context(query, doc_store, embeddings_store, top_k=3):
    """
    Given a query, find the top_k most relevant documents and prompt GPT-4o
    to answer the query using those documents as context.
    """
    best_matches = get_top_k_relevant(query, embeddings_store, top_k)

    context = ""
    for doc_id, distance in best_matches:
        doc_content = doc_store.get(doc_id, "")
        context += f"--- Document: {doc_id} (Distance: {distance:.4f}) ---\n{doc_content}\n\n"

    completion = openai.chat.completions.create(
        model="gpt-4o",
        messages=[
            {
                "role": "system",
                "content": (
                    "You are a helpful assistant. Use the provided context to answer the user’s query. "
                    "If the answer isn't in the provided context, say you don't have enough information."
                )
            },
            {
                "role": "user",
                "content": (
                    f"Context:\n{context}\n"
                    f"Question:\n{query}\n\n"
                    "Please provide a concise, accurate answer based on the above documents."
                )
            }
        ],
        temperature=0.7 
    )

    answer = completion.choices[0].message.content
    return answer





Conclusion

Et voilà ! Il s’agit d’une implémentation intuitive de RAG avec beaucoup de marge d’amélioration. Voici quelques idées sur les prochaines étapes :




Source link