Construire son RAG (Retrieval Augmented Generation) avec LlamaIndex et le modèle LLM Vigogne

Dans cet article nous allons voir comment :

  • Utiliser de grands modèles de langage (LLM) open source pour ingérer des informations à partir de documents.
  • Générer des réponses IA aux questions posées sur le contenu texte.

Mon objectif étant d’évaluer la faisabilité d’un développement d’un système automatisé pour ingérer de la documentation et fournir des réponses générées par l’IA aux questions posées et basées sur les dernières informations disponibles.

Pour ce faire nous créerons un outil d’extraction et d’analyse de données (RAG) utilisant le framework Python LlamaIndex avec le modèle LMM Vigogne. Ainsi ce RAG, ou Retrieval Augmented Generation, nous permettra d’utiliser la puissance d’un agent conversationnel IA avec l’utilisation de nos propres documentations. 

Création de l’application RAG

Dans le cadre de cet article, nous utiliserons Google Colaboratory (Colab). En effet cet outil permet de développer des applications d’l’Intelligence Artificielle (IA) en permettant l’accès à un processeur graphique GPU gratuitement. Des informations détaillées sont disponibles sur la page FAQ de Colab.

Alors comment créer un RAG à partir d’une liste de documents ? Un RAG est constitué des étapes suivantes : 

  • La création de chunks (la division du corpus de textes en sous-parties).
  • La création d’embeddings (la transformation de ces sous-parties de textes en vecteurs de valeurs numériques).
  • La création d’une base de données vectorielles (le stockage des vecteurs dans une base de données adaptée).
  • La recherche d’informations ou information retrieval (la recherche des chunks sémantiquement proches de la question posée).

Installation des dépendances/packages requis

Nous avons besoin des packages suivants :

%%bash
# https://github.com/run-llama/llama_index
pip install -q llama-index
# https://github.com/UKPLab/sentence-transformers
pip install -q sentence-transformers

Il est aussi nécessaire d’installer le package Python llama-cpp-python qui fournit une interface Python à la bibliothèque C++ Llama CPP sans avoir à écrire du code C++ ou à gérer des API C++ de bas niveau.

Si vous avez positionné le type d’exécution du Notebook avec l’accélérateur matériel T4 GPU, pour profiter de la GPU vous devez installer llama-cpp-python de la manière suivante :

# GPU
!CMAKE_ARGS="-DLLAMA_CUBLAS=on" pip install llama-cpp-python

Sinon pour une exécution standard (càd avec l’accélérateur matériel CPU), l’installation doit se faire de la manière suivante :

# CPU
!CMAKE_ARGS="-DLLAMA_CUBLAS=off" pip install llama-cpp-python

Préparation des documents textes

J’ai créé ces 2 fichiers au format texte brut contenant des informations à des fins de test. Les informations dans ces fichiers sont suffisamment précises et assez absurdes afin d’être sûrs que l’IA fait référence à ces informations pour constituer les réponses aux questions posées.

Info1.txt

MrTuto est un développeur de logiciel Open Source. Il est l'auteur principal de TutoPlot, TutoNGram et de plusieurs autres packages Python open source. La couleur préférée de MrTuto est le bleu foncé malgré le fait qu'il soit daltonien. Le plaidoyer de MrTuto en faveur des personnes souffrant d’un déficit de vision des couleurs l’amène à recommander des palettes de couleurs adaptées aux daltoniens pour rendre les graphiques plus accessibles.

Info2.txt

« JupyterTutoGoCrash » est le nom d'un package Python permettant de créer des notebooks Jupyter non maintenables. Ce package n'est plus activement développé et est désormais considéré comme obsolète. En effet les développeurs se sont rendu compte que les exigences de sécurité des environnements Jupyter empêchais ce package de fonctionner.

Chargement des données texte

Nous utilisons L’API LlamaIndex SimpleDirectoryReader comme connecteur de données permettant de lire des fichiers à partir d’un répertoire d’entrée ou d’une liste de fichiers. SimpleDirectoryReader sélectionne automatiquement le meilleur lecteur de fichiers en fonction des extensions de fichiers. Il peut être configuré avec plusieurs arguments, tels que le répertoire d’entrée et la liste de fichiers à lire.

Ici nous chargeons les 2 fichiers Info1.txt & Info2.txt à partir d’un répertoire Google Drive.

from llama_index import SimpleDirectoryReader

print("Chargement des documents 'Info1.txt' & 'Info2.txt'")
documents = SimpleDirectoryReader(
    input_files=[
        '/content/drive/MyDrive/Colab Notebooks/LlamaIndex-Vigogne QA/Data/Info1.txt',
        '/content/drive/MyDrive/Colab Notebooks/LlamaIndex-Vigogne QA/Data/Info2.txt',
    ]
).load_data()

Création des chunks

Ensuite, nous divisons le document en morceaux de texte (chunks), appelés « nœuds » (nodes) dans LlamaIndex, où nous définissons la taille du chunk à 500 caractères.

from llama_index.node_parser import SimpleNodeParser
from llama_index.schema import IndexNode

node_parser = SimpleNodeParser.from_defaults(chunk_size=500)
base_nodes = node_parser.get_nodes_from_documents(documents)

Les embeddings et la base de données vecteurs

Cette troisième étape consiste à transformer nos morceaux de textes en représentations vectorielles, appelées embeddings. Ces embeddings nous permettront de calculer une similarité pour affiner le contexte transmis au modèle de langue. Afin de ne pas recalculer ces embeddings, ils seront stockés dans une base de données vectorielles.

LlamaIndex propose de nombreuses intégrations de modèles d’embeddings et de bases de données vectorielles. Nous utilisons le modèle embedding dangvantuan/sentence-camembert-base qui est un modèle d’embeddings de référence pour les phrases en français. Et nous prenons le modèle de langage LLM bofenghuang/vigogne-2-7b-chat qui est un modèle de Chat LLM basé sur Llama-2 pour générer des réponses basées sur la requête et le contexte récupéré.

LlamaIndex stocke les données en mémoire, et ces données peuvent être explicitement conservées via le code Python suivant :

storage_context.persist(persist_dir="<persist_dir>")

Cela conservera les données sur le disque, sous le répertoire persist_dir spécifié (ou ./storage par défaut).

Embeddings

# Name or path to sentence-transformers embedding model.
#  - Multilingual: paraphrase-multilingual-mpnet-base-v2, paraphrase-multilingual-MiniLM-L12-v2
#  - French: dangvantuan/sentence-camembert-base, dangvantuan/sentence-camembert-large
EMBEDDING_MODEL_NAME = 'dangvantuan/sentence-camembert-base'

from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from llama_index import LangchainEmbedding

print(f"Chargement du modéle Embedding: {EMBEDDING_MODEL_NAME} ...")

embedding_model = LangchainEmbedding(HuggingFaceEmbeddings(
    model_name=EMBEDDING_MODEL_NAME,
    encode_kwargs = {"normalize_embeddings": False}
  )
)

LLM

# https://huggingface.co/TheBloke/Vigogne-2-7B-Chat-GGUF
LLM_MODEL_NAME = "https://huggingface.co/TheBloke/Vigogne-2-7B-Chat-GGUF/resolve/main/vigogne-2-7b-chat.Q5_K_M.gguf"

from llama_index.llms import LlamaCPP
from llama_index.llms.llama_utils import messages_to_prompt, completion_to_prompt

print(f"Chargement du modéle LLM: {LLM_MODEL_NAME} ...")

llm = LlamaCPP(
    # You can pass in the URL to a GGML/GGUF model to download it automatically
    model_url=LLM_MODEL_NAME,
    # optionally, you can set the path to a pre-downloaded model instead of mo-del_url
    model_path=None,
    temperature=0.1,
    max_new_tokens=1024,
    generate_kwargs={},
    model_kwargs={
        "low_cpu_mem_usage": True,
    },
    messages_to_prompt=messages_to_prompt,
    completion_to_prompt=completion_to_prompt,
    verbose=True,
)

Vector store index

from llama_index import ServiceContext
from llama_index import VectorStoreIndex

service_context=ServiceContext.from_defaults(
      llm=llm,
      embed_model=embedding_model
)

vectorstore_index = VectorStoreIndex(
    nodes=base_nodes, 
    service_context=service_context
)
print("Sauvegarde de la base de données vectorielle")
vectorstore_index.storage_context.persist(persist_dir='llama_index')

Voilà la structure de données de type Vector Store Index est créée. Le Vector Store Index prend les morceaux de texte/nœuds, puis crée des intégrations vectorielles du texte de chaque nœud, prêtes à être interrogées par un LLM.

Le Vector Store Index permettra de récupérer rapidement des informations pertinentes pour une requête utilisateur à partir de documents externes.

Questions et réponses sur les documents

Dans cette dernière partie, nous allons créer une chaîne nous permettant de mettre en place notre processus de question/réponse.

Tout d’abord, il faut créer le modèle de prompt adéquat qui aidera le modèle de langue (LLM) à raisonner. Puis le retriever as_query_engine() permettant de poser des questions sur des informations spécifiques (chunks) contenues dans la base vectorielle et de recevoir une réponse correspondante à l’aide du modèle LLM configuré.

from llama_index.prompts import PromptTemplate
from llama_index import ServiceContext

text_qa_template_str = (
  "<|system|>: Vous êtes un assistant IA qui répond à la question posée à la fin en utilisant le contexte suivant. Si vous ne connaissez pas la réponse, dites simplement que vous ne savez pas, n'essayez pas d'inventer une réponse. Veuillez répondre exclusivement en français.\n"
  "<|user|>: {context_str}\n"
  "Question: {query_str}\n"
  "<|assistant|>:"
)

text_qa_template = PromptTemplate(text_qa_template_str)

query_engine = vectorstore_index.as_query_engine(
    text_qa_template=text_qa_template,
    service_context=ServiceContext.from_defaults(
      llm=llm,
      embed_model=embedding_model,
      chunk_size=500,
    ),
)

Ainsi les étapes suivantes sont mises en place :

  • Créer l’embedding de la question
  • Trouver les contenus similaires de la question dans la base vectorielle
  • Créer le prompt adéquat
  • Récupérer la réponse de notre modèle de langue

Nous pouvons désormais effectuer une requête.

rom IPython.display import Markdown

response = query_engine.query("Qui est l’auteur de TutoPlot ? Quelle est sa couleur préférée ?")
print("Question: Qui est l’auteur de TutoPlot ? Quelle est sa couleur préférée ?")
display(Markdown(f"Reponse: <i>{response}</i>"))

Question: Qui est l’auteur de TutoPlot ? Quelle est sa couleur préférée ?

Reponse: L'auteur de TutoPlot est MrTuto, qui est également l'auteur principal de plusieurs autres packages Python open source. Sa couleur préférée est le bleu foncé malgré le fait qu'il soit daltonien.
response = query_engine.query("Pourquoi JupyterTutoGoCrash est-il obsolète ?")
print("Question: Pourquoi JupyterTutoGoCrash est-il obsolète ?")
display(Markdown(f"Reponse: <i>{response}</i>"))

Question: Pourquoi JupyterTutoGoCrash est-il obsolète ?

Reponse: il est obsolète en raison du manque de développement et des exigences de sécurité des environnements Jupyter qui empêchent ce package de fonctionner.

Comme nous pouvons le constater, le modèle LLM Vigogne a répondu avec précision à la requête. Il a effectué une recherche dans l’index et a trouvé les informations pertinentes.

Conclusion

LlamaIndex fournit une boîte à outils puissante pour créer des systèmes de génération augmentée RAG combinant les atouts des grands modèles de langage avec des bases de connaissances personnalisées. Nous avons ainsi vu comment créer une base de données vectorielles indexé de données spécifiques à un domaine et l’exploiter lors de l’inférence pour fournir un contexte pertinent au LLM afin de générer des réponses de haute qualité.

Il est à noter qu’une approche RAG est constitué à partir des deux modèles suivants:

  • du modèle d’embeddings et de la base de données vectorielles.
  • du modèle de langage LLM.

Il convient donc de faire attention à la qualité de ces deux composants. Le premier composant dépend principalement du choix de l’embedding et de la métrique de similarité. Le second composant dépend du choix du modèle de langage utilisé et de la qualité du prompt.

Code Python du Notebook Colab: LlamaIndex-Vigogne QA.ipynb

4 réflexions au sujet de “Construire son RAG (Retrieval Augmented Generation) avec LlamaIndex et le modèle LLM Vigogne”

  1. Super tuto!

    J’essaie de faire fonctionner vigogne sur privateGPT, mais je n’y arrive pas (je me demande si ce n’est pas a cause du prompt spécial que tu utilises).

    Serais-tu intéressé pour faire fonctionner Vigogne sur privateGPT et faire une contribution open source au projet?

    La page GitHub du projet: https://github.com/imartinez/privateGPT
    Tu peux y trouver le lien vers notre serveur discord: https://discord.gg/bK6mRVpErU

    N’hesite pas me ping sur discord (pseudo `lopagela`)

    A bientot!

    Répondre
  2. Bonjour

    Avez vous ce message :

    Chargement du modéle Embedding: dangvantuan/sentence-camembert-large …
    No sentence-transformers model found with name /home/*******/.cache/torch/sentence_transformers/dangvantuan_sentence-camembert-large. Creating a new one with MEAN pooling.

    Répondre

Laisser un commentaire