“Je souhaiterais connaître la démarche pour accéder à mon comité d’entreprise”Qui n’a jamais imaginé poser cette question à un agent conversationnel permettant de lui indiquer facilement la source d’information, plutôt que de chercher dans 10 documents ou de demander à 10 personnes différentes ? Bienvenue au pays des RAG.Figure 1: Exemples de questions posées à notre ChatbotLe RAG, ou Retrieval Augmented Generation, nous permet d’utiliser la puissance d’un agent conversationnel en utilisant nos propres données.Nous développerons à travers cet article les différentes étapes permettant de créer notre Chatbot interne Octo. Cet agent explore la base de documentation Confluence (utilisée par OCTO pour ses activités internes) afin de simplifier la recherche utilisateur et répondre aux questions courantes des employés. Le code détaillé dans cet article est accessible sur ce repository Git.Comment exploiter un modèle de langage ?Le 16 mars dernier, Sam Altman met en lumière un aspect important de l’exploitation des modèles de langues:”The right way to think of the models that we create is a reasoning engine, not a fact database. They can also act as a fact database, but that’s not really what’s special about them – what we want them to do is something closer to the ability to reason, not to memorize.”Sam Altman - fondateur OpenAI Interview ABC - 16 MarsIl propose d’utiliser un agent conversationnel comme un moteur de raisonnement et non pas comme une base de connaissances. Autrement dit, les modèle de langages ne sont pas une immense base de données à la connaissance infinie qui résout nos problèmes en piochant dans son puit de savoir.Les modèles de langues sont initialement entraînés pour prédire le prochain mot d’une phrase. Grâce à des méthodes d’alignement (ici RLHF), la qualité des réponses de ces agents s’est grandement améliorée.Cependant, ces modèles ont aussi tendance à halluciner ou apporter des réponses biaisées. Afin de remédier à ces problèmes et pour personnaliser la réponse d’un modèle de langue, plusieurs solutions ont récemment émergé. Parmi ces solutions, il existe le prompting, ou l’art d’adapter notre question afin d’orienter le modèle vers une réponse souhaitée.Voici un exemple de prompt intéressant pour une problématique de Question Answering :Dans cet exemple, le modèle de langue a pour objectif de répondre à une question selon le contexte qui lui est transmis.Il existe aussi d’autres techniques de “prompting” pour résoudre des problèmes plus complexes tels que les Chain-of-Thought dont l’idée est d’introduire un raisonnement par étape. Comme nous allons le voir, ces étapes de “prompting” seront de plus en plus automatisées.DéfinitionLe RAG est une technique de traitement du langage naturel combinant la recherche d’informations dans une base documentaire et l’exploitation d’un moteur de langage. Il a pour objectif de répondre à des questions issues d’une documentation privée ou inconnue des moteurs de langages open ou closed-source.Pourquoi utiliser l’approche RAG et ne pas donner toute ma base documentaire au modèle ?Pour deux raisons :À titre d’exemple, le modèle ChatGPT autorise une taille de contexte allant jusqu’à 32,768 tokens. Pour comparaison, le roman de Georges Orwell, 1984, a une taille de 89 000 tokens.Il est donc impossible de fournir toute une documentation d’entreprise en contexte d’un modèle de langue. Pour comprendre ce qu’est un token, voici un exemple de découpage ou tokenisation selon Chat GPT:Alors, comment choisir les documents à lui transmettre ?Le RAG est constitué d’une suite d’étapes permettant d’établir cette sélection :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 en vecteurs de valeurs numériques)La création d’une base de données vecteur (le stockage de ces valeurs numériques 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)Ces étapes sont requises pour mener à bien l’indexation de notre base documentaire.Commençons par étudier comment stocker notre documentation pour la rendre exploitable :Dans un premier temps, la base de données de l’entreprise est extraite (.pdf, .md, .txt, …) et divisée en sous-parties.  Chaque sous-partie peut être de taille fixe ou variable selon le choix de découpage des documents. Des techniques d’overlap sont possible mais peuvent introduire des doublons lors du choix des chunks adéquats pour la réponse à une question posée.Ensuite, ces sous-parties sont converties en embeddings, décrivant l’information sémantique qu’elles contiennent.Enfin, ces embeddings sont stockés dans une base de données vecteur.Figure 2: L’ingestion de la base de données de l’entrepriseEnsuite, intéressons-nous à la relation entre la base de données vecteurs et notre modèle de langue.Une fois la base de données vecteur créée, notre documentation d’entreprise est exploitable. L’utilisateur peut alors poser une question:Comment accéder à mon comité d’entreprise ?Cette question est convertie en un vecteur contenant son embedding, représentant l’information sémantique de la question. À partir de l’embedding de la question, une recherche d’embedding similaire est faite dans la base de données vecteur.  L’objectif de cette recherche est de retrouver les parties de documents en relation avec la question. De cette recherche, deux contenus ressortent :Ces contenus forment le contexte et seront ajoutés à notre prompt:Le template de prompt:Se complète avec le contexte et la question:Ce prompt final est mis en entrée d’un modèle de langue, voici sa réponse :Et voilà ! Nous venons d’ajuster la question que nous posons à notre modèle de langue pour que sa réponse soit personnalisée par rapport à nos données.A titre de comparaison: imaginez que vous souhaitez connaître les destinations de vacances éco-responsables qui vous conviendront. Vous pouvez imaginer poser une question sur l’interface publique de Chat GPT en lui demandant quelles sont les stations balnéaires éco-responsables. Votre lieu de vie étant important pour obtenir une bonne réponse, votre cerveau utilisera probablement implicitement l’approche RAG, en orientant le modèle vers la réponse adéquate grâce à l’indication de votre localisation.C’est l’objectif du RAG, piocher parmi vos informations personnelles ou d’entreprise pour mieux vous répondre. La question ‘Ou puis-je partir en vacances dans un lieu éco-responsable ?’ se transformera en ‘Sachant que j’habite à Paris, ou puis-je partir en vacances dans un lieu éco-responsable ?Figure 2: Architecture RAGUne approche RAG pour la recherche de documentation requiert la construction d’un pipeline, comme présenté ci-dessus. Pour simplifier la création de ces pipelines, communément appelé chaînes, Harrison Chase crée en Octobre 2022 LangChain.LangChain est un framework simplifiant la création de chaînes, ou suite d’étapes, permettant entre autres la mise en place de chatbots basés sur les modèles de langue.Grâce à sa large communauté Open Source, LangChain offre de nombreuses fonctionnalités et est aujourd’hui le moyen le plus efficace pour créer des architectures autour des modèles de langues. Parmi les fonctionnalités de LangChain, nous citerons les suivantes:Document Loader : permet de lire des textes d’une sourceVector Store : base de données vectorielle pour les embeddingsRetriever : outils pour retrouver des documents dans une DB ou sur le WebChain : pipeline de traitement LLM, en plusieurs étapesTools: un moyen de connecter un agent avec des services extérieurs - équivalent des plugins de ChatGPTAgent: extension des chaînes pour ajouter des outils (lien avec l’extérieur du LLM)Passons maintenant à l’implémentation d’un Chatbot pour répondre à des questions sur les données de notre entreprise.Chez Octo la documentation de l’entreprise est sur Confluence. Elle regroupe entre autres des informations sur les outils internes, les réponses aux questions des employés et des retours d’expériences sur des sujets techniques. Il arrive que l’information soit dispersée à différents endroits, ou que les titres des documentations ne soient pas explicites.Pour résoudre ces limites et proposer une solution user-friendly, nous avons choisi de créer un Chatbot conversationnel répondant aux questions des employés en indiquant les sources utiles pour approfondir le sujet.Nous présentons ci-dessous les différentes étapes et le code LangChain pour la création de notre solution : Le Help Desk.Vous trouverez le repository Github ici.La première étape consiste à charger les données depuis Confluence.LangChain propose différents types de Loader selon le type de fichier (.pdf, .txt, .md, .png). La documentation Octo dans Confluence est en Markdown (le loader Markdown inclut une fonctionnalité d’extraction de textes dans les images).Nous utilisons le Confluence Loader afin de collecter la documentation directement avec une clé API. Nous avons aussi proposé une MR à LangChain, validée sur la version 0.0.245, pour conserver la structure Markdown et obtenir une meilleure segmentation des chunks.python
import markdownify
from langchain.document_loaders import ConfluenceLoader

loader = ConfluenceLoader(
url=CONFLUENCE_SPACE_NAME, # ex: https://prenomnom.atlassian.net/wiki
username=CONFLUENCE_USERNAME, # ex: monentreprise@gmail.com
api_key=CONFLUENCE_API_KEY # Create a key here
)

docs = loader.load(
space_key=CONFLUENCE_SPACE_KEY, # /spaces//overview
keep_markdown_format=True
)

print(docs[-1].page_content)

>>
“””
...
## Comment accéder à mon CE ?

Pour accéder au CE, vous pouvez consultant l’adresse suivante: <https://mon-CE.fr>

Si vous n’avez pas vos identifiants, vous pouvez envoyer un mail à xxxx@mon-ce.fr, le responsable du comité d’entreprise.

Renseignez les identifiants sur le site du CE et vous pourrez bénéficier de nombreux avantages.

## Rôle du Comité d'Entreprise :

Le Comité d'Entreprise joue un rôle essentiel dans le dialogue social au sein de l'entreprise. Ses principales missions sont les suivantes :

### 1. Défense des droits des salariés :

Le Comité d'Entreprise est chargé de veiller au respect des droits des salariés en matière de travail, de sécurité, de santé et de conditions de travail.
...
“””
Comme expliquée dans cet article de Pinecone, l’étape de création des chunks est stratégique. L’objectif est de conserver l’intégralité du contexte dans un chunk afin que sa représentation vectorielle soit la plus représentative possible.Nos données textuelles ayant été récupérées au format Markdown, il est possible d’en distinguer les parties et sous-parties. On espère maximiser la qualité de nos chunks et des embeddings qui les représenteront dans l’étape suivante.Le Markdown Header Text Splitter découpe notre page Confluence en identifiant les titres et sous-titres de notre page Markdown. En plus d’un découpage intelligent, les métadonnées des chunks sont complétées avec les titres et sous-titres trouvés.Prenons un exemple avec le découpage d’un article.python
from langchain.text_splitter import MarkdownHeaderTextSplitter

# Markdown
headers_to_split_on = [
("#", "Titre"),
("##", "Sous-titre 1"),
("###", "Sous-titre 2"),
]

# Markdown splitter
markdown_splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=headers_to_split_on
)

chunks = markdown_splitter.split_text(docs[1].page_content) # Sample one doc

pretty_print(chunks)

"""
...
Pour accéder au CE, vous pouvez consultant l’adresse suivante: <https://mon-CE.fr>
Si vous n’avez pas vos identifiants, vous pouvez envoyer un mail à xxxx@mon-ce.fr, le responsable du comité d’entreprise.
Renseignez les identifiants sur le site du CE et vous pourrez bénéficier de nombreux avantages.
--------------------------------------------------
{'Sous-titre 1': 'Comment accéder à mon CE ?'}
==================================================
Le Comité d'Entreprise joue un rôle essentiel dans le dialogue social au sein de l'entreprise. Ses principales missions sont les suivantes :
--------------------------------------------------
{'Sous-titre 1': "Rôle du Comité d'Entreprise :"}
==================================================
Le Comité d'Entreprise est chargé de veiller au respect des droits des salariés en matière de travail, de sécurité, de santé et de conditions de travail. Il peut être consulté par la direction de l'entreprise sur différentes questions liées à l'organisation du travail, aux licenciements collectifs, aux restructurations, etc. Il est également informé des projets de l'entreprise et peut émettre des avis.
--------------------------------------------------
{'Sous-titre 1': "Rôle du Comité d'Entreprise :", 'Sous-titre 2': '1. Défense des droits des salariés :'}
...
"""
Que se passe-t-il si les chunks créés suivant la structure de notre page Confluence sont trop longs ?Nous affinons ensuite ce premier découpage en utilisant le Recursive Character Text Splitter. Cette méthode permet de re-diviser de manière récursive les blocs de textes encore trop longs après le premier découpage.Dans l’exemple ci-dessous, la partie 1. La défense des droits des salariés est divisée en deux chunks.python
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
chunk_size=400,
chunk_overlap=20,
separators=['\n\n', '\n', '(?<=. )', ' ', '']
)

splitted_chunks = splitter.split_documents(chunks)
pretty_print(splitted_chunks)

"""
...
Le Comité d'Entreprise est chargé de veiller au respect des droits des
salariés en matière de travail, de sécurité, de santé et de conditions de
travail.
Il peut être consulté par la direction de l'entreprise sur différentes questions liées
à l'organisation du travail, aux licenciements collectifs, aux restructurations, etc.
--------------------------------------------------
{'Sous-titre 1': "Rôle du Comité d'Entreprise :", 'Sous-titre 2': '1. Défense des droits des salariés :'}
==================================================
Il est également informé des projets de l'entreprise et peut émettre des avis.
--------------------------------------------------
{'Sous-titre 1': "Rôle du Comité d'Entreprise :", 'Sous-titre 2': '1. Défense des droits des salariés :'}
...
"""
Le repository Github inclut des fonctionnalités additionnelles qui ne sont pas présentées ici :L’ajout des métadonnées de la page Confluence (url, id) avec les métadonnées des blocs de textes (titre, sous-titre)L’ajout des titres et sous-titres au texte du documentLes fonctions vous permettant de tester le découpage de bout en boutUne interface StreamlitLa troisième étape consiste à transformer nos morceaux de textes en représentations vectorielles, appelées embeddings. Ces embeddings nous permettront de faire une étude de similarité pour affiner le contexte transmis au modèle de langue. Il doivent cependant être stockés dans une base de données vecteurs.LangChain propose de nombreuses intégrations de modèles d’embeddings et de bases de données vecteurs. Nous utilisons par défaut le modèle embedding d’Open AI, text-embedding-ada-002. Nous choisissons la base de données vecteur légère Chroma que LangChain met à dispositionAttention cependant avec le choix des modèles de langage. Utiliser un modèle en utilisant une API tiers tel que celle de Chat GPT rend votre donnée publique. Pour se prémunir de la divulgation de données deux solutions s'offrent à vous:Un prochain article détaillera comment remplacer l’API Open AI par des modèles Open Source.python
# Embeddings and vector store
import shutil
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings

persist_directory = './db/chroma'
chunks = my_custom_splitter(docs) # See notebook for function definition
embeddings = OpenAIEmbeddings() # Default to text-embdding-ada-002

# If the directory exists, first delete it
try:
shutil.rmtree(persist_directory)
except FileNotFoundError as e:
pass

# Create vector store and save the db
db = Chroma.from_documents(
chunks,
embeddings,
persist_directory=persist_directory
)
db.persist()
Dans cette partie, nous allons créer une chaîne nous permettant de mettre en place notre processus de question/réponse.Nous commençons par créer le retriever qui permet de retrouver les chunks de notre base vecteur. Parmi les paramètres possibles, nous pouvons spécifier une mesure de similarité, indiquer le nombre de chunks souhaités en sortie, ou un score minimum de similarité par rapport à la question posée.Ces décisions ont un impact sur la qualité des réponses et doivent être optimisées. Le nombre de chunks dépend de différents paramètres tel que le niveau de granularité du découpage ou la richesse de la documentation. Le score de similarité dépend de la proximité des chunks avec la question et donc du niveau de précision de la base documentaire.python
retriever = db.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={
"k": 5,
"score_threshold": 0.3
}
)
Il convient ensuite de créer le prompt adéquat qui aidera le modèle de langue à raisonner.