Génération augmentée par récupération

Avez-vous déjà rencontré un scénario comme celui-ci :

  • Vous avez un manuel de produit de plusieurs centaines de pages, et un utilisateur pose une question très spécifique. Vous cherchez longtemps mais vous ne trouvez pas où se trouve la réponse.

  • Votre entreprise a accumulé plusieurs années de documents internes, de comptes rendus de réunion et de spécifications techniques. Les nouveaux employés qui veulent comprendre une règle métier particulière n'ont aucune idée d'où commencer à chercher.

  • Vous avez une pile d'ebooks, de rapports de recherche et d'articles, et vous voulez que l'IA réponde à des questions en se basant sur ce contenu, mais si vous collez le livre entier directement à l'IA, la fenêtre de contexte ne pourra pas le contenir.

C'est le problème que RAG vise à résoudre.

RAG (Génération augmentée par récupération) est une technologie qui permet aux grands modèles de langage de répondre à des questions à partir de bases de connaissances spécifiques.。

L'idée centrale de RAG est simple : d'abord récupérer le contenu le plus pertinent de votre base de connaissances, puis envoyer ce contenu avec la question de l'utilisateur au LLM, et laisser le LLM répondre en fonction de ce contexte.

Sans RAG, le LLM ne connaît que ce qui se trouve dans ses données d'entraînement ; avec RAG, le LLM peut utiliser vos données privées et à jour.


Pourquoi RAG est-il nécessaire ?

RAG n'est pas la seule solution à ces problèmes, mais c'est actuellement l'une des options les plus pratiques et les moins coûteuses.

Problème de date de coupure des connaissances des LLM.

  • Tous les LLM ont une date de coupure des connaissances—les données d'entraînement ne vont que jusqu'à un certain point dans le temps, et ils ne connaissent pas les événements postérieurs.

  • Par exemple, la date de coupure des connaissances de GPT-4 est octobre 2023. Si vous l'interrogez sur des nouvelles de 2024, il ne peut que répondre « Je ne sais pas. »

  • Plus important encore, vos données privées (documents internes, manuels produits, politiques d'entreprise) n'ont jamais figuré dans les données d'entraînement du LLM—comment pourrait-il bien les connaître ?

Les données privées ne peuvent pas être directement saisies dans un LLM.

Vous pourriez penser : ne puis-je pas simplement coller les documents directement dans le LLM ?

La réponse est : les documents courts fonctionnent, mais pas les documents longs.

  • Premièrement, la fenêtre de contexte du LLM est limitée. Par exemple, GPT-3.5 ne dispose que de 16K tokens, soit environ 10,000 caractères. Un manuel de plusieurs centaines de pages ne rentrera tout simplement pas.

  • Deuxièmement, même si la fenêtre de contexte est suffisamment grande, insérer tout le document ne fonctionne pas bien. Les LLM ont tendance à se perdre dans les textes longs et à ne pas trouver les informations réellement pertinentes.

  • Enfin, il y a le coût. GPT-4 avec un contexte de 128K coûte 10 $ par million de tokens d’entrée. Si vous fourrez un livre entier à chaque fois, votre portefeuille va pleurer.

RAG vs Fine-tuning : comment choisir

Une autre façon de fournir de nouvelles données à un LLM est le fine-tuning — continuer à entraîner le modèle avec de nouvelles données.

Mais le fine-tuning et le RAG sont fondamentalement différents, avec des cas d’utilisation entièrement différents :

Élément de comparaisonRAGFine-tuning
Fraîcheur des donnéesPeut être mis à jour à tout moment, immédiatement utilisableNécessite un réentraînement, cycle long
Volume de données applicableTrès important (millions de documents)Moyen (de milliers à dizaines de milliers d’échantillons)
Source de citationPeut indiquer de quel document provient la réponseImpossible de retracer la source
Problème d'hallucinationRéduit les hallucinations en fournissant du contextePeut encore produire des hallucinations
Modification des connaissancesIl suffit de supprimer ou de mettre à jour des documentsNécessite un réentraînement, difficile à « oublier »
Barrière techniqueRelativement faible, peut utiliser des frameworks existantsRelativement élevée, nécessite un GPU et de l'expérience en entraînement
CoûtPrincipalement une base de données vectorielle et EmbeddingCoût d'entraînement élevé, et coût d'inférence élevé également

Une règle empirique simple : si vous voulez que le LLM « connaisse » certaines connaissances factuelles (comme les descriptions de produits, les politiques d'entreprise), utilisez RAG ; si vous voulez que le LLM « apprenne » un certain style ou une certaine capacité (comme le style d'écriture, les conventions de code), utilisez le fine-tuning.


Aperçu de l'architecture RAG

RAG se divise en deux phases : la phase d'indexation hors ligne et la phase de récupération en ligne.

Tout d'abord, regardons un schéma d'architecture complet :

RAG 整体架构流程图

Étape d'indexation hors ligne (préparation des données)

Cette phase s'exécute en arrière-plan et les utilisateurs ne la voient pas directement.

Sa tâche consiste à traiter vos documents dans un format qui peut être récupéré rapidement.

Les étapes sont les suivantes :

  • 1. Chargement des documents: Lire des documents dans différents formats tels que PDF, TXT, DOCX et pages web.

  • 2. Découpage des documents: Diviser les documents longs en segments de texte plus petits, généralement de quelques centaines à un millier de mots.

  • 3. Vectorisation: Utiliser un modèle d'Embedding pour convertir chaque segment de texte en un vecteur (une suite de nombres).

  • 4. Stockage: Stocker les vecteurs et le texte original ensemble dans une base de données vectorielle.

Cette phase ne doit être effectuée qu'une seule fois, ou réexécutée lorsque les documents sont mis à jour.

Étape de récupération en ligne (requête utilisateur)

Cette phase se déroule en temps réel lorsque l'utilisateur pose une question.

Les étapes sont les suivantes :

  • 1. Vectorisation de la question: Utilisez le même modèle d'Embedding pour convertir également la question de l'utilisateur en vecteur.

  • 2. Recherche par similarité: Trouvez les segments de texte les plus proches du vecteur de la question dans la base de données vectorielle.

  • 3. Construction du Prompt: Utilisez les segments de texte récupérés comme contexte et assemblez-les avec la question de l'utilisateur pour former un Prompt.

  • 4. Génération par LLM: Envoyez le Prompt au LLM et laissez-le répondre en fonction du contexte.

  • 5. Renvoyer la réponse: Renvoyez la réponse du LLM à l'utilisateur, généralement accompagnée des sources documentaires citées.


Introduction aux bases de données vectorielles

La base de données vectorielle est l'un des composants essentiels de RAG. Pour la comprendre, vous devez d'abord comprendre ce qu'est un vecteur.

Qu'est-ce qu'un vecteur (Embedding) ?

Un vecteur (Embedding) est une chaîne de nombres convertie à partir de texte.

Par exemple, le mot "chat" pourrait être converti en un vecteur comportant plusieurs centaines de dimensions comme [0.23, -0.45, 0.12, 0.89, ...].

Le point clé est :Les textes ayant une sémantique similaire auront des vecteurs proches dans l'espace.。

Par exemple :

  • La distance vectorielle entre "chat" et "minou" est très faible.
  • La distance vectorielle entre "chat" et "chien" est plus faible que celle entre "chat" et "voiture".
  • La distance vectorielle entre "J'aime les chats" et "J'adore les chats" est très faible.

C'est le fondement de la "recherche sémantique" — une correspondance par le sens, et non par mot-clé.

Le principe de calcul de la similarité sémantique.

La similarité entre deux vecteurs est généralement calculée à l'aide de la "similarité cosinus".

L'intervalle de la similarité cosinus est de -1 à 1 :

  • 1 signifie exactement la même chose
  • 0 signifie sans rapport
  • -1 signifie complètement opposé

En pratique, on ne s'intéresse généralement qu'à la similarité positive ; plus elle est proche de 1, plus c'est pertinent.

Lorsque vous posez une question, la base de données vectorielle calcule rapidement la similarité entre le vecteur de la question et tous les vecteurs de documents, puis renvoie les quelques résultats les plus pertinents.

Base de données vectorielle courante

Il existe de nombreuses bases de données vectorielles sur le marché, chacune avec ses propres caractéristiques :

Base de donnéesTypeFonctionnalitésCas d'utilisation
ChromaLocal/Open sourceLéger, facile à utiliser, adapté à PythonPrototypage, applications à petite échelle
PineconeService cloud/SaaSService managé, aucune opération requise, mise à l'échelle élastiqueEnvironnements de production, applications à grande échelle
WeaviateOpen source/ManagéRiche en fonctionnalités, prend en charge GraphQLScénarios nécessitant des fonctionnalités avancées
QdrantOpen source/ManagéHautes performances, écrit en RustScénarios avec des exigences de hautes performances
MilvusOpen source/ManagéFonctionnalités complètes, de niveau entrepriseApplications d'entreprise à grande échelle
FAISSBibliothèque localeDe Facebook, extrêmement rapideScénarios où la persistance n'est pas nécessaire

Pour les débutants, nous recommandons de commencer avec Chroma — il est facile à installer, ne nécessite aucune configuration supplémentaire et est très adapté à l'apprentissage et au prototypage.


Flux de traitement des documents

Le traitement des documents est une étape de RAG souvent négligée, mais en réalité très importante.

La qualité de votre récupération dépend en grande partie de la qualité du traitement des documents.

Formats de documents pris en charge.

LangChain prend en charge de nombreux formats de documents :

  • Texte brut : .txt, .md
  • PDF:.pdf
  • Office : .docx, .pptx, .xlsx
  • Pages web : HTML, URL
  • Code : .py, .js, .java, etc.
  • JSON:.json, .jsonl
  • CSV:.csv

Chaque format possède un chargeur correspondant, ce qui rend son utilisation très pratique.

Stratégie de segmentation des documents

Le découpage de documents est le processus qui consiste à diviser de longs documents en morceaux plus petits.

Cela semble simple, mais il y a de nombreuses nuances :

  • Trop petit : peut perdre le contexte, un sens complet est tronqué
  • Trop grand : peut contenir des informations non pertinentes, gaspillant la fenêtre de contexte

Stratégies de découpage courantes :

StratégiePrincipeScénarios applicables
Par nombre de caractèresDiviser simplement par nombre de caractères, par ex., tous les 500 caractères par segmentScénarios simples avec de faibles exigences de contexte
Par paragrapheDiviser par caractères de nouvelle ligne, en gardant les paragraphes aussi intacts que possibleDocuments bien formatés
Découpage récursifPrivilégiez d'abord les paragraphes, puis les phrases si c'est trop long, et enfin les motsLe premier choix pour la plupart des scénarios
Découpage sémantiqueDécouper en fonction de la similarité sémantique afin de garantir la complétude sémantiqueScénarios avec des exigences sémantiques élevées

Dans la plupart des cas, utiliser RecursiveCharacterTextSplitter de LangChain suffit.

Conseils pour choisir la taille de segmentation

Chunk Size est un paramètre qui doit être ajusté selon le scénario.

Quelques directives de référence :

  • Scénarios de questions-réponses : 500-1000 caractères, suffisamment pour contenir une paire question-réponse complète
  • Scénarios de résumé : 1000-2000 caractères, davantage de contexte est nécessaire
  • Scénarios de code : possibilité de découper par fonction ou classe, en gardant les blocs de code intacts

Il existe également un paramètre important : Overlap — le nombre de caractères qui se chevauchent entre des blocs de texte adjacents.

Le chevauchement peut empêcher des informations importantes d'être scindées entre deux segments ; il est généralement défini entre 10%-20% de la taille du segment.

Ne poursuivez pas aveuglément des paramètres de découpage « optimaux ». Commencez par 500 caractères et un chevauchement de 50, testez les résultats réels, puis ajustez.


Modèle d'embedding

Un modèle d'embedding est un modèle qui convertit le texte en vecteurs.

Sa qualité affecte directement les performances de récupération — un bon modèle d'embedding peut comprendre précisément la sémantique et classer les documents pertinents plus haut.

Qu'est-ce qu'un modèle d'embedding ?

Un modèle d'embedding est un réseau de neurones pré-entraîné qui prend du texte en entrée et produit des vecteurs.

Son objectif d'entraînement est : pour des textes ayant une sémantique similaire, les vecteurs de sortie doivent aussi être similaires.

Par exemple :

  • « Comment apprendre Python » et « méthodes d'apprentissage de Python » → les vecteurs sont proches
  • « Les chats sont mignons » et « Les chiots sont mignons » → les vecteurs sont un peu éloignés mais restent relativement proches
  • « Comment apprendre Python » et « Les chats sont mignons » → la distance vectorielle est très grande

Série OpenAI text-embedding-3

OpenAI propose actuellement les modèles d'embedding les plus populaires :

ModèleDimensionsPrix (par million de tokens)Fonctionnalités
text-embedding-3-small1536$0.02Très bon rapport coût-efficacité, suffisant pour la plupart des scénarios
text-embedding-3-large3072$0.13De meilleures performances, adapté aux besoins de haute précision

text-embedding-3-small est actuellement le meilleur choix—il est plus performant que la génération précédente text-embedding-ada-002 et moins cher.

Modèle d'embedding open source

Si vous ne souhaitez pas utiliser OpenAI, ou si vous avez des exigences de confidentialité des données, il existe également de nombreuses options open source :

ModèleDimensionsFonctionnalités
sentence-transformers/all-MiniLM-L6-v2384Léger, rapide, performances correctes
sentence-transformers/all-mpnet-base-v2768De meilleures performances, mais plus lent et plus volumineux
BAAI/bge-large-zh-v1.51024Optimisé spécifiquement pour le chinois
Alibaba-NLP/gte-large-zh1024Créé par Alibaba, bonnes performances en chinois

Pour les scénarios en chinois, nous recommandons la série BAAI/bge ou la série Alibaba-NLP/gte—elles sont spécifiquement optimisées pour le chinois et offrent de meilleures performances que les modèles généralistes.


Stratégie de récupération

La récupération n'est pas aussi simple que « trouver les documents les plus pertinents »—différentes stratégies conviennent à différents scénarios.

Recherche par similarité vectorielle

C'est la méthode de récupération la plus courante : calculez la similarité entre le vecteur de la question et tous les vecteurs de documents, puis renvoyez les K documents les plus pertinents.

Avantages : comprend la sémantique, les synonymes et les quasi-synonymes peuvent tous être mis en correspondance.

Inconvénients : peut être moins précis que la recherche par mots-clés pour les termes précis et les noms propres.

Par exemple, si un utilisateur demande « tutoriel Python d'exemple », la recherche vectorielle peut comprendre que « tutoriel » et « cours » sont similaires, mais si l'utilisateur demande « documentation Python3.10 d'exemple », la recherche par mots-clés peut être plus précise.

Recherche par mots-clés (BM25)

C'est la méthode utilisée par les moteurs de recherche traditionnels : faire correspondre les mots-clés et calculer un score en fonction de la fréquence des termes, de la fréquence inverse des documents, etc.

  • Avantages : efficace pour la correspondance exacte de termes, de noms de produits, de numéros de modèle, etc.

  • Inconvénients : ne peut pas comprendre les synonymes ni les relations sémantiques.

Par exemple, si un utilisateur demande comment apprendre la programmation, la recherche par mots-clés ne peut trouver que les documents contenant « programmation » et ne peut pas trouver les documents qui disent « écrire du code » mais qui signifient la même chose.

Recherche hybride

Puisque les deux méthodes ont leurs avantages et leurs inconvénients, pourquoi ne pas les utiliser ensemble ?

La recherche hybride effectue simultanément une recherche vectorielle et une recherche par mots-clés, puis fusionne et reclasse les résultats.

Ainsi, elle peut tirer parti de la compréhension sémantique tout en assurant une correspondance exacte.

De nombreuses bases de données vectorielles prennent en charge la recherche hybride, comme Weaviate, Pinecone, Qdrant, etc.

Si vous ne savez pas laquelle utiliser, choisissez la recherche hybride — dans la plupart des cas, c'est celle qui fonctionne le mieux.


Optimisation de la phase de génération

La récupération des documents pertinents n'est que la première étape. Il existe également de nombreuses techniques pour amener le LLM à générer de bonnes réponses à partir de ces documents.

Conception de modèle de prompt

Le modèle de prompt est un facteur clé affectant la qualité des résultats dans le RAG.

Un bon prompt RAG inclut généralement :

  • Définition du rôle : indiquer au LLM quel rôle il joue
  • Contexte : le contenu du document récupéré
  • Description de la tâche : ce qu'il faut demander au LLM de faire
  • Contraintes : par exemple, "répondez uniquement en vous basant sur le contexte" et "si vous ne savez pas, dites que vous ne savez pas"
  • Format de sortie : comment vous voulez que le LLM réponde

Un modèle classique :

Exemple

# Un modèle de prompt RAG typique
RAG_PROMPT_TEMPLATE = """Vous êtes un {role} professionnel.
Veuillez répondre à la question de l'utilisateur en vous basant sur le contexte suivant. Si le contexte ne contient pas d'informations pertinentes, dites "Désolé, je n'ai pas trouvé d'informations pertinentes."

Contexte :
{context}

Question de l'utilisateur : {question}

Veuillez répondre dans un langage concis et précis : """

Comment réduire les hallucinations ?

L'hallucination est le phénomène où un LLM fabrique des faits—elle peut sembler très convaincante, mais tout est inventé.

Le RAG peut réduire les hallucinations, mais ne peut pas complètement les éliminer.

Quelques techniques pour réduire les hallucinations :

  • Mettez l'accent sur « répondre uniquement en fonction du contexte » dans le prompt
  • Demandez au LLM de citer des extraits du texte original dans ses réponses
  • Si les résultats de récupération ne sont pas pertinents, demandez au LLM de dire « Je ne sais pas »
  • Utilisez plusieurs LLM pour valider les réponses par recoupement
  • Demandez au LLM de d'abord trouver le contenu pertinent dans le contexte, puis de répondre

Annotation des citations de sources

Il est important d'indiquer aux utilisateurs d'où vient la réponse—cela augmente non seulement la crédibilité, mais facilite aussi la vérification par les utilisateurs.

Pratiques courantes :

  • Indiquez la source dans la réponse, par ex. : « selon le document A, page 2 »
  • Listez les titres et les liens des documents de référence à la fin de la réponse
  • Permettez aux utilisateurs de cliquer et d'aller directement à l'emplacement du texte original

Certains frameworks avancés de LangChain (comme LlamaIndex) peuvent le faire automatiquement pour vous.


Mise en pratique : construire un système de questions-réponses sur documents basé sur LangChain

Assez parlé—passons à la pratique et construisons un système RAG complet.

Nous allons utiliser LangChain + Chroma + OpenAI pour construire un système de questions-réponses documentaire.

Configuration de l'environnement

Installez d'abord les bibliothèques requises :

pip install langchain langchain-openai langchain-chroma python-dotenv

Ensuite, créez un fichier .env dans le répertoire du projet et renseignez votre clé API OpenAI :

OPENAI_API_KEY=你的-api-key-在这里

Implémentation complète du code

Il s'agit d'un système RAG complet, incluant la création d'index et la fonctionnalité d'interrogation :

Exemple

# ============================================
# Implémentation complète du système de questions-réponses sur documents RAG
# ============================================

import os
from dotenv import load_dotenv
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from langchain.schema import Document


def load_documents_from_directory(directory: str) -> list[Document]:
    """Charge les documents depuis le répertoire
À des fins de démonstration, nous créons manuellement quelques documents
Dans des projets réels, vous pouvez utiliser DirectoryLoader pour charger de vrais fichiers
    """

    documents = [
        Document(
            page_content="""Le site de tutoriels EXAMPLE a été fondé en 2013, créé par l'équipe Rookie Tutorial.
Le site Web vise à fournir des tutoriels de programmation faciles à comprendre pour les débutants en programmation.
Nous proposons des tutoriels pour de multiples langages de programmation, notamment HTML, CSS, JavaScript, Python, Java, C++, et plus encore."""
,
            metadata={"source": "example_intro.txt", "page": 1}
        ),
        Document(
            page_content="""Le tutoriel Python d'EXAMPLE couvre tous les points de connaissance importants de Python.
Y compris : la syntaxe de base de Python, les types de données, les instructions de contrôle, les fonctions, les modules, les opérations sur les fichiers, etc.
Le tutoriel fournit un grand nombre d'exemples de code, chacun soigneusement conçu et testé.
Nous fournissons également un éditeur Python en ligne, vous permettant d'exécuter du code directement dans votre navigateur."""
,
            metadata={"source": "python_tutorial.txt", "page": 1}
        ),
        Document(
            page_content="""Les tutoriels EXAMPLE se caractérisent par un contenu concis, des exemples riches et un accent sur la pratique.
Nous croyons que "la meilleure façon d'apprendre la programmation est d'écrire du code."
Par conséquent, chaque point de connaissance est accompagné d’exemples de code exécutables, ce qui vous permet d’apprendre et de pratiquer en même temps.
Nos tutoriels sont entièrement gratuits, et tout le monde peut y accéder et apprendre."""
,
            metadata={"source": "features.txt", "page": 1}
        ),
        Document(
            page_content="""En plus des tutoriels textuels, EXAMPLE propose également des tutoriels vidéo.
Les tutoriels vidéo sont plus intuitifs et adaptés aux apprenants visuels.
Nos tutoriels vidéo couvrent des sujets populaires tels que Python, le développement Web, l'analyse de données, et plus encore.
Vous pouvez trouver nos vidéos sur Bilibili et YouTube."""
,
            metadata={"source": "video_course.txt", "page": 1}
        ),
    ]
    print(f"{len(documents)} documents chargés")
    return documents


def split_documents(documents: list[Document], chunk_size: int = 500, chunk_overlap: int = 50) -> list[Document]:
    """Diviser les documents"""
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        length_function=len,
        separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""],
    )
    splits = text_splitter.split_documents(documents)
    print(f"Diviser {len(documents)} documents en {len(splits)} segments de texte")
    return splits


def create_vector_store(splits: list[Document], persist_directory: str = "./chroma_db") -> Chroma:
    """Créer une base de données vectorielle"""
    embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
    vector_store = Chroma.from_documents(
        documents=splits,
        embedding=embeddings,
        persist_directory=persist_directory,
    )
    print(f"Base de données vectorielle créée, avec un total de {vector_store._collection.count()} enregistrements")
    return vector_store


def load_vector_store(persist_directory: str = "./chroma_db") -> Chroma:
    """Charger la base de données vectorielle existante"""
    embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
    vector_store = Chroma(
        persist_directory=persist_directory,
        embedding_function=embeddings,
    )
    print(fBase de données vectorielle chargée, total {vector_store._collection.count()} enregistrements)
    return vector_store


def create_rag_chain(vector_store: Chroma):
    """Créer une chaîne de questions-réponses RAG"""

    # Modèle de prompt personnalisé
    template = """Vous êtes un assistant professionnel du service client EXAMPLE.
Veuillez répondre à la question de l'utilisateur en vous basant sur le contexte suivant. Si le contexte ne contient pas d'informations pertinentes, dites "Désolé, je n'ai pas trouvé d'informations pertinentes."
Gardez la réponse concise et claire, en utilisant le chinois.

Contexte :
{context}

Question de l'utilisateur : {question}

Réponse : """


    prompt = PromptTemplate(
        template=template,
        input_variables=["context", "question"],
    )

    llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

    # Créer une chaîne de récupération
    rag_chain = RetrievalQA.from_chain_type(
        llm=llm,
        chain_type="stuff",
        retriever=vector_store.as_retriever(search_kwargs={"k": 3}),
        chain_type_kwargs={"prompt": prompt},
        return_source_documents=True,
    )

    return rag_chain


def main():
    """Fonction principale"""
    # Charger les variables d'environnement
    load_dotenv()

    # Vérifier si la base de données existe déjà
    db_exists = os.path.exists("./chroma_db") and os.path.isdir("./chroma_db")

    if db_exists:
        print("Base de données existante trouvée, chargement...")
        vector_store = load_vector_store()
    else:
        print("Création d'une nouvelle base de données...")
        # 1. Charger les documents
        docs = load_documents_from_directory("./data")
        # 2. Découper les documents
        splits = split_documents(docs)
        # 3. Créer une base de données vectorielle
        vector_store = create_vector_store(splits)

    # 4. Créer une chaîne RAG
    rag_chain = create_rag_chain(vector_store)

    # 5. Tester les requêtes
    test_questions = [
        "Quand EXAMPLE a-t-il été fondé ?",
        "Quelles sont les fonctionnalités du tutoriel Python d'EXAMPLE ?",
        "EXAMPLE a-t-il des tutoriels vidéo ?",
        "Comment apprendre le C++ sur EXAMPLE ?",
        "Les tutoriels d'EXAMPLE nécessitent-ils un paiement ?",
    ]

    for question in test_questions:
        print(f"\n{'='*60}")
        print(f"Question : {question}")
        print(f"{'='*60}")

        result = rag_chain.invoke({"query": question})
        answer = result["result"]
        sources = result["source_documents"]

        print(f"Réponse : {answer}")
        print("\nSources de référence :")
        for i, doc in enumerate(sources, 1):
            print(f" [{i}] {doc.metadata['source']} (page {doc.metadata['page']})")
            print(f" Extrait : {doc.page_content[:50]}...")


if __name__ == "__main__":
    main()

En exécutant ce programme, vous verrez une sortie similaire à ceci :

创建新的数据库...
已加载 4 个文档
已将 4 个文档切分为 4 个文本块
向量数据库已创建,共 4 条记录

============================================================
问题:EXAMPLE 是什么时候创立的?
============================================================
回答:EXAMPLE 教程网站创立于 2013 年。

参考来源:
  [1] example_intro.txt (第 1 页)
      片段:EXAMPLE 教程网站创立于 2013 年,由Example团队创建。...
  [2] features.txt (第 1 页)
      片段:除了文字教程,EXAMPLE 还提供了视频教程。...

============================================================
问题:EXAMPLE 的 Python 教程有什么特点?
============================================================
回答:EXAMPLE 的 Python 教程涵盖了 Python 的所有重要知识点,包括基础语法、数据类型、控制语句、函数、模块、文件操作等,并且提供了大量经过精心设计和测试的代码示例,还提供了在线 Python 编辑器,可以直接在浏览器中运行代码。

参考来源:
  [1] python_tutorial.txt (第 1 页)
      片段:EXAMPLE 的 Python 教程涵盖了 Python 的所有重要知识点。...
  [2] features.txt (第 1 页)
      片段:EXAMPLE 教程的特点是内容简洁、示例丰富、注重实践。...

Félicitations ! Vous avez réussi à construire un système complet de questions-réponses sur documents RAG.


Optimisation avancée : Re-ranking, HyDE

Le RAG de base fonctionne, mais si vous voulez de meilleurs résultats, vous pouvez essayer ces méthodes d'optimisation avancées.

Réordonnancement

Les résultats renvoyés par la recherche vectorielle sont triés par similarité, mais une similarité élevée ne signifie pas nécessairement qu'il s'agit de la réponse la plus appropriée à la question.

Le réordonnancement consiste à : d'abord utiliser la recherche vectorielle pour renvoyer les K meilleurs résultats (par ex., 20), puis utiliser un modèle plus précis pour réordonner ces résultats et sélectionner les meilleurs pour les envoyer au LLM.

Modèles de réordonnancement courants :

  • cross-encoder/ms-marco-MiniLM-L-6-v2
  • BAAI/bge-reranker-large

Bien que cela ajoute une étape supplémentaire, l'amélioration des résultats est évidente et vaut la peine d'être faite.

HyDE(Plongement de document hypothétique)

HyDE est une idée astucieuse : d'abord utiliser le LLM pour générer une "réponse hypothétique" à la question de l'utilisateur, puis utiliser cette réponse hypothétique pour la recherche.

Pourquoi est-ce efficace ? Parce que les questions sont généralement courtes et ont une faible densité d'information ; tandis que les réponses hypothétiques sont plus longues, ont un style plus proche de celui d'un document et présentent une similarité plus élevée avec de vrais documents.

Par exemple :

  • Question de l'utilisateur : "Quand EXAMPLE a-t-il été fondé ?"
  • Réponse hypothétique : "Le site de tutoriels EXAMPLE a été fondé en 2013, créé par l'équipe Rookie Tutorial, dans le but de fournir des tutoriels de qualité aux débutants en programmation."

L'utilisation de la réponse hypothétique pour la recherche permet souvent de trouver des documents plus pertinents.

Autres extensions