Inès Bouaziz & Etienne Le Gourrierec|17 novembre 2023
Introduction
Contexte
Dans le paysage moderne, les applications telles que ChatGPT, basees sur des LLM (Large Language Model), sont en passe de devenir omnipresentes. Leur potentiel s’etend a travers tous les secteurs, du service client a la gestion des ressources humaines, en offrant des solutions adaptees a des problemes de tous types.
Les nombreuses annonces des acteurs du secteur soulignent une democratisation de l’IA. L’evolution constante des outils permet d’entrevoir, pour les entreprises, des perspectives de performance et d’efficacite operationnelle grace a la GenAI.
Notre processus de R&D
Au vu des avancees faites ces derniers mois dans le domaine de l’intelligence artificielle generative, Nobori a souhaite lancer une demarche en interne pour tenter de comprendre et maitriser les concepts cles 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 egalement a developper des solutions innovantes en interne pour faciliter le travail des consultants et automatiser certains processus.
Notre R&D nous a amene a developper un chatbot qui implemente de la RAG (Retrieval-Augmented Generation). Un chatbot est simplement un agent conversationnel, qui dans notre cas se base sur un LLM pour generer des reponses aux questions de l’utilisateur.
On rencontre regulierement deux problemes lorsque l’on interagit avec des LLM :
Hallucinations : la capacite d’un modele a inventer des informations lors de la generation de texte.
Reponses obsoletes : certaines informations qu’utilisent les modeles peuvent provenir de sources caduques au moment de la generation.
La RAG aspire a pallier ces problemes, en mettant en place un systeme de recherche d’informations pour fournir du contexte au modele lors de la generation des reponses. Ce concept fondamental permet d’enrichir la generation de texte en extrayant des donnees pertinentes au prealable pour contextualiser et ameliorer la precision des reponses.
Vision macro du PoC
Ce PoC est une application qui donne la possibilite 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 reflexions 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 schema du fonctionnement general qui sera detaille au long de cet article :
Dans un premier temps, l’utilisateur upload ses documents, qui passent par une etape de Document Processing avant d’etre stockes.
L’utilisateur interagit ensuite avec l’application comme avec un chatbot classique. Pendant le Query Processing, la question est reformulee pour inclure l’historique de la conversation, puis utilisee pour la suite du processus.
La phase de Retrieval est le coeur d’une application de questions & reponses (Q&A) sur des documents. On cherche a trouver des informations pertinentes dans les documents grace a la requete utilisateur. Ces informations serviront de contexte pour repondre a sa demande.
Une fois le contexte et la question obtenus, ils sont transmis au prompt final et le tout est utilise par le modele qui s’occupe de generer notre reponse. Ceci conclut la phase d’Answer Generation !
Paysage technologique
Nous avons commence par etudier diverses solutions open-source existantes, qui repondaient en partie a 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 averees insuffisamment flexibles, et nous recherchions une application offrant une modularite sur le long terme.
N’ayant pas trouve de solution satisfaisante, nous avons decide de developper notre propre application. Nous nous sommes interesses au choix d’un framework afin d’entamer le developpement du PoC. Nous avons effectue un comparatif entre LlamaIndex et LangChain et notre choix s’est finalement porte sur ce dernier. Il possede des exemples concrets illustrant les differents concepts qu’il implemente, ainsi qu’une documentation fournie et explicite. De plus, le GitHub du projet est bien plus actif et c’est un argument majeur a prendre en compte dans le choix d’un framework open-source.
Une fois le choix du framework effectue il nous fallait trouver le moyen d’acceder a des modeles. On interagit avec un LLM par le biais de requetes API ou via une CLI. Deux types d’hebergements sont possibles :
L’execution d’un modele en local, comme avec Ollama par exemple. Le LLM tourne alors sur notre architecture, a savoir un ordinateur portable dans notre cas.
Dans la version actuelle du PoC, nous avons le choix quant a l’utilisation de modeles open-source en local ou de modeles payants tels que GPT-4 d’OpenAI. Notre application permet d’interchanger simplement le ou les modeles utilises.
Focus sur les notions de Prompt et Chaine
Avant de rentrer dans les details, abordons simplement deux notions importantes a avoir en tete pour la suite de l’article :
Prompt : une serie d’instructions ecrites en langage naturel, donnees au modele pour lui indiquer ce qu’on attend de sa reponse. Ce prompt peut etre une question, une amorce de phrase, voire une combinaison de plusieurs elements, et sert de guide pour orienter la generation du modele. Il conditionne la maniere dont le modele comprend la tache a accomplir et influence directement la nature et le contenu de la reponse generee en sortie.
Exemple de prompt
Chaine : l’utilisation d’un LLM de maniere isolee convient aux applications simples, mais les applications plus complexes necessitent l’enchainement de plusieurs LLM, soit entre eux, soit avec d’autres composants. Un des principes fondamentaux du framework LangChain, qui lui a donne son nom, est le concept de chaine. On definit une chaine de maniere tres generique comme une sequence d’appels a differents composants geres par le framework (prompt, LLM, memoire, etc.), qui peuvent eux-memes inclure d’autres chaines.
Analyse approfondie des processus
Avec ces concepts en tete, on peut aborder le coeur des explications, en commencant par une version plus detaillee du schema presente plus tot :
Cette representation offre une vue complete du fonctionnement de chaque partie du processus et leurs interconnexions.
1. Traitement des documents
Il existe deux types de donnees, les donnees structurees et non structurees. Pour fonctionner au mieux, les LLM ont besoin d’etre alimentes par des donnees claires et bien organisees.
Lorsque l’information provient de bases de donnees ou d’outils comme Notion, qui gardent les donnees de maniere structuree, leur exploitation est simple. Dans notre cas on traite des documents PDF, qui rentrent dans la categorie des donnees non structurees. Pour gerer l’extraction des elements de ce type de document, on utilise l’ETL (Extract, Transform, Load) nomme Unstructured.
La gestion du traitement des documents repose sur l’application du concept de Parent Document. Le document original est decoupe en sections, appelees chunks, qui sont ensuite stockes.
Puis, des modifications sont apportees a ces chunks, telles que la creation de resumes (on pourrait egalement diviser a nouveau les chunks pour obtenir des sections plus petites). Un identifiant maintient le lien entre les chunks d’origine et les chunks resumes, puis ces derniers sont sauvegardes sous forme d’embeddings. Grace a cette methode, il devient facile de rechercher parmi les embeddings tout en obtenant des resultats exploitables.
Mais que signifie embeddings ? Developpons cette notion !
Un embedding est une representation vectorielle couramment utilisee par les modeles d’IA car elle facilite la manipulation des donnees.
Dans notre cas, un embedding de mot peut avoir des centaines de valeurs, chacune representant un aspect different 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”.
A mesure que le modele traite l’ensemble de mots que represente une phrase, ici “LangChain is a framework”, il produit un vecteur — ou une liste de valeurs — et l’ajuste en fonction de la proximite de chaque mot avec les autres mots dans les donnees d’entrainement.
Au terme de la phase de traitement des documents, on obtient un lieu de stockage mixte, qui contient a la fois des chunks et des embeddings. On se servira de ce lieu de stockage lors de la phase de Retrieval, qui apparait plus tard dans le processus.
2. Traitement de la requete utilisateur
La traitement de la requete de l’utilisateur implique une phase de reformulation qui s’appuie sur l’historique de conversation. Cela permet d’integrer les informations prealablement echangees a la nouvelle question.
Voici un exemple de prompt qui pourrait etre utilise a 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 remplacees par leur valeur quand le prompt est utilise.
3. Retrieval — Recuperation du contexte
Cette phase est le coeur d’une application de questions & reponses (Q&A) sur des documents. Comme precise precedemment, le retriever se base sur un stockage mixte :
docstore : chunks des documents originaux
vectorstore : embeddings des resumes des chunks
Dans notre cas le stockage des documents (docstore) se fait dans la memoire vive et le vectorstore utilise lui une base de donnee vectorielle appele ChromaDB.
On effectue ensuite une recherche de similarite sur le contenu du vectorstore en utilisant la question reformulee obtenue precedemment, et on obtient en retour les chunks contenant les informations pertinentes qui serviront de contexte au LLM final.
On reussit ainsi a recuperer des chunks presents dans les documents originaux qui contiennent des informations utiles a la generation de la reponse.
Rentrons plus en detail sur la recherche de similarite :
La recherche de similarite peut prendre differentes formes. Dans le cadre de cet article nous nous interesserons uniquement a celle que nous utilisons a savoir la similarite cosinus. Elle consiste a trouver les vecteurs les plus proches de celui qui represente la question formulee par l’utilisateur.
Mathematiquement, la similarite 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 consideres comme similaires.
Plus l’angle est petit, plus les vecteurs sont similaires.
C’est grace a cette formule mathematique que le retriever effectue la recuperation des sections de document qui sont les plus pertinentes par rapport a la question de l’utilisateur.
Nous disposons desormais de tous les elements necessaires : une question et les informations requises pour y repondre. Il ne reste plus qu’a les exploiter pour conclure le processus.
4. Generation de la reponse
Le prompt utilise par le LLM qui genere la reponse finale est compose de plusieurs parties, celles collectees dans les precedentes etapes ainsi que d’autres ecrites a l’avance :
La section “System” est ecrite a l’avance en dur dans le prompt. Elle sert a indiquer au modele le comportement a adopter et de quelle maniere generer la reponse.
La section “Context” contient les chunks des documents servant de contexte pour produire la reponse.
La section “Query” contient la question de l’utilisateur reformulee avec l’historique de la conversation.
La section “Assistant” est egalement ecrite en dur, et explicite a quel endroit le modele doit commencer la generation.
Afin de faciliter l’utilisation du PoC, le contenu genere est envoye directement a l’interface utilisateur. Actuellement, nous utilisons Streamlit, une solution offrant des fonctionnalites simples a prendre en main pour le developpement d’applications basees sur des LLM.
Ceci conclut les explications developpees sur le fonctionnement du PoC et amene une ouverture sur les difficultes rencontrees au cours du developpement ainsi que les limitations du produit dans son etat actuel.
Difficultes et limitations
Prompt Engineering : Le prompt engineering est un des points les plus importants a creuser pour ameliorer les resultats du PoC. L’application se base sur des chaines composees de differents LLM et leurs prompts sont la cle pour augmenter la pertinence et la qualite des reponses.
Qualite et taille du contexte : Dans cette version du PoC, nous choisissons de faire des resumes des chunks des documents originaux pour les stocker sous forme d’embeddings. En realite, nous pourrions simplement stocker plusieurs versions des chunks sans faire de resumes, pour ne pas perdre d’information. Pour cela, il faudrait decouper a nouveau les chunks, et garder tous les embeddings dans notre stockage. Nous surveillons les progres actuels pour trouver, ou developper, une solution visant a repondre a ce probleme. Par exemple, lors de leurs dernieres annonces, OpenAI a sorti une version de GPT-4 avec une fenetre de contexte de 128k tokens, une capacite superieure a celle de la grande majorite des LLM existants.
Stockage generique des chunks : Le dernier point a mentionner est le stockage des chunks originaux des documents utilise par le retriever. Le PoC manipule a la fois texte et tableaux, mais ne stocke pas les chunks d’une session a l’autre. Cela decoule du fait que le framework n’offre pas la possibilite de gerer le stockage de differents types de chunks autrement qu’en memoire vive. Cela ralentit la realisation de tests et n’est pas viable comme solution a terme. Si dans le futur le PoC est amene a gerer en plus des images comme mentionne dans cet article, ce probleme sera d’autant plus important. Il faudra alors mettre en place un stockage pouvant gerer du texte, des tableaux, et des images, tout en etant utilisable par le retriever.
Conclusion
A notre echelle nous n’avons pas la pretention de developper un produit capable de repondre a tous les problemes mais une solution capable de traiter des use cases specifiques.
Comme indique dans les limitations, l’accent est mis sur l’amelioration des resultats a travers le traitement des documents. Nous sommes attentifs aux evolutions des RAG afin de traiter les documents plus efficacement. C’est dans cette idee que nous explorons actuellement des solutions pour rendre notre RAG multimodale, c’est-a-dire que nous pourrions traiter des images au meme titre que du texte ou des tableaux.
Les progres accomplis dans l’open-source laissent envisager un futur propice a l’utilisation de LLM par des petites structures et particuliers. On peut mentionner le travail de Mistral AI, qui a permis le developpement de plusieurs modeles bases sur Mistral-7B, comme Zephyr-7B-beta par exemple. Ces LLM ont des performances qui rivalisent avec des modeles payants comme GPT-3.5 et peuvent etre heberges localement, les rendant accessibles par le grand public.
Pour clore cet article, nous aimerions adresser nos remerciements a la communaute open-source, sans laquelle ce PoC n’existerait pas dans sa forme actuelle. Les ressources mises a disposition et l’investissement de la communaute participent a l’apparition de multiples initiatives qui, nous en sommes surs, feront grandir l’ensemble de l’ecosysteme.
Nous esperons que cet article contribuera a son echelle a partager le savoir acquis tout au long du developpement de ce PoC et qu’il en inspirera d’autres a explorer ce sujet si passionnant.