Skip to Content
Reading notesGenerative AI Design Patterns

Generative AI Design Patterns

3 - Adding Knowledge : Bass

  • La notion de RAG est née en 2020, dans un papier de Facebook : Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks.
  • Les patterns décrits dans les chapitres 3 et 4 ont des liens logiques les uns avec les autres : a chaque nouveau besoin on ajoute une couche de complexité supplémentaire.

Pattern 6 : Basic RAG

  • Le pattern basique n’est probablement pas utilisable tel quel pour la plupart des cas, mais permet de comprendre ce qui manque ou non en fonction de notre besoin.
  • Le RAG vient répondre à des problèmes particuliers que posent les modèles de fondation :
    • Les données d’entraînement sont arrêtées au cutoff => le RAG apporte de la donnée fraîche.
    • La taille du modèle limite sa capacité à retenir des choses apprises => le RAG stocke l’information à l’extérieur du modèle.
    • Les données privées ne sont pas incluses dans l’entraînement => le RAG peut contenir des données privées d’une entreprise.
    • Le modèle peut halluciner s’il doit parler de choses qu’il ne connaît pas bien => le RAG donne les infos manquantes, pour réduire les chances d’hallucination.
    • Il ne peut pas citer des extraits exacts présents dans ses données d’entraînement => le texte est fourni directement, donc la citation peut avoir lieu.
  • Les informations présentes dans le prompt influencent grandement la réponse (grounding).
    • Il suffit par exemple de parler de nourriture, pour que quand on demande les meilleures villes du pays, le LLM les mentionne sous l’angle de la nourriture. Le RAG consiste justement à ajouter des morceaux de données pertinentes dans le contexte, pour influencer la réponse.
    • Une bonne pratique pour éviter les hallucinations est de dire au modèle quoi faire s’il ne trouve pas ce qu’on lui demande : par exemple nous dire qu’il n’a pas trouvé.
  • Le RAG est composé de 3 étapes :
    • 1 - Indexing : on a besoin de documents sous forme de chunks qu’on va retrouver ensuite.
      • On veut des chunks suffisamment petits pour ne pas surcharger le contexte.
      • On veut des chunks avec beaucoup d’infos, donc on enlève ce qui est répétitif comme les espaces en trop.
      • Les chunks ont besoin de metadata pour être tracées.
      • La manière la plus simple de faire les chunks est d’utiliser le nombre de caractères, et d’ajouter un overloap pour éviter les phrases coupées.
    • 2 - Retrieval : on retrouve les chunks pertinents.
      • Le moyen le plus simple est de chercher les termes exacts dans les chunks.
      • Si on a une phrase, on peut :
        • Enlever les stop words qui sont trop fréquents.
        • Et ensuite chercher les chunks qui contiennent le plus de mots parmi les mots les plus rares dans l’ensemble des chunks disponibles : on appelle ça TF-IDF (Term Frequency - Inverse Document Frequency).
        • BM25 est une version de TF-IDF modifiée qui le rend plus efficace en évitant que les termes très répétés dans un même chunk prennent trop le pas.
    • 3 - Generation : on crée le prompt en intégrant les chunks dedans, et on envoie tout ça au LLM.
  • Le parsing de PDF est globalement assez complexe : il faut récupérer le texte, les images et aussi les objets et leur positionnement. Il existe des outils spécialisés qui font l’extraction, ou alors on peut en extraire une image et la donner à un LLM multi-modal.
  • Si la totalité des documents rentrent dans le contexte du LLM, il peut mieux valoir de tout lui donner et lui poser la question, plutôt que de risquer de lui donner des données partielles via RAG.
  • Limitations du pattern :
    • Si les mots exacts qu’on recherche ne se trouvent pas dans les chunks pertinents pour nous, les chunks ne seront pas remontés par l’algo TF-IDF.
    • Le chunking lui-même peut être limitant : par exemple amener une partie du texte pertinent, mais pas la suite qui serait dans le chunk suivant.
  • Les patterns 7 à 12 permettent d’améliorer ce RAG basique mais ont un coût.
    • Il faut d’abord vérifier qu’on en a besoin, et qu’ils vont apporter une vraie plus value.
    • Ils viennent en complément du RAG basique : il faut éviter par exemple la tentation de mettre en place un RAG à embedding sans recherche exacte type TD-IDF.

Pattern 7 : Semantic Indexing

  • Le RAG basique trouve ses limites quand :
    • Les chunks contiennent des synonymes des mots clés recherchés plutôt que les mots exacts.
    • Il faut trouver les chunks par la signification globale de ce dont ça parle.
    • La query et les chunks sont dans des langues différentes.
    • Les chunks sont autre chose que du texte (images, vidéos, son).
    • Les chunks contiennent un formatage particulier, par exemple des données dans des tableaux.
  • Le semantic indexing répond à ces problèmes en encodant la sémantique du texte et des autres modaux dans des vecteurs, stockés dans une base de données vectorielle.
  • Il y a un trade off entre augmenter la dimensionnalité des vecteurs pour obtenir une représentation plus fine de la sémantique, et la question de la performance.
  • Concernant les stratégies de chunking de texte, on peut :
    • Couper à un nombre fixe de caractères :
      • Avec un overlap.
      • En laissant la dernière phrase se terminer.
      • En laissant le dernier paragraphe se terminer.
    • Utiliser la nature structurée des documents (par exemple markdown) pour couper à un niveau particulier de sous-section. Par exemple, chaque titre de niveau 2 forme un chunk.
    • Couper au niveau des changements sémantiques plutôt que du nombre de caractères.
      • Il existe des techniques de topic modeling pour trouver les points de coupe sémantiques, par exemple latent dirichlet, et nonnegative matrix factorization.
      • On peut aussi utiliser des embeddings de sous sections de notre document pour essayer d’identifier là où la sémantique change.
  • Concernant la stratégie de chunking des images, on peut :
    • Utiliser un LLM pour décrire l’image sous forme de texte, et chunker puis vectoriser ce texte.
    • Utiliser un LLM pour directement générer des embeddings à partir de l’image.
  • Concernant la stratégie de chunking des vidéos, on peut :
    • Utiliser un LLM pour décrire la vidéo sous forme de texte, et chunker puis vectoriser ce texte.
    • Sampler les images de la vidéo, et pour chaque sample, le traiter comme une image qu’on veut indexer sémantiquement.
  • Concernant la stratégie de chunking des tableaux, on peut :
    • Mettre le tableau dans un seul chunk dans le cas où il est suffisamment petit ou peu important.
    • Chunker le tableau comme du texte : par nombre de caractères ou par changements sémantiques. On peut en plus inclure les headers concernés dans chaque chunk.
    • Créer un chunk par row. Ça marche bien quand les rows sont indépendants, par exemple un tableau de transactions.
    • Créer un chunk pour chaque colonne entière. Ça marche bien quand les informations d’une même colonnes sont liées entre elles.
  • Dans le cas où les documents indexés contiennent du jargon complexe, il est possible que les embeddings ne rapprochent pas suffisamment bien certains termes. Pour éviter ça, on va construire et maintenir un dictionnaire des synonymes, qu’on utilisera pour faire du synonym expansion.
    • Exemple : dans un contexte juridique où discovery est parfois utilisé pour désigner deposition, What was the timeline for discovery in federal court? devient What was the timeline for discovery|disclosure|deposition in federal court?
    • On peut faire ce dictionnaire à la main et le maintenir, ou alors utiliser des techniques d’analyse statistique pour le construire automatiquement, ou encore utiliser un LLM pour faire l’expansion directement sans dictionnaire.
  • Une autre possibilité quand on est dans un domaine avec du jargon complexe c’est d’utiliser des modèles d’embeddings domain specific. Par exemple pour le médical ou le juridique.
  • Pour rendre la recherche plus pertinente, on peut ajouter du contexte à chaque chunk au moment de l’indexation.
    • Anthropic propose de le faire avec un appel LLM  où on donne le document entier, le chunk, et on demande le contexte en réponse.
      • Ils ont montré que ça réduit les erreurs de 67% dans plusieurs domaines.
      • On peut mettre le document entier au début du prompt et mettre en cache cette partie, pour que les appels LLM soient peu chers.
    • Cette pratique sera utile à la recherche sémantique comme à la recherche lexicale.
  • Le trade off fondamental du chunking (si trop petit on récolte du contexte épars, si trop gros les vecteurs ne réussiront peut être pas à trouver les chunks parce qu’ils encoderont trop de choses, et donc pas assez de nuances sémantiques) peut être surmonté si on accepte de payer le coût d’appels LLM et d’une base vectorielle plus grosse : on peut faire un chunking hiérarchique.
    • Il s’agit de faire une structure sous forme d’arbre : d’abord des chunks plutôt petits, puis pour chaque cluster de chunks qui vont ensemble, un noeud qui les résume, puis un niveau au dessus qui résume les noeuds clusters etc. jusqu’à arriver à un seul noeud racine.
    • Chacun des nœuds intermédiaires contiennent du texte résumé qu’il a fallu produire par des appels LLM, et chacun d’entre eux ont un embedding associé et sont un nœud dans la base vectorielle.
    • Au moment du retrieve, on va chercher tous les nœuds qui peuvent matcher sémantiquement : les originaux comme ceux qui ont un texte qui résume d’autres nœuds. On va ensuite donner l’ensemble de la hiérarchie des nœuds qui ont matché en contexte à la génération.
    • Cette approche s’appelle RAPTOR (Recursive Abstractive Processing for Tree-Organized Retrieval) et est une forme simplifiée de GraphRAG.
  • Le semantic indexing a quelques limitations :
    • Les chunks sont représentés par un vecteur de dimension fixe, qui compresse beaucoup l’information.
    • La stratégie de chunking est en soi difficile à optimiser.
    • Quand on arrive à des millions ou milliards de nœuds, on commence à avoir des problèmes de scalabilité. On est obligés d’utiliser la recherche par ANN (Approximate Nearest Neighbors) qui est moins précis.
    • Le semantic indexing en lui-même ne permet pas de prendre en compte la date des documents ni aucune forme de temporalité.
    • Il n’y a pas de raisonnement logique dans la recherche, seulement une ressemblance sémantique. Et donc on perd parfois des chunks importants qui sont pourtant liés à certains chunks qu’on a trouvés.
    • La recherche sémantique multimodale ne marche pas toujours très bien.
    • Dans le cas où on veut ajouter des dimensions numériques supplémentaires à notre embedding pour y caser des chiffres, il y a un risque que nos chiffres soient noyés dans la grande dimensionnalité du vecteur et donc n’aient pas suffisamment d’impact.

Pattern 8 : Indexing at Scale

  • Il y a plusieurs problèmes qui peuvent survenir avec un RAG en production :
    • Ambiguïté : plus la quantité de données en base augmente, plus on a un risque que certains mots soient mal interprétés sémantiquement à cause d’un manque de contexte.
    • Fraîcheur de la donnée : sans stratégie de mise à jour des chunks qui sont dans la base de données du RAG, les informations qu’ils contiennent risque de devenir petit à petit obsolètes, voire contradictoires.
    • Performance : à mesure que les données augmentent, la recherche est de plus en plus coûteuse.
    • Cycle de vie du modèle : dans le cas où on utilise un modèle propriétaire, s’il arrive en fin de vie et est déprécié, on va devoir réindexer l’ensemble de la base vectorielle avec un autre modèle plus récent, ce qui peut coûter très cher.
  • L’usage des métadata permet de l’essentiel de mitiger ces problèmes.
    • L’auteur conseille d’inclure les metadata suivantes sur chaque noeud :
      • Au sujet du document :
        • L’identification de la source du document
        • Date de création / modification
        • L’auteur
        • Les catégories associées
        • La complexité du document
        • La longueur du document
      • Au sujet du chunk :
        • La position dans le document initial
        • Les entités mentionnées (personnes, organisations etc.)
        • Le rôle sémantique du chunk (introduction, exemple, définition etc.)
        • La langue du chunk
      • Au sujet du domaine : selon le domaine des choses pertinentes.
        • Si c’est de la doc d’API : la version de la doc, les langages de programmation si c’est un SDK.
        • Si c’est de la documentation scientifique sur l’IA : la méthodologie, la taille des samples, les trouvailles clés.
        • etc.
      • Au sujet des autorisations :
        • Quels rôles ont droit d’avoir accès à ces données
        • Comment ils doivent s’authentifier
        • S’il y a des consentements à obtenir avant de donner accès à ces données
        • Si la donnée doit être chiffrée, anonymisée
  • L’ambiguïté inter-domaines peut être mitigée par le filtre par la metadata de catégorie.
    • En théorie ça peut aussi répondre en partie au problème de performance, puisqu’on fait une recherche vectorielle sur un sous-ensemble des nœuds. En pratique, toutes les bases de données vectorielles ne le supportent pas.
  • Le problème des informations contradictoires peut être mitigé par certaines metadata :
    • Si la date des deux chunks contradictoires est différente, le LLM pourra en déduire que le plus récent est celui à suivre parce qu’il indique une mise à jour de l’info.
    • Si la source des deux chunks contradictoires est différente, le LLM pourra prioriser l’une sur l’autre en fonction de son niveau d’autorité.
    • Le fait de limiter à un même domaine par la catégorie permet de limiter aussi les informations contradictoires entre chunks.
  • Le problème des informations obsolètes peut être mitigé en utilisant des metadata :
    • Au moment de la recherche :
      • On peut filtrer les chunks trouvés plus vieux qu’une certaine date.
      • On peut privilégier le contenu avec une date plus récente par un rerank des résultats de recherche.
    • De manière régulière sur la base elle-même : on peut éliminer les chunks plus vieux qu’une certaine date.
  • Concernant le problème de cycle de vie du modèle : il faut bien réfléchir au choix du modèle d’embedding en fonction du besoin.
    • Bien regarder la performance des modèles (l’auteur conseille le Massive Text Embedding Benchmark de Hugging Face ) et leur caractéristiques (par exemple la langue supportée, la spécificité à un domaine).
    • Un modèle open weight ne sera jamais obsolète, et pour un modèle propriétaire on peut viser une durée de cycle de vie longue.
    • Dans certains cas, changer de modèle en vaut quand même la peine : par exemple, si le nouveau modèle permet la même performance en divisant la dimensionnalité par 4, ou si on a besoin d’un modèle entraîné sur des données récentes pour appréhender des éléments sémantiques récents.
  • Les metadata ont aussi des limites :
    • Si les metadata ont des problèmes de qualité, ils ne permettront pas de mitiger les problèmes de manière fiable.
    • Certaines bases de données vectorielles ne supportent les metadata que de manière limitée.
    • Se fier uniquement à la date des documents peut être insuffisant pour régler les problèmes d’obsolescence selon le domaine.
    • Certains éléments de metadata vont être spécifiques à un domaine seulement, et donc potentiellement n’auront d’utilité que pour une partie des nœuds.
  • Comme autres solutions alternatives :
    • On peut très bien créer plusieurs indexes sémantiques (l’équivalents de tables), par exemple un par domaine.
    • Pour le problème de l’obsolescence, on peut maintenir des metadata de liens entre des chunks qui parlent de la même chose à des dates différentes. De cette manière, on peut systématiquement récupérer l’ensemble de ces chunks, pour faire comprendre au LLM l’historique de l’évolution de l’information.

4 - Adding Knowledge : Syncopation

Pattern 9 : Index-Aware Retrieval

  • Trouver les bons chunks correspondant à la question de l’utilisateur peut poser des difficultés :
    • Les termes de la question peuvent très bien ne pas être présents dans les chunks, ni sémantiquement, ni lexicalement.
    • Les termes techniques utilisés peuvent être différents entre les queries et les chunks.
    • Des détails importants peuvent être noyés dans le texte des chunks et donc difficilement trouvables.
    • Parfois il est nécessaire de retrouver plusieurs chunks liés entre eux de manière logique pour pouvoir répondre à l’utilisateur, mais le RAG par mots clés ou sémantique ne permet pas de faire ces liens.
  • HyDE (Hypothetical Document Embedding) :** **une première possibilité est de demander à un LLM de **donner une réponse préalable à la question, à l’étape de retrieval**, puis de chercher des documents concernant cette réponse, et enfin de passer ces documents et la première réponse du LLM au LLM à l’étape de génération.
    • On appelle cette technique de réponse intermédiaire .
    • Pour répondre au cas où il existe des points de vus divergents dans la documentation, on peut générer plusieurs réponses hypothétiques intermédiaires, et faire un retrieval sur chacune d’entre elles, avant de donner le tout au LLM de l’étape génération.
    • HyDE est notamment utile quand on a besoin de retrouver des chunks qui ont une structure logique entre eux, ou quand on a besoin de retrouver des chunks qui ont des détails qui seront potentiellement noyés dans le texte.
  • Query expansion : on peut enrichir la question initiale en ajoutant de nouvelles formulations et du contexte, de manière à ce que le retrieval puisse retrouver plus facilement les documents à donner au LLM à l’étape génération.
    • C’est notamment utile quand les utilisateurs font des queries avec un langage non technique, mais que la base des chunks contient du vocabulaire technique : l’expansion ajoute les mots techniques concernés.
  • Hybrid search : on peut faire à la fois une recherche lexicale (BM25) et une recherche sémantique. De nombreuses bases de données supportent la recherche hybride nativement, et acceptent un paramètre alpha qui indique quel poids on met sur l’aspect lexical et quel poids sur l’aspect sémantique pour récupérer les k chunks les plus pertinents demandés.
  • GraphRAG : il s’agit d’avoir une base de données de graphe (comme Neo4j) avec chacun des nœuds contenant le chunk sous forme de texte, sous forme d’embedding, et ayant des relations avec d’autres nœuds.
    • Quand chaque chunk est petit, ça permet d’aller chercher les chunks qui ont des relations logiques.
    • On peut aussi créer une structure en arbre (un peu comme l’approche RAPTOR cf. pattern 7) où l’embedding du parent sert d’appât et permet de récupérer ensuite les chunks enfants qui n’auraient peut être pas été récupérés par la recherche.
      • Et de la même manière, on peut générer un résumé du contenu dans les nœuds parents.
    • Le graphe doit normalement être modélisé à la main pour être pertinent, mais si on ne veut pas faire cet effort, on peut également utiliser LangChain pour demander à un LLM de créer automatiquement une modélisation en graphe à partir de chunks sans lien.
  • Quelques limitations :
    • HyDE et query expansion dépendent de la capacité du LLM à connaître le sujet, sinon il peut donner un résultat hors sujet halluciné, ou même un résultat basé sur des considérations anciennes selon les connaissances qu’il a arrêtées à la date de son cutoff.
    • La query expansion peut parfois faire dériver la question de l’utilisateur vers un terrain qui en fait ne l’intéressait pas : on trouve plus de documents, mais potentiellement hors sujet.
    • Un GraphRAG mal modélisé peut aussi faire remonter des chunks non pertinents.

Pattern 10 : Node Postprocessing

  • La liste de chunks trouvés peut présenter des problèmes :
    • Les chunks retrouvés peuvent être liés lexicalement ou sémantiquement à la question, mais ne pas être pertinents pour le besoin. Par exemple, trouver une table des matières qui contient le terme n’apportera sans doute rien.
    • Un chunk peut avoir seulement une partie de son texte pertinent pour une question donnée, mais la totalité du chunk sera inclue dans le prompt de génération.
    • Certains chunks peuvent être liés à des concepts proches sémantiquement, mais non pertinents pour la question, sans qu’on puisse le savoir avant d’avoir regardé le contenu du chunk.
    • Des chunks peuvent contenir de l’information obsolète.
    • La réponse générée par le RAG n’est pas personnalisée par rapport à l’utilisateur.
  • De nombreux outils existent en pos-processing du retrieval pour régler ces problèmes :
    • Reranking : une fois qu’on a récupéré les chunks pertinents par similarité lexicale et sémantique, on peut les donner à un juge LLM pour qu’il nous indique lesquels sont en fait les plus pertinents pour notre query.
      • Il existe des modèles qui ont été fine-tunés spécifiquement pour le reranking, par exemple BGE.
      • Le reranking ajoute de la latence et des coûts, tout comme à chaque fois qu’on utilise une technique où on fait des calls LLM supplémentaires (HyDE, query expansion etc.)
    • Hybrid search : si on rerank de toute façon, il n’y a pas forcément besoin de faire un weighted average avec un paramètre alpha entre le résultats lexicaux et les résultats sémantiques : on donne tout au reranker, et on garde les top k chunks.
    • Query expansion and decomposition : on peut modifier différemment la query qu’on donne à chaque retriever s’il y en a plusieurs.
      • Par exemple faire de l’expansion pour BM25 avec les synonymes mais pas pour le retriever sémantique.
      • On peut découper la query en plusieurs parties et faire plusieurs retrievals.
      • On peut faire un retrieval, et utiliser le résultat comme résultat intermédiaire pour en faire d’autres.
    • Filtering for obsolete information : on peut éliminer des chunks, par exemple basé sur une date minimale.
      • On pourrait aussi filtrer les chunks qui ont des informations contradictoires, mais les coûts risquent de ne pas en valoir la peine.
    • Contextual compression : si on fait un call LLM pour chaque chunk trouvé, par exemple dans le cadre du reranking pour lui attribuer une note de pertinence, on peut en profiter pour demander au LLM de nous extraire uniquement les informations qui sont utiles du chunk en question.
    • Disambiguation : si on fait déjà un call LLM pour chaque chunk trouvé, on peut en profiter aussi pour demander si les entités qui sont concernées sont bien les mêmes que celles qui sont présentes dans le 1er chunk.
    • Personalization and conversation history : on peut ajouter du contexte supplémentaire au prompt de génération. Ca peut par exemple être certaines choses tirées de l’historique de conversation, éventuellement en fonction des chunks récupérés.
  • Le problème principal du postprocessing c’est la latence et le coût.
    • Dans le cas où on a au moins 1 call LLM par chunk, il vaut mieux en profiter pour les grouper avec les autres étapes de post-processing.
    • Si on veut faire plusieurs actions il faut un vrai LLM, pas BGE.

Pattern 11 : Trustworthy Generation

  • Le RAG peut poser un certain nombre de problèmes de confiance.
    • Ces problèmes peuvent vraiment causer des dommages, notamment quand la réponse est critique : par exemple si une IA conseille un médecin sur le traitement à prescrire.
    • Exemples de problèmes :
      • On peut retrouver les mauvais chunks, ou ne pas retrouver ceux qui étaient pertinents pour répondre à la query.
      • Les chunks retrouvés peuvent avoir de mauvaises informations.
      • Le LLM de l’étape de génération peut mal interpréter les chunks, ou faire un raisonnement erroné.
      • Le LLM de l’étape de génération peut très bien halluciner des informations.
  • Out-of-domain detection : le RAG devrait être capable de détecter le cas où l’utilisateur soumet une query qui est hors sujet par rapport à ce qu’il peut donner comme information. Plusieurs méthodes peuvent être combinées, éventuellement en en prenant un weighted average.
    • Embedding distance : dans le cas où on a un RAG sémantique, on peut fixer un seuil de similarité en dessous duquel on considère que les chunks ne sont pas pertinents du tout. Ce seuil peut être trouvé empiriquement en comparant avec la valeur de similarité des chunks habituels.
    • Zero-shot classification : on peut utiliser un modèle spécialisé pour classifier chaque chunk en tant que valide ou non valide pour le domaine visé, pour éliminer les hors sujets au regard de la query.
    • Domain-specific keyword : on peut exiger que le chunk et/ou la query contiennent des mots clés d’une liste qu’on maintient pour éliminer le hors sujet.
  • Citations : elles permettent aux utilisateurs de vérifier par eux mêmes les informations et leur donnent plus confiance dans les réponses.
    • Source-level tracking : on peut inclure des citations exactes dans les metadata des chunks au moment de l’indexation, et les récupérer au retrival, pour les donner au LLM de génération.
    • Classification-based citations : pour éviter d’inclure trop de citations, on peut ajouter un composant classifier qui va vérifier si pour chaque connaissance qu’on apporte, si une citation serait pertinente à fournir ou pas avant de l’inclure.
    • Token-level attribution : on va utiliser un mécanisme interne au LLM de l’étape génération, en suivant son mécanisme d’attention, pour s’assurer que la citation est correctement attribuée.
      • Il s’agit d’un domaine en cours de recherche.
    • NDLR : d’autres méthodes existent pour les citations, notamment le fait de vérifier après génération pour diminuer le nombre d’hallucinations.
  • Guardrails : on peut les installer à plusieurs niveaux pour sécuriser l’exécution de la pipeline.
    • Avant le retrieval :
      • Utiliser l’out-of-domain detection pour filtrer l’input utilisateur
      • Ne mettre que des chunks venant de sources fiables dans la DB.
    • Après le retrieval :
      • Vérifier les metadata des chunks
      • Prioriser et filtrer les chunk selon des règles prédéfinies, y compris par exemple liés à la privacy
      • Fact-checker les chunks contre le RAG lui-même
    • Avant la génération :
      • Filtrer les chunks obsolètes ou ayant un contenu dangereux
      • Filtrer les chunks pour obtenir une diversité des sources
    • Après la génération :
      • S’assurer que les citations sont faites et sont correctes
      • Fact-checker les informations de la réponse
      • Vérifier la privacy, et les contenus dangereux
    • L’auteur met en avant 3 libs : Guardrails AI qui est une collection d’outils de guardrails, et DeepEval et Ragas qui sont des librairies d’evals.
  • Observability : obtenir une visibilité constante sur la pipeline RAG augmente la confiance et permet une amélioration continue.
    • On obtient la possibilité de voir les input/outputs LLM, et des métriques telles que context relevance, response relevance, faithfulness, context recall et context precision.
    • L’auteur conseille d’utiliser des solutions existantes, par exemple en open source : Arize Phoenix, Comet Opik, Langfuse, Langtrace.
  • Human feedback : on peut ajouter du feedback humain de diverses manières.
    • En mode human in the loop juste après le retrieval, pour scorer les chunk à la place du LLM as a judge, ou alors après la génération, éventuellement seulement les générations où la méthode automatique lève un doute.
    • En mode offline, en constituant un dataset de rankings des retrievals pour entraîner un modèle.
    • Le comportement des utilisateurs peut aussi être récupéré pour comprendre les problèmes du RAG ou entraîner un modèle.
  • CRAG (Corrective RAG) : on a une étape d’évaluation qui suit le retrieval. Si l’évaluation indique que le chunk est ambigu ou risque de provoquer des hallucinations, 2 actions peuvent être faites avant de le donner à l’étape de génération :
    • Rechercher sur le web ou dans des bases internes de l’entreprise pour enrichir le chunk et le rendre moins ambigu.
    • Modifier le chunk pour le recenter sur l’information importante qu’il comporte de manière claire.
  • Self-RAG (ou reflection RAG) : il s’agit de transformer le RAG en un agent qui va à chaque étape décider de ce qu’il fait : il va critiquer la qualité des chunks retrieved, les filtrer / modifier, décider s’il faut récupérer plus de documents avant de répondre etc.
    • On se retrouve avec un système plus complexe, avec plus de latence et de coûts, mais avec moins d’hallucinations et une meilleure qualité globale des réponses.
  • User interface design : une manière de gagner la confiance de l’utilisateur est de donner des détails sur le processus du RAG : liens des sources vérifiables, citations, jauge de confiance.
    • Pour ne pas noyer les utilisateurs sous une information trop importante, on peut montrer peu de choses de base, et laisser les détails en libre accès via des options.
    • Un autre levier de confiance peut aussi être le fait de laisser l’utilisateur configurer certains aspects de fonctionnement du RAG.
    • Et enfin il est important de prendre ses feedbacks et lui montrer qu’on les intègre au produit.
  • Quelques trade offs à prendre en compte :
    • Plus on met en place des outils avancés dans le RAG, plus on ajoute de la latence, de la complexité et de coûts en échange d’une meilleure pertinence.
    • Si on utilise des modèles pour scorer le fait de filtrer, il faut que ces modèles soient et restent pertinents vis-à-vis du domaine à mesure qu’on ajoute des chunks, donc il faut les maintenir.
    • Si on met en place des guardrails trop restrictifs, on va être très sécure sur ce qui est montré, mais on risque de filtrer trop de choses et rendre le RAG inutile.
    • Plutôt qu’un self-RAG, on peut envisager un RAG hybride qui cherche dans plusieurs sources, ça donne souvent un résultat suffisant.
    • Plutôt que d’ajouter trop d’outils pour régler les hallucinations, on peut mettre le focus sur la UX en détaillant ce qui est vérifié ou non, et en donnant à l’utilisateur des leviers de configuration.
  • Il existe de nombreux tutoriels pour implémenter les techniques de out-of-domain detection, CRAG ou self-RAG.
  • Les RAG ont des limitations pour les recherches complexes :
    • Limite de taille de contexte : pour répondre à une question complexe il y a parfois besoin de plus de chunks que ce qui rentre dans le contexte du LLM.
    • Query ambiguity : si la question posée pose des termes qui peuvent faire remonter des chunks de plusieurs notions sans rapport, le RAG simple aura du mal à choisir, pourtant il y a probablement une des solutions qui est la plus vraisemblable.
    • Informations obsolètes : le RAG simple ne permet pas de vérifier les informations récupérées depuis la base.
    • Raisonnement limité : le RAG simple permet de retrouver des informations, et de faire un réponse basé sur elles, il ne permet pas de raisonner de manière complexe au travers de plusieurs documents, avec plusieurs tours de raisonnement.
  • Le deep search permet de répondre à ces problématiques, en ajoutant une étape de raisonnement après le retrieval, en faisant autant de retrievals que nécessaire jusqu’à attendre l’objectif ou épuiser le budget, et en utilisant des tools variés (web search, retrieval etc.).
    • Le problème de contexte limité est levé par le fait qu’on fait plusieurs retrievals et qu’on stocke seulement les informations qu’on trouve utiles à partir de ces chunks.
    • L’ambiguïté dans la query est réglée par le fait que le modèle est capable de voir qu’il y a une ambiguïté, et reformuler la query pour viser un concept plus précis qu’il a identifié par son raisonnement.
    • Concernant l’information obsolète, le deep search intègre des tools pour fact checker, y compris avec des sources en temps réel.
    • Le deep search propose un raisonnement en plusieurs étapes par définition.
  • Certaines entreprises distinguent deep search où elles créent une réponse globale à la question initiale au deep research où elles créent une réponse plus longue avec l’ensemble des recherches faites. Il s’agit simplement d’une différence de format de sortie.
  • Le deep search peut utiliser les techniques décrites dans le pattern de node postprocessing en utilisant des modèles de fondation : décomposer une query en sub-queries, extraire l’information pertinente des chunks, évaluer et filtrer ces informations etc.
    • On peut notamment décomposer la query de l’utilisateur en sub-queries plus simples, et traiter le problème étape par étape pour converger vers une solution.
  • Le deep search fonctionne de manière itérative, donc savoir évaluer en direct où est-ce qu’il en est et quand est-ce qu’il a une réponse qui passe la barre est très important.
    • L’auteur conseille d’utiliser le framework Ragas pour fournir les métriques nécessaires, en décidant d’un weighted average entre plusieurs métriques pré-définies, qui couvriraient au moins : relevance, comprehensiveness, accuracy and factual correctness, coherence, logical flow and organization, citation quality, efficiency.
    • Il nous faut aussi créer un dataset pour l’évaluation offline.
  • Un autre aspect important du deep search est aussi la synthèse des informations récupérées. Il doit notamment identifier les entités au travers des documents, y compris quand il y a une ambiguïté, et identifier et gérer les contradictions entre les documents.
  • Le problème principal du deep search est sa lenteur, même si on peut en partie la mitiger en parallélisant certaines actions.