Skip links

Propulsez vos documents dans la dimension GenAI

~20min de lecture

 

Introduction

Contexte

Dans le paysage moderne, les applications telles que ChatGPT, basées sur des LLM (Large Language Model), sont en passe de devenir omniprésentes. Leur potentiel s’étend à travers tous les secteurs, du service client à la gestion des ressources humaines, en offrant des solutions adaptées à des problèmes de tous types.

 

Les nombreuses annonces des acteurs du secteur soulignent une démocratisation de l’IA. L’évolution constante des outils permet d’entrevoir, pour les entreprises, des perspectives de performance et d’efficacité opérationnelle grâce à la GenAI.

Notre processus de R&D

Au vu des avancées faites ces derniers mois dans le domaine de l’intelligence artificielle générative, Nobori a souhaité lancer une démarche en interne pour tenter de comprendre et maîtriser les concepts clés en jeu. Le but est d’utiliser ces savoir-faire nouvellement acquis pour accompagner au mieux nos clients dans cette transition vers les usages GenAI. Nous nous attachons également à développer des solutions innovantes en interne pour faciliter le travail des consultants et automatiser certains processus.

 

Notre R&D nous a amené à développer un chatbot qui implémente de la RAG (Retrieval-Augmented Generation). Un chatbot est simplement un agent conversationnel, qui dans notre cas se base sur un LLM pour générer des réponses aux questions de l’utilisateur.

 

On rencontre régulièrement deux problèmes lorsque l’on interagit avec des LLM :

  • Hallucinations : la capacité d’un modèle à inventer des informations lors de la génération de texte.
  • Réponses obsolètes : certaines informations qu’utilisent les modèles peuvent provenir de sources caduques au moment de la génération.

 

La RAG aspire à pallier ces problèmes, en mettant en place un système de recherche d’informations pour fournir du contexte au modèle lors de la génération des réponses. Ce concept fondamental permet d’enrichir la génération de texte en extrayant des données pertinentes au préalable pour contextualiser et améliorer la précision des réponses.

Vision macro du PoC

Ce PoC est une application qui donne la possibilité de discuter avec un chatbot. Vous lui fournissez des documents, tels que des textes ou des articles, et ensuite, vous pouvez lui poser des questions. Il vous fournira des informations ou des réflexions en se basant sur les documents que vous lui avez fournis auparavant. C’est comme si vous aviez une discussion avec un assistant virtuel intelligent qui tire ses connaissances des documents que vous partagez avec lui.

 

Ci-dessous, le schéma du fonctionnement général qui sera détaillé au long de cet article :

 

 

  1. Dans un premier temps, l’utilisateur upload ses documents, qui passent par une étape de Document Processing avant d’être stockés.
  2. L’utilisateur interagit ensuite avec l’application comme avec un chatbot classique. Pendant le Query Processing, la question est reformulée pour inclure l’historique de la conversation, puis utilisée pour la suite du processus.
  3. La phase de Retrieval est le cœur d’une application de questions & réponses (Q&A) sur des documents. On cherche à trouver des informations pertinentes dans les documents grâce à la requête utilisateur. Ces informations serviront de contexte pour répondre à sa demande.
  4. Une fois le contexte et la question obtenus, ils sont transmis au prompt final et le tout est utilisé par le modèle qui s’occupe de générer notre réponse. Ceci conclut la phase d’Answer Generation !

Paysage technologique

Nous avons commencé par étudier diverses solutions open-source existantes, qui répondaient en partie à notre cahier des charges, pour comprendre leur fonctionnement et la structure de leur projet. Parmi celles-ci figuraient h2oGPT et PrivateGPT. Au moment des tests, elles se sont avérées insuffisamment flexibles, et nous recherchions une application offrant une modularité sur le long terme.

 

N’ayant pas trouvé de solution satisfaisante, nous avons décidé de développer notre propre application. Nous nous sommes intéressés au choix d’un framework afin d’entamer le développement du PoC. Nous avons effectué un comparatif entre LlamaIndex et LangChain et notre choix s’est finalement porté sur ce dernier. Il possède des exemples concrets illustrant les différents concepts qu’il implémente, ainsi qu’une documentation fournie et explicite. De plus, le GitHub du projet est bien plus actif et c’est un argument majeur à prendre en compte dans le choix d’un framework open-source.

 

Une fois le choix du framework effectué il nous fallait trouver le moyen d’accéder à des modèles. On interagit avec un LLM par le biais de requêtes API ou via une CLI. Deux types d’hébergements sont possibles :

  • Des fournisseurs externes tels que HuggingFace, together.ai, Azure AI, ou encore Amazon Bedrock.
  • L’exécution d’un modèle en local, comme avec Ollama par exemple. Le LLM tourne alors sur notre architecture, à savoir un ordinateur portable dans notre cas.

 

Dans la version actuelle du PoC, nous avons le choix quant à l’utilisation de modèles open-source en local ou de modèles payants tels que GPT-4 d’OpenAI. Notre application permet d’interchanger simplement le ou les modèles utilisés.

Focus sur les notions de Prompt et Chaîne

Avant de rentrer dans les détails, abordons simplement deux notions importantes à avoir en tête pour la suite de l’article :

 

  • Prompt : une série d’instructions écrites en langage naturel, données au modèle pour lui indiquer ce qu’on attend de sa réponse. Ce prompt peut être une question, une amorce de phrase, voire une combinaison de plusieurs éléments, et sert de guide pour orienter la génération du modèle. Il conditionne la manière dont le modèle comprend la tâche à accomplir et influence directement la nature et le contenu de la réponse générée en sortie.

 

Exemple de prompt

  • Chaîne : l’utilisation d’un LLM de manière isolée convient aux applications simples, mais les applications plus complexes nécessitent l’enchaînement de plusieurs LLM, soit entre eux, soit avec d’autres composants. Un des principes fondamentaux du framework LangChain, qui lui a donné son nom, est le concept de chaîne. On définit une chaîne de manière très générique comme une séquence d’appels à différents composants gérés par le framework (prompt, LLM, mémoire, etc.), qui peuvent eux-mêmes inclure d’autres chaînes.

Analyse approfondie des processus

Avec ces concepts en tête, on peut aborder le cœur des explications, en commençant par une version plus détaillée du schéma présenté plus tôt :

 

Cette représentation offre une vue complète du fonctionnement de chaque partie du processus et leurs interconnexions.

1. Traitement des documents

 

Il existe deux types de données, les données structurées et non structurées. Pour fonctionner au mieux, les LLM ont besoin d’être alimentés par des données claires et bien organisées.

Lorsque l’information provient de bases de données ou d’outils comme Notion, qui gardent les données de manière structurée, leur exploitation est simple. Dans notre cas on traite des documents PDF, qui rentrent dans la catégorie des données non structurées. Pour gérer l’extraction des éléments de ce type de document, on utilise l’ETL (Extract, Transform, Load) nommé Unstructured.

 

La gestion du traitement des documents repose sur l’application du concept de Parent Document. Le document original est découpé en sections, appelées chunks, qui sont ensuite stockés.

 

Puis, des modifications sont apportées à ces chunks, telles que la création de résumés (on pourrait également diviser à nouveau les chunks pour obtenir des sections plus petites). Un identifiant maintient le lien entre les chunks d’origine et les chunks résumés, puis ces derniers sont sauvegardés sous forme d’embeddings. Grâce à cette méthode, il devient facile de rechercher parmi les embeddings tout en obtenant des résultats exploitables.

 

Mais que signifie embeddings ? Développons cette notion !

Un embedding est une représentation vectorielle couramment utilisée par les modèles d’IA car elle facilite la manipulation des données.

 

Dans notre cas, un embedding de mot peut avoir des centaines de valeurs, chacune représentant un aspect différent de la signification d’un mot. Exemple avec le mot “framework” dans la phrase “LangChain is a framework” et “Framework provides a structure for software development”:

 


À mesure que le modèle traite l’ensemble de mots que représente une phrase, ici « LangChain is a framework », il produit un vecteur – ou une liste de valeurs – et l’ajuste en fonction de la proximité de chaque mot avec les autres mots dans les données d’entraînement.

 

Au terme de la phase de traitement des documents, on obtient un lieu de stockage mixte, qui contient à la fois des chunks et des embeddings. On se servira de ce lieu de stockage lors de la phase de Retrieval, qui apparaît plus tard dans le processus.

 

2. Traitement de la requête utilisateur

 

 

La traitement de la requête de l’utilisateur implique une phase de reformulation qui s’appuie sur l’historique de conversation. Cela permet d’intégrer les informations préalablement échangées à la nouvelle question.

 

Voici un exemple de prompt qui pourrait être utilisé à cet effet :

 

Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question.

At the end of standalone question add this ‘Answer the question in {language}.’

Chat History:

{chat_history}

Follow Up Input: {question}

Standalone question:

 

Ici, les variables entre accolades sont remplacées par leur valeur quand le prompt est utilisé.

 

3. Retrieval – Récupération du contexte

 

Cette phase est le cœur d’une application de questions & réponses (Q&A) sur des documents. Comme précisé précédemment, le retriever se base sur un stockage mixte :

 

  • docstore : chunks des documents originaux
  • vectorstore : embeddings des résumés des chunks

 

Dans notre cas le stockage des documents (docstore) se fait dans la mémoire vive et le vectorstore utilise lui une base de donnée vectorielle appelé ChromaDB.

 

On effectue ensuite une recherche de similarité sur le contenu du vectorstore en utilisant la question reformulée obtenue précédemment, et on obtient en retour les chunks contenant les informations pertinentes qui serviront de contexte au LLM final.

 

On réussit ainsi à récupérer des chunks présents dans les documents originaux qui contiennent des informations utiles à la génération de la réponse.

 

Rentrons plus en détail sur la recherche de similarité :

La recherche de similarité peut prendre différentes formes. Dans le cadre de cet article nous nous intéresserons uniquement à celle que nous utilisons à savoir la similarité cosinus. Elle consiste à trouver les vecteurs les plus proches de celui qui représente la question formulée par l’utilisateur.

 

 

Mathématiquement, la similarité cosinus calcule l’angle entre deux vecteurs dans un espace multidimensionnel, en se basant sur le concept que des vecteurs pointant dans des directions similaires sont considérés comme similaires.

 

 

 

 

 

Plus l’angle est petit, plus les vecteurs sont similaires.

 

C’est grâce à cette formule mathématique que le retriever effectue la récupération des sections de document qui sont les plus pertinentes par rapport à la question de l’utilisateur.

Nous disposons désormais de tous les éléments nécessaires : une question et les informations requises pour y répondre. Il ne reste plus qu’à les exploiter pour conclure le processus.

 

4. Génération de la réponse

 

 

Le prompt utilisé par le LLM qui génère la réponse finale est composé de plusieurs parties, celles collectées dans les précédentes étapes ainsi que d’autres écrites à l’avance :

 

  • La section “System” est écrite à l’avance en dur dans le prompt. Elle sert à indiquer au modèle le comportement à adopter et de quelle manière générer la réponse.
  • La section “Context” contient les chunks des documents servant de contexte pour produire la réponse.
  • La section “Query” contient la question de l’utilisateur reformulée avec l’historique de la conversation.
  • La section “Assistant” est également écrite en dur, et explicite à quel endroit le modèle doit commencer la génération.

 

Afin de faciliter l’utilisation du PoC, le contenu généré est envoyé directement à l’interface utilisateur. Actuellement, nous utilisons Streamlit, une solution offrant des fonctionnalités simples à prendre en main pour le développement d’applications basées sur des LLM.

 

Ceci conclut les explications développées sur le fonctionnement du PoC et amène une ouverture sur les difficultés rencontrées au cours du développement ainsi que les limitations du produit dans son état actuel.

Difficultés et limitations

Prompt Engineering : Le prompt engineering est un des points les plus importants à creuser pour améliorer les résultats du PoC. L’application se base sur des chaînes composées de différents LLM et leurs prompts sont la clé pour augmenter la pertinence et la qualité des réponses.

 

Qualité et taille du contexte : Dans cette version du PoC, nous choisissons de faire des résumés des chunks des documents originaux pour les stocker sous forme d’embeddings. En réalité, nous pourrions simplement stocker plusieurs versions des chunks sans faire de résumés, pour ne pas perdre d’information. Pour cela, il faudrait découper à nouveau les chunks, et garder tous les embeddings dans notre stockage. Nous surveillons les progrès actuels pour trouver, ou développer, une solution visant à répondre à ce problème. Par exemple, lors de leurs dernières annonces, OpenAI a sorti une version de GPT-4 avec une fenêtre de contexte de 128k tokens, une capacité supérieure à celle de la grande majorité des LLM existants.

 

Stockage générique des chunks : Le dernier point à mentionner est le stockage des chunks originaux des documents utilisé par le retriever. Le PoC manipule à la fois texte et tableaux, mais ne stocke pas les chunks d’une session à l’autre. Cela découle du fait que le framework n’offre pas la possibilité de gérer le stockage de différents types de chunks autrement qu’en mémoire vive. Cela ralentit la réalisation de tests et n’est pas viable comme solution à terme. Si dans le futur le PoC est amené à gérer en plus des images comme mentionné dans cet article, ce problème sera d’autant plus important. Il faudra alors mettre en place un stockage pouvant gérer du texte, des tableaux, et des images, tout en étant utilisable par le retriever.

 

Conclusion

À notre échelle nous n’avons pas la prétention de développer un produit capable de répondre à tous les problèmes mais une solution capable de traiter des use cases spécifiques.

 

Comme indiqué dans les limitations, l’accent est mis sur l’amélioration des résultats à travers le traitement des documents. Nous sommes attentifs aux évolutions des RAG afin de traiter les documents plus efficacement. C’est dans cette idée que nous explorons actuellement des solutions pour rendre notre RAG multimodale, c’est-à-dire que nous pourrions traiter des images au même titre que du texte ou des tableaux.

 

Les progrès accomplis dans l’open-source laissent envisager un futur propice à l’utilisation de LLM par des petites structures et particuliers. On peut mentionner le travail de Mistral AI, qui a permis le développement de plusieurs modèles basés sur Mistral-7B, comme Zephyr-7B-beta par exemple. Ces LLM ont des performances qui rivalisent avec des modèles payants comme GPT-3.5 et peuvent être hébergés localement, les rendant accessibles par le grand public.

 

Pour clore cet article, nous aimerions adresser nos remerciements à la communauté open-source, sans laquelle ce PoC n’existerait pas dans sa forme actuelle. Les ressources mises à disposition et l’investissement de la communauté participent à l’apparition de multiples initiatives qui, nous en sommes sûrs, feront grandir l’ensemble de l’écosystème.

 

Nous espérons que cet article contribuera à son échelle à partager le savoir acquis tout au long du développement de ce PoC et qu’il en inspirera d’autres à explorer ce sujet si passionnant.

 

Références

 

Rédigé par :

  • Ewan Lemonnier
  • Etienne Le Gourrierec