📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

Learn RAG From Scratch – Python AI Tutorial from a LangChain Engineer

freeCodeCamp.org2:33:11

Transcription

Dans ce cours, Lance Martin vous enseignera comment implémenter RAG à partir de zéro. Lance est ingénieur logiciel chez LangChain, et LangChain est l'une des méthodes les plus courantes pour implémenter RAG. Lance vous aidera à comprendre comment utiliser RAG pour combiner des données personnalisées avec des LLM.

Bonjour, je suis Lance Martin, ingénieur logiciel chez LangChain. Je vais donner un court cours axé sur RAG, ou Retrieval Augmented Generation, qui est l'une des idées les plus populaires dans les LLM aujourd'hui. La motivation derrière cela est que la plupart des données mondiales sont privées, alors que les LLM sont entraînés sur des données publiquement disponibles. Vous pouvez voir en bas, sur l'axe des x, le nombre de tokens utilisés pour le pré-entraînement de divers LLM. Cela varie d'environ 1,5 billion de tokens pour les modèles plus petits comme LLaMA 2, à un nombre très élevé que nous ne connaissons pas pour les modèles propriétaires comme GPT-4 ou Claude 3.

Ce qui est vraiment intéressant, c'est que la fenêtre de contexte, c'est-à-dire la capacité à introduire des informations externes dans ces LLM, devient de plus en plus grande. Il y a environ un an, les fenêtres de contexte étaient comprises entre 4 000 et 8 000 tokens, ce qui correspond à une douzaine de pages de texte. Récemment, nous avons vu des modèles allant jusqu'à un million de tokens, ce qui représente des milliers de pages de texte. Ainsi, bien que ces LLM soient entraînés sur des données publiques à grande échelle, il devient de plus en plus faisable de leur fournir cette énorme masse de données privées qu'ils n'ont jamais vues. Ces données privées peuvent être vos données personnelles, des données d'entreprise ou toute autre information que vous souhaitez transmettre à un LLM et qui n'est pas nativement dans son ensemble d'entraînement.

C'est donc la principale motivation pour RAG. L'idée est que les LLM sont au centre d'un nouveau type de système d'exploitation, et il devient de plus en plus essentiel de pouvoir introduire des informations provenant de sources externes, telles que des données privées, dans les LLM pour traitement. C'est donc la motivation générale de RAG.

Maintenant, RAG signifie Retrieval Augmented Generation, et vous pouvez y penser en trois étapes très générales. Il y a un processus d'indexation des données externes. Vous pouvez y penser comme la construction d'une base de données, par exemple. De nombreuses entreprises disposent déjà de bases de données à grande échelle sous différentes formes : bases de données SQL, bases de données relationnelles, bases de données vectorielles, etc. L'objectif est que les documents soient indexés de manière à pouvoir être récupérés en fonction de certaines heuristiques relatives à une entrée, comme une question. Ces documents pertinents peuvent être transmis à un LLM, et le LLM peut produire des réponses basées sur ces informations récupérées. C'est donc la pièce maîtresse ou l'idée centrale derrière RAG et pourquoi c'est une technologie vraiment puissante, car elle unit la connaissance et la capacité de traitement des LLM avec des sources de données externes privées à grande échelle, où vit encore la plupart des données importantes du monde.

Dans les courtes vidéos suivantes, nous allons construire une compréhension complète du paysage RAG et nous couvrirons un certain nombre d'articles et de techniques intéressants qui expliquent comment faire du RAG. J'ai vraiment décomposé cela en quelques sections différentes. En commençant par une question à gauche, la première section est ce que j'appelle la traduction de requête. Cela couvre un certain nombre de méthodes différentes pour prendre une question d'un utilisateur et la modifier d'une manière ou d'une autre pour la rendre mieux adaptée à la récupération à partir d'un des index dont nous avons parlé. Cela peut utiliser des méthodes comme la réécriture de requête, la décomposition de la requête en sous-questions constitutives.

Ensuite, il y a la question du routage : prendre cette requête décomposée ou réécrite et la router au bon endroit. Vous pourriez avoir plusieurs bases de données vectorielles, une base de données relationnelle, une base de données graphique et une base de données vectorielle. Le défi est donc de faire parvenir une question à la bonne source.

Ensuite, il y a le défi de la construction de requête, qui consiste essentiellement à prendre du langage naturel et à le convertir en DSL nécessaire pour la source de données avec laquelle vous souhaitez travailler. Un exemple classique ici est le texte vers SQL, qui est un processus bien étudié. Mais le texte vers Cypher pour les bases de données graphiques est très intéressant. Le texte vers des filtres de métadonnées pour les bases de données vectorielles est également un très grand domaine d'étude.

Ensuite, il y a l'indexation. C'est le processus de prise de vos documents et de leur traitement d'une manière ou d'une autre afin qu'ils puissent être facilement récupérés. Il existe un certain nombre de techniques pour cela. Nous parlerons de différentes méthodes d'embedding, nous parlerons de différentes stratégies d'indexation.

Après la récupération, il existe différentes techniques pour réorganiser ou filtrer les documents récupérés. Et enfin, nous parlerons de la génération et d'un nouvel ensemble de méthodes intéressantes pour faire ce que nous pourrions appeler du RAG actif. Dans cette étape de récupération ou de génération, les documents sont notés, les réponses sont notées, notées pour leur pertinence par rapport à la question, notées pour leur fidélité aux documents, c'est-à-dire vérifier les hallucinations. Si l'un ou l'autre échoue, on renvoie un feedback, on récupère ou on réécrit la question, on régénère la réponse, etc. Il existe donc un ensemble de méthodes très intéressantes que nous allons aborder, qui couvrent la récupération et la génération avec feedback.

En termes de plan général, nous commencerons par les bases. Nous passerons par l'indexation, la récupération et la génération, de manière très rudimentaire. Ensuite, nous parlerons de techniques plus avancées que nous venons de voir sur la diapositive précédente : transformations de requête, routage, construction, etc.

Bonjour, je suis Lance de LangChain. C'est la deuxième vidéo de notre série RAG à partir de zéro, axée sur l'indexation. Dans la vidéo précédente, vous avez vu les principaux composants des pipelines RAG : indexation, récupération et génération. Ici, nous allons approfondir l'indexation et en donner un bref aperçu.

Le premier aspect de l'indexation est que nous avons des documents externes que nous voulons charger et mettre dans ce que nous appelons un récupérateur. Le but de ce récupérateur est simplement, étant donné une question d'entrée, de trouver des documents qui sont liés à ma question d'une manière ou d'une autre. La façon d'établir cette relation, cette pertinence ou cette similarité, se fait généralement à l'aide d'une représentation numérique des documents. La raison en est qu'il est très facile de comparer des vecteurs, par exemple, de nombres, par rapport à du texte libre. Ainsi, de nombreuses approches ont été développées au fil des ans pour prendre des documents texte et les compresser en une représentation numérique qui peut ensuite être très facilement recherchée.

Il existe quelques façons de faire cela. Google et d'autres ont développé de nombreuses méthodes statistiques intéressantes où vous prenez un document, vous regardez la fréquence des mots et vous construisez ce qu'ils appellent des vecteurs épars, de sorte que les emplacements des vecteurs correspondent à un grand vocabulaire de mots possibles. Chaque valeur représente le nombre d'occurrences de ce mot particulier, et c'est épars car il y a bien sûr beaucoup de zéros, un très grand vocabulaire par rapport à ce qui est présent dans le document. Il existe de très bonnes méthodes de recherche sur ce type de représentation numérique.

Plus récemment, des méthodes d'embedding basées sur l'apprentissage automatique ont été développées. Vous prenez un document et vous construisez une représentation compressée de longueur fixe de ce document, avec des méthodes de recherche correspondantes très performantes sur les embeddings. L'intuition ici est que nous prenons des documents et nous les divisons généralement, car les modèles d'embedding ont des fenêtres de contexte limitées, de l'ordre de 512 tokens à 8 000 tokens ou plus, mais elles ne sont pas infinies. Les documents sont donc divisés et chaque document est compressé en un vecteur qui capture le sens sémantique du document lui-même. Les vecteurs sont indexés, les questions peuvent être encodées de la même manière, puis une comparaison numérique sous une forme ou une autre, en utilisant des types de méthodes très différents, peut être effectuée sur ces vecteurs pour trouver les documents pertinents par rapport à ma question.

Faisons un rapide tour de code sur certains de ces points. J'ai mon notebook ici, je l'ai installé. J'ai configuré quelques clés API pour LangSmith, qui sont très utiles pour le traçage, comme nous le verrons bientôt. Précédemment, j'ai parcouru ce démarrage rapide qui montrait comment organiser ces pipelines RAG. Ici, je vais approfondir un peu l'indexation. Je vais prendre une question et un document, et d'abord, je vais simplement calculer le nombre de tokens dans, par exemple, la question. C'est intéressant car les modèles d'embedding et les LLM en général fonctionnent sur des tokens, il est donc agréable de comprendre la taille des documents que j'essaie d'introduire. Dans ce cas, c'est évidemment une question très petite.

Je vais spécifier les embeddings OpenAI, je spécifie un modèle d'embedding ici et je dis simplement "embed query". Je peux passer ma question, mon document, et ce que vous pouvez voir ici, c'est que cela s'exécute et que cela est mappé à un vecteur de longueur 1536. Cette représentation vectorielle de longueur fixe sera calculée pour les documents et pour n'importe quel document. Vous calculez donc toujours ce vecteur de longueur fixe qui encode la sémantique du texte que vous avez passé.

Je peux ensuite faire des choses comme la similarité cosinus pour les comparer. Comme nous le verrons ici, je peux charger des documents, c'est comme nous l'avons vu précédemment. Je peux les diviser et je peux les indexer ici, comme nous l'avons fait auparavant. Mais nous pouvons voir sous le capot ce que nous faisons réellement : nous prenons chaque segment, nous l'encodons en utilisant les embeddings OpenAI dans cette représentation vectorielle, et cela est stocké avec un lien vers le document d'origine dans notre base de données vectorielle. Ensuite, nous verrons comment effectuer la récupération à l'aide de cette base de données vectorielle.

Bonjour, je suis Lance de LangChain. C'est la troisième vidéo de notre série RAG à partir de zéro, construisant de nombreuses motivations pour RAG à partir des composants de base. Nous allons parler de la récupération aujourd'hui. Dans les deux dernières courtes vidéos, j'ai décrit l'indexation et donné un aperçu de ce flux, qui commence par l'indexation de nos documents, la récupération de documents pertinents pour notre question, puis la génération de réponses basées sur les documents récupérés.

Nous avons vu que le processus d'indexation rend les documents faciles à récupérer. Il suit un flux qui ressemble à ceci : nous prenons nos documents, nous les divisons d'une manière ou d'une autre en petits morceaux qui peuvent être facilement encodés. Ces embeddings sont ensuite des représentations numériques de ces documents qui sont facilement recherchables et stockées dans un index. Lorsqu'on lui donne une question qui est également encodée, l'index effectue une recherche de similarité et renvoie les morceaux pertinents pour la question.

Si nous creusons un peu plus sous le capot, nous pouvons y penser comme ceci : si nous prenons un document et que nous l'encodons, imaginons que cet embedding ait seulement trois dimensions. Chaque document est projeté en un point dans cet espace 3D. L'idée est que l'emplacement dans l'espace est déterminé par le sens sémantique ou le contenu de ce document. Donc, pour suivre cela, les documents situés à des emplacements similaires dans l'espace contiennent des informations sémantiques similaires. Cette idée très simple est la pierre angulaire de nombreuses méthodes de recherche et de récupération que vous verrez avec les bases de données vectorielles modernes.

Ainsi, en particulier, nous prenons nos documents, nous les encodons dans cet espace 3D (dans ce cas, un exemple jouet), nous prenons notre question, nous faisons de même, nous pouvons alors effectuer une recherche comme une recherche de voisinage local, vous pouvez penser dans cet espace 3D autour de notre question pour dire : "Quels documents sont à proximité ?" Ces voisins proches sont alors récupérés car ils ont des sémantiques similaires par rapport à notre question, et c'est vraiment ce qui se passe ici.

Donc, encore une fois, nous avons pris nos documents, nous les avons divisés, nous les avons encodés, et maintenant ils existent dans cet espace de haute dimension. Nous avons pris notre question, nous l'avons encodée, projetée dans le même espace, et nous effectuons simplement une recherche autour de la question parmi les documents voisins et nous prenons ceux qui sont proches. Nous pouvons en choisir un certain nombre, nous pouvons dire que nous voulons un, deux, trois ou n documents proches de ma question dans cet espace d'embedding. Il existe de nombreuses méthodes très intéressantes qui implémentent cela très efficacement. J'en mets un lien ici, et nous avons de nombreuses intégrations agréables pour jouer avec cette idée générale : de nombreux modèles d'embedding différents, de nombreux index différents, de nombreux chargeurs de documents, et de nombreux diviseurs qui peuvent être combinés pour tester différentes façons de faire ce type d'indexation ou de récupération.

Je vais maintenant vous montrer un petit aperçu du code. Ici, nous avons défini, nous avons parcouru cela précédemment. C'est notre notebook, nous avons installé quelques packages, nous avons configuré quelques variables d'environnement en utilisant LangSmith, et nous l'avons montré précédemment. C'est juste un aperçu montrant comment exécuter RAG de manière globale, de bout en bout. Dans le dernier court exposé, nous avons abordé l'indexation. Ce que je vais faire très simplement, c'est que je vais recharger nos documents. J'ai maintenant nos documents, je vais les rediviser, et nous avons vu auparavant comment construire notre index. Maintenant, faisons la même chose, mais dans la diapositive, nous avons montré cette notion de recherche dans cet espace 3D. Un paramètre agréable à considérer lors de la construction de votre récupérateur est K. K indique le nombre de voisins proches à récupérer lors du processus de récupération. Et nous avons parlé, dans cet espace 3D, est-ce que je veux un voisin proche, ou deux, ou trois ? Ici, nous pouvons spécifier k=1, par exemple.

Maintenant, nous construisons notre index. Nous prenons chaque segment, nous l'encodons, nous le stockons. Maintenant, ce qui est bien, c'est que j'ai posé une question : "Qu'est-ce que la décomposition de tâches ?" C'est lié au billet de blog. Et je vais exécuter "get relevant documents". Je l'exécute, et maintenant combien de documents me reviennent ? J'en obtiens un, comme prévu, basé sur k=1. Ce document récupéré devrait être lié à ma question.

Je peux maintenant aller sur LangSmith et nous pouvons l'ouvrir, et nous pouvons regarder notre récupérateur. Nous pouvons voir ici notre question, voici le document que nous avons obtenu, et d'accord, cela a du sens. Ce document concerne la décomposition de tâches en particulier, et il présente un certain nombre d'approches qui peuvent être utilisées pour le faire. Tout cela a du sens, et cela montre en pratique comment vous pouvez implémenter cette recherche KNN ou k-nearest neighbor très facilement, juste en quelques lignes de code. Ensuite, nous parlerons de la génération.

Salut, c'est Lance de LangChain. C'est la quatrième courte vidéo de notre série RAG à partir de zéro, qui sera axée sur la génération. Dans les vidéos précédentes, nous avons parcouru le flux général pour le RAG de base, en commençant par l'indexation, suivie de la récupération, puis de la génération d'une réponse basée sur les documents que nous avons récupérés et qui sont pertinents pour notre question. C'est le flux de base.

Une considération importante dans la génération est ce qui se passe réellement : nous prenons les documents que vous récupérez et nous les insérons dans la fenêtre de contexte du LLM. Si nous retraçons le processus, nous prenons les documents, nous les divisons pour plus de commodité ou d'embedding, nous encodons ensuite chaque segment et nous le stockons dans une base de données vectorielle sous forme de représentation numérique ou de vecteur facilement recherchable. Nous prenons une question, nous l'encodons pour produire une représentation numérique similaire. Nous pouvons alors rechercher, par exemple, en utilisant quelque chose comme KNN dans cet espace dimensionnel, des documents similaires à notre question, en fonction de leur proximité ou de leur emplacement dans cet espace. Dans ce cas, vous pouvez voir que la 3D est un exemple jouet.

Maintenant, nous avons récupéré des segments pertinents pour notre question, nous les empaquetons dans la fenêtre de contexte et nous produisons notre réponse. Cela introduit la notion de prompt. Le prompt est en quelque sorte un espace réservé qui contient, par exemple, nos clés. Ces clés peuvent être comme le contexte et la question. Ce sont essentiellement des conteneurs dans lesquels nous allons insérer ces documents récupérés. Nous allons insérer notre question également. Et si vous suivez ce flux, vous pouvez voir que nous pouvons construire un dictionnaire à partir de nos documents récupérés et de notre question, puis nous pouvons simplement remplir notre modèle de prompt avec les valeurs du dictionnaire. Cela devient alors une valeur de prompt qui peut être transmise à un LLM comme un modèle de chat, résultant en des messages de chat que nous analysons ensuite en une chaîne et obtenons notre réponse. C'est donc le flux de base que nous allons voir.

Faisons un rapide tour de code pour vous donner une intuition pratique. Nous avions notre notebook, nous avons parcouru précédemment, installé quelques packages, je configure quelques variables d'environnement LangSmith, nous verrons que c'est agréable pour observer et déboguer nos traces. Précédemment, nous avons fait ce démarrage rapide, nous allons le sauter. Et ce que je vais faire, c'est que je vais construire notre récupérateur. Encore une fois, je vais prendre des documents et les charger, puis je vais les diviser. Nous avons fait cela précédemment, donc je vais passer rapidement, puis nous allons les encoder et les stocker dans notre index. Nous avons maintenant cet objet récupérateur ici.

Maintenant, je vais sauter ici. C'est là que ça devient amusant. C'est la partie génération. Et vous pouvez voir ici que je définis quelque chose de nouveau : c'est un modèle de prompt. Et ce que mon modèle de prompt est, c'est quelque chose de très simple : il va dire "Réponds à la question suivante en te basant sur ce contexte". Il aura cette variable de contexte et cette question.

Maintenant, je construis mon prompt. Super. Maintenant, j'ai ce prompt. Définissons un LLM. Je choisirai GPT-3.5. Cela introduit la notion de chaîne. Dans LangChain, nous avons un langage d'expression appelé LCEL (LangChain Expression Language) qui vous permet de composer très facilement des choses comme des prompts, des LLM, des parseurs, des récupérateurs et d'autres choses. Mais l'exemple très simple ici est juste de prendre notre prompt que vous avez défini ici et de le connecter à un LLM que vous avez défini ici dans cette chaîne.

Il y a donc notre chaîne. Maintenant, tout ce que nous faisons, c'est d'invoquer cette chaîne. Chaque chaîne LCEL a quelques méthodes communes comme `invoke`, `batch`, `stream`. Dans ce cas, nous l'invoquons simplement avec un dictionnaire : contexte et question, qui correspondent aux clés attendues ici dans notre modèle. Et si nous exécutons `invoke`, ce que nous voyons, c'est qu'elle va simplement exécuter cette chaîne et nous obtenons notre réponse.

Si nous zoomons sur LangSmith, nous devrions voir qu'il a été rempli. Oui, nous voyons une séquence exécutable très simple. C'était notre document, et voici notre sortie. Et voici notre prompt : "Réponds à la question suivante en te basant sur ce contexte". Voici le document que nous avons passé, voici la question, et nous obtenons notre réponse. C'est donc plutôt bien.

Il existe de nombreuses autres options pour les prompts RAG. Je vais en prendre une de notre "prompt hub". Celle-ci est un peu populaire, elle a un peu plus de détails, mais l'intuition principale est la même. Vous passez des documents, vous leur demandez de raisonner sur les documents, étant donné une question, de produire une réponse. Et maintenant, je vais définir une chaîne RAG qui fera automatiquement la récupération pour nous. Et tout ce que nous avons à faire est de spécifier : voici notre récupérateur, que nous avons défini auparavant. Voici notre question, que nous invoquons avec la question, qui est transmise à la clé "question" dans notre dictionnaire. Et cela déclenchera automatiquement le récupérateur, qui renverra des documents qui seront transmis à notre contexte. C'est exactement ce que nous avons fait ici, sauf qu'avant nous l'avons fait manuellement, et maintenant tout cela est automatisé pour nous. Nous passons ce dictionnaire, qui est automatiquement rempli, à notre prompt, LLM, puis au parseur.

Maintenant, invoquons-le, et tout cela devrait s'exécuter. Et super, nous obtenons une réponse. Et nous pouvons regarder le trace et voir tout ce qui s'est passé. Nous pouvons voir que notre récupérateur a été exécuté, ces documents ont été récupérés, ils sont transmis à notre LLM, et nous obtenons notre réponse finale. C'est donc la fin de notre aperçu, où nous avons parlé de l'indexation, de la récupération et maintenant de la génération. Dans les courtes vidéos suivantes, nous allons approfondir certains des thèmes plus complexes ou détaillés qui abordent certaines limitations qui peuvent survenir dans ce pipeline très simple. Merci.

Salut, je suis Lance de LangChain. Au cours des prochaines vidéos, nous allons parler de la traduction de requête. Et dans cette première vidéo, nous allons couvrir le sujet du multi-requête. La traduction de requête se situe au premier stade d'un pipeline RAG avancé, et l'objectif de la traduction de requête est vraiment de prendre une question d'utilisateur d'entrée et de la traduire d'une manière ou d'une autre afin d'améliorer la récupération.

Le problème est assez intuitif : les requêtes des utilisateurs peuvent être ambiguës, et si la requête est mal écrite, parce que nous effectuons généralement une sorte de recherche de similarité sémantique entre la requête et nos documents, si la requête est mal écrite ou mal formulée, nous ne récupérerons pas les bons documents de notre index. Il existe donc quelques approches pour résoudre ce problème, et vous pouvez les regrouper de différentes manières. Voici une façon dont j'aime y penser : certaines approches impliquent la réécriture de requête, c'est-à-dire prendre une requête et la reformuler, comme l'écrire d'une perspective différente. Et c'est ce dont nous allons parler un peu ici en profondeur, en utilisant des approches comme le multi-requête ou la fusion RAG, dont nous parlerons dans la prochaine vidéo.

Vous pouvez également faire des choses comme prendre une question et la décomposer pour la rendre moins abstraite, en sous-questions. Il existe un certain nombre d'articles intéressants axés sur cela, comme "Least to Most" de Google. Vous pouvez également adopter l'approche opposée : prendre une question pour la rendre plus abstraite. Il existe en fait une approche dont nous parlerons plus tard dans une future vidéo, appelée "step-back prompting", qui se concentre sur une question de niveau supérieur par rapport à l'entrée.

L'intuition derrière cette approche multi-requête est que nous prenons une question et nous la décomposons en quelques questions formulées différemment, de différentes perspectives. L'intuition ici est simplement qu'il est possible que la façon dont une question est initialement formulée, une fois encodée, ne soit pas bien alignée ou à proximité dans cet espace d'embedding de haute dimension avec un document que nous voulons récupérer et qui est réellement lié. L'idée est qu'en la réécrivant de quelques manières différentes, vous augmentez la probabilité de récupérer le document que vous voulez vraiment, en raison des nuances dans la façon dont les documents et les questions sont encodés. Cette approche plus "shotgun", consistant à prendre une question et à la diffuser sous quelques perspectives différentes, peut améliorer et augmenter la fiabilité de la récupération. C'est vraiment l'intuition.

Et bien sûr, nous pouvons combiner cela avec la récupération. Nous pouvons prendre nos questions "diffusées", effectuer une récupération sur chacune d'elles et les combiner d'une manière ou d'une autre, puis effectuer RAG. C'est donc l'aperçu général.

Maintenant, allons à notre code. C'est un notebook, et nous allons partager tout cela. Nous installons simplement quelques packages, nous configurons des clés API LangSmith, ce que nous verrons pourquoi c'est assez utile ici bientôt. Il y a notre diagramme. D'abord, je vais indexer ce billet de blog sur les agents. Je vais le diviser, enfin, je vais le charger, je vais le diviser, puis je vais l'indexer localement dans Chroma. C'est une base de données vectorielle, nous l'avons fait précédemment. J'ai donc maintenant mon index défini.

Voici où je définis mon prompt pour le multi-requête, qui est : "Votre assistant, votre tâche est essentiellement de reformuler cette question en quelques sous-questions." Il y a donc notre prompt juste ici. Nous le passerons à un LLM, le parserons en une chaîne, puis diviserons la chaîne par des sauts de ligne. Nous obtiendrons donc une liste de questions à partir de cette chaîne, c'est tout ce que nous faisons ici.

Maintenant, tout ce que nous faisons ici, c'est : voici une question d'exemple. Il y a notre chaîne "generate queries" que nous avons définie. Nous allons prendre cette liste, puis simplement appliquer chaque question au récupérateur. Nous ferons donc une récupération par question, et cette petite fonction va simplement prendre l'union unique des documents à travers toutes ces récupérations. Exécutons cela et voyons ce qui se passe. Nous allons exécuter cela et nous obtiendrons un ensemble de questions ou de documents.

Allons sur LangSmith maintenant. Nous pouvons réellement voir ce qui s'est passé sous le capot. Voici le point clé : nous avons exécuté notre chaîne initiale pour générer un ensemble de questions reformulées à partir de notre entrée, et voici ce prompt, et voici cet ensemble de questions que nous avons générées. Ce qui s'est passé, c'est que pour chacune de ces questions, nous avons effectué une récupération indépendante, c'est ce que nous montrons ici. C'est donc la première étape, ce qui est excellent.

Je peux maintenant revenir au notebook et nous pouvons montrer que cela fonctionne de bout en bout. Nous allons maintenant prendre cette chaîne de récupération, la passer dans le contexte de notre prompt RAG final, nous allons également passer la question, la passer à notre prompt RAG, la passer à un LLM, puis parser la sortie. Exécutons cela et voyons comment cela fonctionne. Encore une fois, c'est bon, il est là.

Alors, allons dans LangSmith et voyons ce qui s'est passé sous le capot. C'était notre chaîne finale. C'est excellent. Nous avons pris notre question d'entrée, nous l'avons décomposée en ces cinq questions reformulées. Pour chacune d'elles, nous avons effectué une récupération, c'est tout très bien. Nous avons ensuite pris l'union unique des documents, et vous pouvez voir dans notre prompt final "Réponds à la question suivante en te basant sur le contexte", c'est l'ensemble final de documents uniques que nous avons récupérés de toutes nos sous-questions. Voici notre question initiale, voici notre réponse. Cela vous montre donc comment vous pouvez configurer cela très facilement, comment vous pouvez utiliser LangSmith pour enquêter sur ce qui se passe, et en particulier utiliser LangSmith pour enquêter sur ces questions intermédiaires que vous générez dans cette phase de génération de questions. Et dans les futures présentations, nous aborderons certaines de ces autres méthodes que nous avons introduites au début de celle-ci. Merci.

Salut, je suis Lance de LangChain. C'est la deuxième vidéo de notre plongée approfondie sur la traduction de requête dans notre série RAG à partir de zéro, axée sur une méthode appelée RAG Fusion. Comme nous l'avons montré précédemment, la traduction de requête peut être considérée comme la première étape d'un pipeline RAG avancé. Nous prenons une question d'utilisateur d'entrée et nous la traduisons d'une manière ou d'une autre afin d'améliorer la récupération.

Nous avons montré ce mappage général des approches précédemment. Encore une fois, vous avez des réécritures, vous pouvez prendre une question et la décomposer en différentes formulations ou perspectives différentes de la même question. C'est donc une réécriture. Il y a des sous-questions où vous prenez une question, vous la décomposez en problèmes plus petits, vous résolvez chacun indépendamment. Et puis il y a le "step back" où vous prenez une question et allez plus abstrait, où vous posez une question de niveau supérieur comme précondition pour répondre à la question de l'utilisateur. Ce sont donc les approches, et nous allons approfondir l'une des approches particulières de réécriture appelée RAG Fusion.

C'est très similaire à ce que nous venons de voir avec le multi-requête. La différence est que nous appliquons une étape de classement astucieuse de nos documents récupérés, que nous appelons Reciprocal Rank Fusion. C'est vraiment la seule différence. L'étape d'entrée consistant à prendre une question, à la décomposer en quelques questions formulées différemment, la récupération sur chacune d'elles est la même, et nous allons le voir dans le code ici bientôt.

Passons donc au code et regardons cela. Encore une fois, voici un notebook que nous avons introduit précédemment. Voici les packages que nous avons installés. Nous avons configuré quelques clés API pour LangSmith, dont nous voyons pourquoi elles sont très utiles. Et vous pouvez descendre ici dans une section RAG Fusion. La première chose que vous remarquerez est notre prompt. Il ressemble beaucoup au prompt que nous venons de voir avec le multi-requête, et simplement : "Votre assistant utile qui génère plusieurs requêtes de recherche basées sur l'entrée de l'utilisateur." Et voici la sortie des requêtes pour la question.

Définissons notre prompt. Et voici notre chaîne de génération de requêtes. Encore une fois, cela ressemble beaucoup à ce que nous venons de voir. Nous prenons notre prompt, nous le transmettons à un LLM, puis nous le parsons par sauts de ligne, et cela va essentiellement diviser ces questions en une liste. C'est tout ce qui va se passer ici. C'est donc cool.

C'est là que réside la nouveauté. Chaque fois que nous effectuons une récupération à partir de l'une de ces questions, nous obtenons une liste de documents de notre récupérateur. Nous le faisons donc sur les quatre questions générées par notre prompt. Eh bien, comme une liste de listes, essentiellement.

Reciprocal Rank Fusion est très bien adapté à ce problème exact. Nous voulons prendre cette liste de listes et construire une seule liste consolidée. Et tout ce qui se passe, c'est qu'elle examine les documents dans chaque liste et les agrège en un classement de sortie final. Et c'est vraiment l'intuition derrière ce qui se passe ici.

Alors, allons-y et regardons cela en détail. Nous pouvons voir que nous exécutons la récupération, c'est excellent. Allons maintenant sur LangSmith et regardons ce qui se passe ici. Nous pouvons voir que voici notre prompt : "Votre assistant utile qui génère plusieurs requêtes de recherche basées sur une seule entrée." Et voici nos requêtes de recherche. Et puis voici nos quatre récupérations. C'est donc très bien. Nous savons que tout fonctionne.

Et puis ces récupérations sont simplement allées dans cette fonction de classement, et elles ont été classées en une liste finale de six documents classés uniques. C'est vraiment tout ce que nous avons fait. Mettons donc tout cela ensemble dans une chaîne RAG complète qui va exécuter la récupération, renvoyer cette liste finale de documents classés et la transmettre à notre contexte, transmettre notre question, l'envoyer à un prompt RAG, la transmettre à un LLM, la parser en une sortie. Exécutons tout cela ensemble et voyons cela fonctionner.

Cool, voici notre réponse finale. Regardons maintenant dans LangSmith. Nous pouvons voir que voici nos quatre questions, voici nos récupérations, puis notre prompt RAG final, alimenté par la liste finale de six questions classées, que nous pouvons voir ici, et notre réponse finale. Cela peut être très pratique, en particulier si nous opérons sur différentes bases de données vectorielles, ou si nous voulons effectuer une récupération sur un grand nombre de questions formulées différemment. Cette étape de Reciprocal Rank Fusion est vraiment agréable pour construire ce classement consolidé sur toutes ces récupérations indépendantes, puis la transmettre pour la génération finale. C'est donc vraiment l'intuition derrière ce qui se passe ici. Merci.

Salut, je suis Lance de LangChain. C'est notre troisième vidéo axée sur la traduction de requête dans la série RAG à partir de zéro, et nous allons parler de décomposition. La traduction de requête en général est un ensemble d'approches qui se situent à l'avant de ce pipeline RAG global, et l'objectif est de modifier, de réécrire ou de décomposer autrement une question d'entrée d'un utilisateur afin d'améliorer la récupération.

Nous avons pu aborder certaines de ces approches précédemment, en particulier diverses façons de faire de la réécriture de requête comme RAG Fusion et Multi-Query. Il existe un ensemble distinct de techniques qui deviennent très populaires et vraiment intéressantes pour certains problèmes, que nous pourrions appeler "décomposition" ou "décomposition d'une question d'entrée en un ensemble de sous-questions". Certaines des publications ici qui sont assez cool sont, par exemple, ce travail de Google. Et l'objectif est vraiment de prendre une question d'entrée et de la décomposer en un ensemble de sous-problèmes. Cet exemple particulier de l'article était le problème de la concaténation de la dernière lettre. Il a donc pris la question d'entrée de trois mots "think machine learning" et l'a décomposée en trois sous-problèmes : "think", "think machine", "think machine learning" comme troisième sous-problème. Et vous pouvez voir dans le panneau inférieur qu'il résout chacun d'eux individuellement. Il montre, par exemple, en vert, la résolution du problème "think machine", où vous concaténez la dernière lettre de "k" avec la dernière lettre de "machine", ou la dernière lettre de "think" "k" et "machine" "e", concaténez-les pour obtenir "ke". Puis, pour le problème global, en prenant cette solution et en construisant dessus pour obtenir la solution globale de "keg". C'est donc un concept de décomposition en sous-problèmes, les résolvant séquentiellement.

Un travail connexe appelé IRC, ou In-Context Retrieval, combine la récupération avec le raisonnement Chain-of-Thought. Vous pouvez donc combiner ces éléments en une seule approche que vous pouvez considérer comme une récupération dynamique pour résoudre un ensemble de sous-problèmes, cette récupération s'entremêlant avec Chain-of-Thought, comme indiqué dans le deuxième article, et un ensemble de questions décomposées basées sur votre question initiale du premier travail de Google. L'idée est donc que nous prenons une sous-question, nous y répondons, nous utilisons cette réponse pour aider à répondre à la deuxième sous-question, et ainsi de suite.

Alors, parcourons cela en code pour montrer comment cela pourrait fonctionner. C'est le notebook que nous avons utilisé dans certaines des autres vidéos. Vous pouvez voir que nous avons déjà un récupérateur défini ici en haut. Et ce que nous allons faire, c'est d'abord définir un prompt qui va essentiellement dire : "Étant donné une question d'entrée, décomposons-la en un ensemble de sous-problèmes ou de sous-questions qui peuvent être résolus individuellement." Nous pouvons le faire. Et ce billet de blog est axé sur les agents. Posons donc une question sur les principaux composants d'un système d'agent autonome alimenté par LLM. Exécutons cela et voyons quelles sont les questions décomposées.

Vous pouvez voir que les questions décomposées sont : "Qu'est-ce que la technologie LLM ?", "Comment fonctionne-t-elle ?", "Quels sont les composants ?" et "Comment les composants interagissent-ils ?". C'est une façon sensée de décomposer ce problème en quelques sous-problèmes que vous pourriez aborder individuellement.

Voici où nous définissons un prompt qui va très simplement prendre notre question, nous prendrons toutes les questions précédentes auxquelles nous avons répondu, et nous prendrons notre récupération, et nous les combinerons simplement. Et nous pouvons définir cette chaîne très simple. En fait, revenons en arrière et assurons-nous que le récupérateur est défini en haut. Nous construisons donc notre récupérateur. Bien, nous l'avons. Revenons donc ici et exécutons cela. Nous exécutons donc, et ce qui se passe, c'est que nous essayons de résoudre chacune de ces questions individuellement en utilisant la récupération et en utilisant toutes les réponses aux questions précédentes.

D'accord, très bien. Il semble que cela ait été fait. Et nous pouvons voir ici notre réponse. Allons maintenant sur LangSmith et voyons réellement ce qui s'est passé sous le capot. Voici ce qui est intéressant et utile à voir pour la première question. Voici notre première question. Il semble qu'elle effectue simplement une récupération, ce à quoi nous nous attendons, puis elle utilise cela pour répondre à cette question initiale. Maintenant, pour la deuxième question, cela devrait être un peu plus intéressant, car si vous regardez notre prompt ici, voici notre question. Maintenant, voici notre paire de questions/réponses disponibles en arrière-plan. C'était la paire réponse/question de la première question, que nous ajoutons à notre prompt. Et puis voici la récupération pour cette question particulière. Nous construisons donc la solution car nous ajoutons la paire question/réponse de la question un. Et de même avec la question trois, cela devrait combiner tout cela.

Nous pouvons donc regarder ici. Voici notre question. Voici la question un. Voici la question deux. Super. Maintenant, voici une récupération supplémentaire liée à cette question particulière, et nous obtenons notre réponse finale. C'est donc une excellente façon de construire des solutions en utilisant ce type de récupération entrelacée et de concaténation de paires question/réponse.

Je tiens à mentionner très brièvement que nous pouvons également adopter une approche différente, où nous pouvons simplement répondre à toutes individuellement, puis simplement concaténer toutes ces réponses pour produire une réponse finale. Je vais le montrer très rapidement ici. C'est un peu moins intéressant, peut-être, car vous n'utilisez pas les réponses de chaque question pour informer la suivante. Vous y répondez simplement toutes en parallèle. Cela pourrait être mieux pour les cas où ce n'est pas vraiment une décomposition en sous-questions, mais peut-être un ensemble de plusieurs questions indépendantes dont les réponses ne dépendent pas les unes des autres. Cela pourrait être pertinent pour certains problèmes. Et nous pouvons exécuter.

D'accord, cela a également fonctionné. Nous pouvons regarder notre trace, et dans ce cas, oui, nous pouvons voir que cela concatène simplement toutes nos paires QA pour produire la réponse finale. Cela vous donne donc une idée de la façon dont vous pouvez utiliser la décomposition de requêtes, en employant des idées de deux articles différents qui sont assez cool. Merci.

Salut, je suis Lance de LangChain. C'est la quatrième vidéo de notre plongée approfondie sur la traduction de requête dans la série RAG à partir de zéro, et nous allons nous concentrer sur le "step-back prompting". La traduction de requête, comme nous l'avons dit dans certaines des vidéos précédentes, se situe à la première étape d'un pipeline ou d'un flux RAG. Et l'objectif principal est de prendre une question et de la traduire ou de la modifier de manière à améliorer la récupération.

Nous avons abordé quelques façons différentes d'aborder ce problème. Une approche générale implique la réécriture d'une question, et nous avons parlé de deux façons de le faire : RAG Fusion et Multi-Query. Encore une fois, il s'agit vraiment de prendre une question et de la modifier pour capturer quelques perspectives différentes, ce qui peut améliorer le processus de récupération.

Une autre approche consiste à prendre une question et à la rendre moins abstraite, comme la décomposer en sous-questions, puis à résoudre chacune d'elles indépendamment. C'est ce que nous avons vu avec le "Least to Most prompting" et un certain nombre d'autres variantes dans cette veine de résolution de sous-problèmes et de consolidation de ces solutions en une réponse finale.

Une approche différente présentée, également par Google, est le "step-back prompting". Le "step-back prompting" adopte l'approche opposée, où il essaie de poser une question plus abstraite. L'article parle beaucoup de l'utilisation du "few-shot prompting" pour produire ce qu'ils appellent les questions "step-back" ou plus abstraites. Et la façon dont il le fait est qu'il fournit un certain nombre d'exemples de questions "step-back" étant donné votre question d'origine. Par exemple, c'est comme pour le prompt template : "Vous êtes un expert en connaissances mondiales. Je vous pose une question. Votre réponse doit être complète et ne pas contredire ce qui suit." Et c'est là que vous fournissez votre question d'origine, puis le "step-back". Voici quelques exemples de questions. Par exemple, "À quelle année a eu lieu la création de la région où se trouve le comté ? De quelle région du comté le comté d'Hertfordshire est-il lié ?" "Janell est née dans quel pays ? Quelle est l'histoire personnelle de Janell ?" C'est peut-être un exemple plus intuitif. Il s'agit de poser une question très spécifique sur le pays où quelqu'un est né, la question plus abstraite est simplement de me donner l'histoire générale de cette personne sans se soucier de cette question spécifique.

Alors, parcourons comment cela peut être fait en pratique. Encore une fois, voici un diagramme des différentes approches, de moins d'abstraction à plus d'abstraction. Voici où nous formulons notre prompt en utilisant quelques exemples "few-shot" de l'article. Donc, par exemple, "Entrée : quelque chose sur la police qui commet des arrestations illégales et que font les membres de la police ?" Donc, cela donne essentiellement au modèle quelques exemples. Nous formulons cela en un prompt, c'est tout ce qui se passe ici. Encore une fois, nous répétons ce prompt global que nous avons vu dans l'article : "Vous êtes un expert en connaissances mondiales. Votre tâche est de prendre du recul et de paraphraser une question, de générer une question "step-back" plus générique, plus facile à répondre. Voici quelques exemples." C'est donc un prompt très intuitif.

Alors, commençons par la question : "Qu'est-ce que la composition de tâches pour les agents LLM ?" Et nous allons dire "générer une question step-back". C'est assez intuitif, n'est-ce pas ? "Quel est le processus de composition de tâches ?" Donc, sans se soucier autant des agents, mais qu'est-ce que ce processus de composition de tâches en général ? Et ensuite, j'espère que cela pourra être récupéré indépendamment. Nous pouvons récupérer indépendamment des documents liés à la question "step-back", et en plus, récupérer des documents liés à la question réelle, et combiner les deux pour produire une réponse finale. C'est donc vraiment tout ce qui se passe.

Et voici le modèle de réponse où nous intégrons le contexte "step-back" et le contexte de la question. Nous allons donc prendre notre question d'entrée et effectuer une récupération sur celle-ci. Nous allons également générer notre question "step-back" et effectuer une récupération sur celle-ci. Nous allons intégrer cela dans le prompt comme ceci : voici nos clés de prompt normales : question, question "step-back", et notre question globale. Encore une fois, nous les formulons comme un dictionnaire. Nous les intégrons dans notre prompt de réponse, puis nous tentons de répondre à notre question globale.

Nous allons exécuter cela. Cela s'exécute, et d'accord, nous avons notre réponse. Je veux maintenant aller sur LangSmith et essayer de vous montrer ce que cela ressemblait sous le capot. Voyons, allons dans chacune de ces étapes. Voici notre prompt : "Vous êtes un expert en connaissances mondiales. Votre tâche est de prendre du recul et de paraphraser une question." Donc, voici nos prompts "few-shot", et c'était notre question "step-back". "Quel est le processus de composition des tâches ?" Bien, à partir de l'entrée "Qu'est-ce que la composition des tâches pour les agents LLM ?" Nous effectuons une récupération sur les deux : "Quel est le processus de composition ?" et "Qu'est-ce que pour les agents LLM ?" Nous effectuons les deux récupérations. Nous remplissons ensuite notre prompt avec la réponse d'origine et la question, puis voici le contexte récupéré de la question et de la question "step-back". Voici notre réponse finale.

Encore une fois, c'est une technique agréable. Cela dépend probablement beaucoup du type de domaine sur lequel vous souhaitez effectuer une récupération. Mais dans certains domaines où, par exemple, il y a beaucoup de connaissances conceptuelles qui sous-tendent les questions que vous attendez des utilisateurs, cette approche "step-back" pourrait être très pratique pour formuler automatiquement une question de niveau supérieur afin, par exemple, d'améliorer la récupération. J'imagine que si vous travaillez avec des manuels ou de la documentation technique, où vous avez des chapitres indépendants axés sur des concepts de niveau supérieur, puis d'autres chapitres sur des implémentations plus détaillées, ce type d'approche "step-back" et de récupération indépendante pourrait être très utile. Merci.

Salut, je suis Lance de LangChain. C'est la cinquième vidéo axée sur la traduction de requête dans notre série RAG à partir de zéro, et nous allons parler d'une technique appelée HyDE. Encore une fois, la traduction de requête se situe à l'avant du flux RAG global, et l'objectif est de prendre une question d'entrée et de la traduire d'une manière qui améliore la récupération.

HyDE est une approche intéressante qui tire parti d'une idée très simple. Le flux RAG de base prend une question et l'encode, prend un document et l'encode, et recherche la similarité entre un document encodé et une question encodée. Mais les questions et les documents sont des objets texte très différents. Les documents peuvent être de très grands extraits provenant de publications denses ou d'autres sources, tandis que les questions sont courtes, potentiellement mal formulées par les utilisateurs. Et l'intuition derrière HyDE est de prendre les questions et de les mapper dans l'espace des documents en utilisant

un document hypothétique ou en générant un document hypothétique, euh, c'est l'intuition de base et l'idée un peu montrée ici visuellement est qu'en principe, pour certains cas, un document hypothétique est plus proche d'un document souhaité que vous voulez réellement récupérer dans cet espace d'intégration de haute dimension que la question brute éparse elle-même. Donc, encore une fois, c'est juste une sorte de moyen de traduire les questions brutes en ces documents hypothétiques qui sont mieux adaptés à la récupération. Alors, faisons une analyse de code pour voir comment cela fonctionne et c'est en fait assez facile à implémenter, ce qui est vraiment bien. Donc, d'abord, nous commençons juste avec un prompt et nous utilisons le même notebook que nous avons utilisé pour les vidéos précédentes. Nous avons un article de blog sur les agents r index, euh, donc ce que nous allons faire, c'est définir un prompt pour générer des documents hypothétiques. Dans ce cas, nous dirons "écrivez un passage de papier pour répondre à une question donnée". Alors, exécutons cela et voyons ce qui se passe. Encore une fois, nous prenons notre prompt, nous le transmettons à OpenAI, ChatGPT, puis nous utilisons String Opa Parser. Et voici une section de document hypothétique liée à notre question. Et cela est dérivé, bien sûr, de la connaissance du monde intégrée des LLM, ce qui est un endroit sain pour générer des documents hypothétiques. Maintenant, prenons ce document hypothétique et, fondamentalement, nous allons le transmettre à un récupérateur. Cela signifie que nous allons récupérer des documents de notre index liés à ce document hypothétique qui a été intégré. Et vous pouvez voir que nous obtenons quelques fragments récupérés qui sont liés à ce document hypothétique. C'est tout ce que nous avons fait. Et ensuite, faisons la dernière étape où nous prenons ces documents récupérés ici, que nous avons définis, et notre question, nous allons les transmettre à ce prompt RAG, puis nous allons exécuter notre chaîne RAG ici, que vous avez déjà vue, et nous obtenons notre réponse. C'est donc vraiment tout. Nous pouvons aller sur LangSmith et nous pouvons réellement regarder ce qui s'est passé. Donc, ici, par exemple, c'était notre prompt RAG final : "Répondez à la question suivante en vous basant sur ce contexte", et voici les documents récupérés que nous avons transmis. Donc, cette partie est assez simple. Nous pouvons également regarder. D'accord, c'est notre récupération. Maintenant, c'est en fait ce que nous avons généré un document hypothétique ici. D'accord, c'est notre document hypothétique. Nous avons donc exécuté ChatGPT d'OpenAI, nous avons généré ce passage avec notre document hypothétique, puis nous avons exécuté la récupération ici. Cela montre donc essentiellement la génération de documents hypothétiques suivie de la récupération. Donc, encore une fois, voici notre passage que nous avons transmis, et voici nos documents récupérés du récupérateur qui sont liés au contenu du passage. Donc, encore une fois, dans ce cas d'index particulier, il est possible que la question d'entrée ait été suffisante pour récupérer ces documents. En fait, compte tenu des exemples précédents, je sais que certains de ces mêmes documents sont effectivement récupérés à partir de la question brute, mais dans d'autres contextes, cela peut ne pas être le cas. Donc, les gens ont signalé de bonnes performances en utilisant Hyde pour certains domaines, et la chose vraiment pratique est que vous pouvez prendre ce prompt de génération de documents, vous pouvez l'ajuster arbitrairement pour votre domaine d'intérêt. Il vaut donc absolument la peine d'expérimenter. C'est une approche intéressante qui peut surmonter certains des défis de la récupération. Merci beaucoup. Salut, c'est Lance de LangChain. C'est la 10ème vidéo de notre série RAG from Scratch axée sur le routage. Nous avons parlé de la traduction de requêtes, qui est le processus de prise d'une question et de sa traduction d'une manière ou d'une autre. Cela pourrait être sa décomposition en utilisant le "stepback prompting" ou autrement, mais l'idée ici était de prendre notre question et de la transformer en une forme mieux adaptée à la récupération. Maintenant, le routage est la prochaine étape, qui consiste essentiellement à router cette question potentiellement décomposée vers la bonne source. Et dans de nombreux cas, cela pourrait être une base de données différente. Alors, disons que dans cet exemple jouet, nous avons un "vector store", une base de données relationnelle et une base de données graphique. Ce que nous refaisons avec le routage, c'est simplement router la question en fonction du contenu de la question vers la source de données pertinente. Il y a donc quelques façons différentes de le faire. L'une est ce que nous appelons le routage logique. Dans ce cas, nous donnons essentiellement à un LLM la connaissance des diverses sources de données dont nous disposons et nous laissons le LLM raisonner sur celle qu'il doit appliquer à la question. C'est donc un peu comme si le LLM appliquait une certaine logique pour déterminer, par exemple, quelle source de données utiliser. Alternativement, vous pouvez utiliser le routage sémantique, où nous prenons une question, nous l'intégrons, et par exemple, nous intégrons des prompts. Nous calculons ensuite la similarité entre notre question et ces prompts, puis nous choisissons un prompt en fonction de la similarité. L'idée générale est que dans notre diagramme, nous parlons de router vers, par exemple, une base de données différente, mais cela peut être très général. Cela peut être un routage vers un prompt différent. Cela peut être, vous savez, vraiment prendre arbitrairement cette question et l'envoyer à différents endroits, que ce soit à différents prompts, que ce soit à différents "vector stores". Alors, parcourons un peu le code. Vous pouvez voir, comme avant, que nous avons fait quelques installations pip, nous avons configuré LangSmith et parlons d'abord du routage logique. Donc, dans cet exemple jouet, disons que nous avions, par exemple, trois documents différents, comme nous avions des documents Python, des documents JS, des documents Go. Ce que nous voulons faire, c'est prendre une question et la router vers l'un de ces trois. Alors, ce que nous faisons réellement, c'est que nous configurons un modèle de données qui sera essentiellement lié à notre LLM et permettra au LLM de produire l'une de ces trois options comme objet structuré. Vous pensez donc vraiment à cela comme à une classification, une classification plus un appel de fonction pour produire une sortie structurée qui est contrainte à ces trois possibilités. La façon dont nous le faisons, c'est que nous allons juste zoomer un peu ici. Nous pouvons définir comme un objet structuré que nous voulons obtenir de notre LLM, comme dans ce cas, nous voulons, par exemple, l'une de ces trois sources de données à produire. Nous pouvons prendre cela et nous pouvons en fait le convertir en schéma de fonction ouvert, par exemple, et ensuite nous le passons et le lions à notre LLM. Alors, ce qui se passe, c'est que nous posons une question, notre LLM invoque cette fonction sur la sortie pour produire une sortie qui adhère au schéma que nous spécifions. Donc, dans ce cas, par exemple, nous produisons comme, vous savez, dans cet exemple jouet, disons que nous voulions une sortie qui soit une source de données, un "vector store" ou une base de données SQL. La sortie contiendra un objet "data source" et ce sera, vous savez, l'une des options que nous spécifions sous forme de chaîne JSON. Nous instancions également un analyseur à partir de cet objet pour analyser cette chaîne JSON en une sortie comme un objet Pydantic, par exemple. C'est donc juste un exemple jouet, et montrons-en un ici. Donc, dans ce cas, encore une fois, nous avions nos trois sources de documents. Nous le lions à notre LLM. Vous pouvez donc voir que nous faisons "structured output" essentiellement sous le capot, ce qui prend cette définition d'objet, la transforme en schéma de fonction et lie ce schéma de fonction à notre LLM. Et nous appelons notre prompt : "Vous êtes un expert en routage d'une question d'utilisateur basée sur, vous savez, le langage de programmation auquel l'utilisateur fait référence." Alors, définissons notre routeur ici. Maintenant, ce que nous allons faire, c'est que nous allons poser une question qui est du code Python. Nous allons donc l'appeler, et maintenant c'est fait, et vous voyez que l'objet que nous obtenons est bien, c'est un objet "route query". Il adhère donc exactement à ce modèle de données que nous avons mis en place, et dans ce cas, c'est correct. Il appelle donc ce document Python. Vous pouvez donc l'extraire ici sous forme de chaîne. Maintenant, une fois que nous avons cela, vous pouvez facilement configurer comme une route. Cela pourrait donc être notre chaîne complète où nous prenons ce routeur que nous avons défini ici, puis cette fonction "choose route" peut essentiellement prendre cette sortie et en faire quelque chose. Par exemple, si "python docs", cela pourrait alors appliquer la question à un récupérateur plein d'informations Python, ou JS, même chose. C'est donc là que vous connecteriez essentiellement cette question à différentes chaînes qui sont comme, vous savez, une chaîne de récupérateur, une pour Python, une chaîne de récupérateur deux pour JS, et ainsi de suite. C'est donc un peu comme le mécanisme de routage, mais cela fait vraiment le gros du travail de prendre une question d'entrée et de la transformer en un objet structuré qui restreint la sortie à l'un des quelques types de sortie qui nous intéressent dans notre problème de routage. C'est donc vraiment la façon dont tout cela se connecte. Maintenant, le routage sémantique est en fait peut-être encore un peu plus simple, basé sur ce que nous avons vu précédemment. Dans ce cas, disons que nous avons deux prompts. Nous avons un prompt de physique et un prompt de mathématiques. Nous pouvons intégrer ces prompts, pas de problème. Nous le faisons ici. Maintenant, disons que nous avons une question d'entrée d'un utilisateur, comme dans ce cas, "Qu'est-ce qu'un trou noir ?" Nous la transmettons. Nous appliquons ensuite cette fonction Lambda exécutable qui est définie ici. Ce que nous faisons ici, c'est que nous intégrons la question, nous calculons la similarité entre la question et les prompts, nous prenons le plus similaire, puis nous choisissons essentiellement le prompt en fonction de cette similarité. Et vous pouvez voir, exécutons cela et essayons. Et nous utilisons le prompt de physique. Et voilà, les trous noirs, la région de l'espace. Cela vous montre donc simplement comment vous pouvez utiliser le routage sémantique pour intégrer une question, intégrer par exemple divers prompts, choisir le prompt en fonction de la similarité sémantique. Cela vous donne donc vraiment deux façons de faire du routage. L'une est le routage logique avec appel de fonction, qui peut être utilisé de manière très générale. Dans ce cas, nous l'avons appliqué à différents langages de codage, mais imaginez qu'ils pourraient être remplacés par, vous savez, mon Python, mon "vector store" par rapport à ma base de données graphique par rapport à ma base de données relationnelle. Et vous pourriez simplement avoir une description de ce que chacun est, et vous savez, alors non seulement le LLM fera du raisonnement, mais il retournera également un objet qui peut être analysé très proprement pour produire l'un des quelques types très spécifiques, sur lesquels vous pouvez ensuite raisonner, comme nous l'avons fait ici dans votre fonction de routage. Cela vous donne donc l'idée générale, et ce sont vraiment des outils très utiles, et je vous encourage à expérimenter avec eux. Merci. Salut, c'est Lance de LangChain. C'est la 11ème partie de notre série RAG from Scratch axée sur la construction de requêtes. Nous avons précédemment parlé de la traduction de requêtes, qui est le processus de prise d'une question et de sa conversion ou traduction en une question mieux optimisée pour la récupération. Ensuite, nous avons parlé du routage, qui est le processus de prise de cette question et de son acheminement vers la bonne source, qu'il s'agisse d'un "vector store" donné, d'une base de données graphique ou d'une base de données SQL, par exemple. Maintenant, nous allons parler du processus de construction de requêtes, qui consiste essentiellement à prendre du langage naturel et à le convertir en un langage spécifique au domaine pour l'une de ces sources. Maintenant, nous allons parler spécifiquement du processus de passage du langage naturel aux filtres de métadonnées pour les "vector stores". Le problème est essentiellement le suivant : imaginons que nous ayons un index de transcriptions de vidéos LangChain. Vous pourriez vouloir poser une question comme : "Donnez-moi, euh, ou trouvez-moi des vidéos sur Chat LangChain publiées après 2024", par exemple. Le processus de structuration des requêtes convertit essentiellement cette question en langage naturel en une requête structurée qui peut être appliquée aux filtres de métadonnées de votre "vector store". La plupart des "vector stores" auront une sorte de filtres de métadonnées qui peuvent effectuer des requêtes structurées sur les fragments indexés. Par exemple, ce type de requête récupérera tous les fragments qui parlent du sujet de Chat LangChain, publiés après la date 2024. C'est donc le problème. Et pour ce faire, nous allons utiliser l'appel de fonction. Dans ce cas, vous pouvez utiliser, par exemple, OpenAI ou d'autres fournisseurs pour le faire. Et ce que nous allons faire, à un niveau élevé, c'est prendre les champs de métadonnées présents dans notre "vector store" et les fournir au modèle comme une sorte d'information. Et le modèle peut ensuite les prendre et produire des requêtes qui adhèrent au schéma fourni. Et ensuite, nous pouvons les analyser en un objet structuré comme un objet Pydantic, qui peut ensuite être utilisé dans la recherche. C'est donc le problème. Et parcourons le code. Voici notre notebook, que nous avons déjà parcouru. Et je vais vous montrer un exemple. Prenons une vidéo YouTube d'exemple et regardons les métadonnées que vous obtenez avec la transcription. Vous pouvez donc voir que vous obtenez des choses comme la description, l'URL, la date de publication, la durée, etc. Maintenant, disons que nous avions un index qui avait un certain nombre de champs de métadonnées et de filtres qui nous permettaient de faire du filtrage par plage sur le nombre de vues, la date de publication, la durée de la vidéo, ou une recherche non structurée sur le contenu et le titre. Ce sont donc un peu comme, imaginez que nous avions un index qui avait ces types de filtres disponibles. Ce que nous pouvons faire, c'est capturer ces informations sur les filtres disponibles dans un objet. Nous appelons cela "tutorial search object". Il encapsule essentiellement ces informations sur les recherches disponibles que nous pouvons effectuer. Et nous l'énumérons donc ici : recherche de contenu et recherche de titre, ou recherches sémantiques qui peuvent être effectuées sur ces champs. Et ensuite, ces filtres sont différents types de recherches structurées que nous pouvons effectuer sur, par exemple, la durée, le nombre de vues, et ainsi de suite. Et nous pouvons donc simplement construire cet objet. Maintenant, nous pouvons le configurer très facilement avec un prompt simple qui dit : "Vous êtes un expert capable de transformer le langage naturel en requêtes de base de données. Vous avez accès aux vidéos tutoriels de la base de données. Étant donné une question, retournez une requête de base de données pour optimiser la récupération." C'est donc à peu près tout. Maintenant, voici le point clé : lorsque vous appelez ce LLM avec une sortie structurée, vous liez cet objet Pydantic, qui contient toutes les informations sur notre index, au LLM. C'est exactement ce dont nous avons parlé précédemment. C'est vraiment ce processus ici : vous prenez cet objet, vous le convertissez en un schéma de fonction, par exemple OpenAI, vous le liez à votre modèle, et ensuite vous pourrez obtenir un objet structuré au lieu d'une chaîne JSON à partir d'une question en langage naturel, qui peut ensuite être analysée en un objet Pydantic que vous obtenez. C'est donc vraiment le flux, et il tire parti de l'appel de fonction, comme nous l'avons dit. Si nous revenons en arrière, nous configurons notre chaîne d'analyse de requêtes ici. Essayons maintenant de l'exécuter sur une entrée purement sémantique. "RAG from scratch". Exécutons cela, et vous pouvez voir que cela fait juste une recherche de contenu et une recherche de titre. C'est exactement ce à quoi vous vous attendriez. Maintenant, si nous transmettons une question qui inclut un filtre de date, voyons si cela fonctionne. Et voilà. Vous obtenez donc toujours la recherche sémantique, mais vous obtenez également une recherche sur, par exemple, la date de publication, la date de publication la plus ancienne et la plus récente, comme vous vous y attendriez. Essayons une autre ici. Vidéos axées sur le sujet de Chat LangChain, publiées avant 2024. C'est juste une réécriture de cette question d'une manière légèrement différente, en utilisant un filtre de date différent. Et ensuite, vous pouvez voir que nous pouvons obtenir, nous obtenons une recherche de contenu, une recherche de titre, puis nous pouvons obtenir une sorte de recherche de date. C'est donc une stratégie très générale qui peut être appliquée de manière large à différents types de requêtes que vous souhaitez effectuer. C'est vraiment le processus de passage d'une entrée non structurée à un objet de requête structuré, en suivant un schéma arbitraire que vous fournissez. Et comme noté, vraiment, tout cela que nous avons créé ici, ce "tutorial search", est basé sur les spécificités de notre "vector store" d'intérêt. Et si vous voulez en savoir plus à ce sujet, je lie à une documentation qui parle beaucoup des différents types d'intégrations que nous avons avec différents fournisseurs de "vector store" pour faire exactement cela. C'est donc une astuce très utile. Elle vous permet d'effectuer un filtrage de métadonnées à la volée à partir d'une question en langage naturel. C'est une astuce très pratique qui fonctionne avec de nombreuses bases de données vectorielles différentes. J'encourage donc à jouer avec. Merci. Salut, c'est Lance de LangChain. Je vais parler de l'indexation, et en particulier de l'indexation par émulation, pour la 12ème partie de notre série RAG from Scratch. Nous avons précédemment parlé de quelques domaines majeurs. Nous avons parlé de la traduction de requêtes, qui prend une question et la traduit d'une manière ou d'une autre pour optimiser la récupération. Nous avons parlé du routage, qui est le processus de prise d'une question et de son acheminement vers la bonne source de données, qu'il s'agisse d'un "vector store", d'une base de données graphique ou d'une base de données SQL. Nous avons parlé de la construction de requêtes, nous avons examiné essentiellement la construction de requêtes pour les "vector stores", mais bien sûr, il y a aussi du texte SQL, du texte vers Cypher. Nous allons donc maintenant parler un peu de l'indexation, en particulier des techniques d'indexation pour les "vector stores". Et je veux mettre en évidence une méthode particulière aujourd'hui appelée indexation multi-représentations. L'idée générale est dérivée un peu d'un article appelé "proposition indexing", qui fait une simple observation. Vous pouvez penser à découpler les documents bruts et l'unité que vous utilisez pour la récupération. Dans le cas typique, vous prenez un document, vous le divisez d'une certaine manière pour l'indexer, puis vous intégrez la division directement. Cet article parle en fait de prendre un document, de le diviser d'une certaine manière, puis d'utiliser un LLM pour produire ce qu'ils appellent une proposition, que vous pouvez considérer comme une sorte de distillation de cette division. C'est donc un peu comme utiliser un LLM pour modifier cette division d'une manière ou d'une autre pour la distiller ou la rendre plus nette, comme un résumé, pour ainsi dire, qui est mieux optimisé pour la récupération. C'est donc une mise en évidence, une pièce d'intuition. Nous avons donc pris cette idée et nous l'avons un peu développée d'une manière vraiment agréable qui, je pense, est très bien adaptée aux LLM à long contexte. L'idée est assez simple : vous prenez un document et vous le distillez ou créez une proposition, comme ils le montrent dans l'article précédent. Je pense généralement à cela comme produire un résumé du document, et vous intégrez ce résumé. Ce résumé est destiné à être optimisé pour la récupération, il peut donc contenir un tas de mots-clés du document ou les grandes idées, de sorte que lorsque vous intégrez le résumé, vous intégrez une question, vous faites une recherche, vous pouvez essentiellement trouver ce document basé sur ce résumé hautement optimisé pour la récupération. C'est donc représenté ici dans votre "vector store". Mais voici le hic : vous stockez indépendamment le document brut dans un "doc store". Et lorsque vous récupérez le résumé dans le "vector store", vous retournez le document complet pour que le LLM effectue la génération. Et c'est une astuce agréable car au moment de la génération, avec les LLM à long contexte, par exemple, le LLM peut gérer ce document entier. Vous n'avez pas à vous soucier de le diviser ou quoi que ce soit. Vous utilisez simplement le résumé pour créer une très bonne représentation pour extraire ce document complet. Utilisez ce document complet dans la génération. Il peut y avoir de nombreuses raisons pour lesquelles vous voulez faire cela. Vous voulez vous assurer que le LLM a le contexte complet pour répondre à la question. C'est donc la grande idée. C'est une astuce agréable. Et parcourons un peu le code. Nous avons un notebook tout configuré, comme avant. Nous avons fait quelques installations pip, configuré peut-être des clés API pour LangSmith. Voici un diagramme. Maintenant, montrons un exemple. Chargeons simplement deux articles de blog différents. L'un concerne les agents, l'autre concerne la qualité des données humaines. Et ce que nous allons faire, c'est créer un résumé de chacun d'eux. C'est donc la première étape de ce processus, où nous passons des documents bruts aux résumés. Jetons un coup d'œil et assurons-nous qu'ils ont fonctionné. D'accord, cool. Donc, le premier document discute de la construction d'agents autonomes. Le second document contient l'importance de données humaines de haute qualité pour l'entraînement. C'est donc assez bien. Nous avons nos résumés. Maintenant, nous allons passer par un processus assez simple. D'abord, nous définissons un "vector store" qui indexera ces résumés. Maintenant, nous allons définir ce que nous appelons notre stockage de documents, qui stockera les documents complets. Donc, ce "multi-vector retriever" rassemble simplement ces deux choses. Nous ajoutons essentiellement notre "doc store". Nous avons ce "byte store", qui est essentiellement le stockage de documents complets. Le "vector store" est notre "vector store". Et maintenant, cet ID est ce que nous allons utiliser pour faire référence entre les fragments ou les résumés et les documents complets. C'est vraiment tout. Donc, pour chaque document, nous allons définir un nouvel ID de document. Et ensuite, nous allons essentiellement prendre nos documents de résumé. Et nous allons extraire, pour chacun de nos résumés, l'ID de document associé. Alors allons-y. Donc, nous avons nos documents de résumé que nous ajoutons au "vector store". Nous avons nos documents complets, nos ID de documents et les documents bruts complets qui sont ajoutés à notre "doc store". Et ensuite, faisons juste une recherche de similarité dans le "vector store". Mémoire et agents. Et nous pouvons voir, d'accord, nous pouvons extraire, par exemple, à partir des résumés, nous pouvons obtenir, par exemple, le résumé qui concerne les agents. C'est donc une bonne chose. Maintenant, allons-y et exécutons une requête pour obtenir les documents pertinents sur notre récupérateur, qui combine essentiellement les résumés que nous utilisons pour la récupération, puis le "doc store" que nous utilisons pour obtenir le document complet en retour. Nous allons appliquer notre requête. Nous allons essentiellement exécuter cela, et voici le point clé : nous avons récupéré l'article entier. Et nous pouvons même, si vous voulez regarder le tout, nous pouvons simplement le faire. Voici l'article entier que nous obtenons de cette recherche. C'est donc une astuce assez agréable. Encore une fois, nous interrogeons avec juste "mémoire et agents", et nous pouvons revenir à notre diagramme. Nous avons interrogé "mémoire et agents", cela a commencé par nos résumés, a trouvé le résumé lié à "mémoire et agents", a utilisé cet ID de document pour faire référence entre le "vector store" et le "doc store", a extrait le bon document complet, nous renvoie le document complet, dans ce cas, la page Web complète. C'est donc vraiment tout. Idée simple, belle façon de passer de l'indexation de style proposition ou résumé simple à la récupération de documents complets, ce qui est très utile, surtout avec les LLM à long contexte. Merci. Salut, c'est Lance de LangChain. C'est la 13ème partie de notre série RAG from Scratch axée sur une technique appelée Raptor. Raptor se situe dans un ensemble de différentes techniques d'indexation qui peuvent être appliquées aux "vector stores". Nous venons de parler de l'indexation multi-représentations. J'ai mis un lien vers une vidéo très bonne qui parle des différentes méthodes de "chunking", donc je vous encourage à la regarder. Et nous allons parler aujourd'hui d'une technique appelée Raptor, que vous pouvez considérer comme une technique d'indexation hiérarchique. L'intuition générale est la suivante : certaines questions nécessitent des informations très détaillées d'un corpus pour y répondre, comme celles qui concernent un seul document ou un seul fragment. Nous pouvons donc appeler ces questions de bas niveau. Certaines questions nécessitent une consolidation à travers de larges pans d'un document, comme à travers de nombreux documents ou de nombreux fragments au sein d'un document, et vous pouvez appeler ces questions de niveau supérieur. Il y a donc un défi dans la récupération, et généralement, nous faisons une récupération par K voisins les plus proches, comme nous en avons parlé. Vous extrayez un certain nombre de fragments, mais que se passe-t-il si vous avez une question qui nécessite des informations à travers, disons, cinq, six, ou un certain nombre de fragments différents, qui peuvent dépasser le paramètre K de votre récupération ? Encore une fois, lorsque vous faites généralement une récupération, vous pourriez définir un paramètre K de trois, ce qui signifie que vous récupérez trois fragments de votre "vector store". Et peut-être avez-vous une question de très haut niveau qui pourrait bénéficier d'informations à travers plus de trois fragments. Cette technique appelée Raptor est donc essentiellement un moyen de construire un index hiérarchique de résumés de documents. Et l'intuition est la suivante : vous commencez avec un ensemble de documents comme vos feuilles ici à gauche, vous les regroupez, puis vous résumez chaque groupe. Donc, chaque groupe de documents similaires consultera des informations provenant de votre contexte, qui est, vous savez, votre contexte peut être un tas de divisions différentes ou même un tas de documents différents. Vous capturez essentiellement les similaires et vous consolidez les informations à travers eux dans un résumé. Et voici la chose intéressante : vous faites cela récursivement jusqu'à ce que vous atteigniez une limite ou que vous vous retrouviez avec un seul groupe qui est un résumé de très haut niveau de tous vos documents. Et ce que l'article montre, c'est que si vous effondrez simplement tout cela et que vous les indexez ensemble comme un grand pool, vous obtenez un très bon ensemble de fragments qui couvrent la hiérarchie d'abstraction. Vous avez un tas de fragments provenant de documents individuels qui sont plus détaillés, concernant ce document unique, mais vous avez aussi des fragments provenant de ces résumés. Ou je dirais, peut-être pas des fragments, mais dans ce cas, le résumé est une distillation. Donc, les fragments bruts à gauche, qui représentent vos feuilles, sont un peu la forme la plus brute d'information, soit des fragments bruts, soit des documents bruts. Ensuite, vous avez ces résumés de niveau supérieur, qui sont tous indexés ensemble. Donc, si vous avez des questions de niveau supérieur, elles devraient être plus similaires, dans une recherche sémantique par exemple, à ces fragments de résumé de niveau supérieur. Si vous avez des questions de niveau inférieur, elles récupéreront ces fragments de niveau inférieur, et vous avez donc une meilleure couverture sémantique à travers la hiérarchie d'abstraction des types de questions. C'est l'intuition. Ils font un tas de bonnes études pour montrer que cela fonctionne assez bien. J'ai en fait fait une vidéo de plongée approfondie sur cela, à laquelle je mets un lien ci-dessous. Je voulais juste en couvrir brièvement, à un niveau très élevé. Alors, faisons juste une analyse de code. Je l'ai ajouté à ce notebook de cours RAG from Scratch, mais je mets un lien vers ma vidéo de plongée approfondie ainsi que vers l'article et le notebook de code complet qui est déjà vérifié, discuté plus en détail dans la plongée approfondie. La technique est un peu détaillée, donc je veux juste vous donner un aperçu très général. Et vous pouvez regarder la vidéo de plongée approfondie si vous voulez aller plus loin. Encore une fois, nous avons parlé de cette hiérarchie d'abstraction. Je l'ai appliquée à un grand ensemble de documents LangChain. Donc, c'est moi qui charge essentiellement tous nos documents LangChain Expression Language. Il s'agit donc de l'ordre de 30 documents. Vous pouvez voir que je fais un histogramme ici du nombre de jetons par document. Certains sont assez grands, la plupart sont assez petits, moins de 4 000 jetons. Et ce que j'ai fait, c'est que je les ai tous indexés individuellement. Donc, tous ces documents bruts, vous pouvez les imaginer ici à gauche, puis je fais, j'intègre, je regroupe, je résume, et je fais cela récursivement jusqu'à ce que je me retrouve, dans ce cas, je crois que j'ai seulement défini trois niveaux de récursion, puis je les enregistre tous dans mon "vector store". C'est donc l'idée générale. J'applique cette technique Raptor à un tas de documents LangChain qui ont un nombre assez important de jetons. Je fais donc cela. Et oui, j'utilise en fait à la fois Claude et OpenAI ici. Cela parle de la méthode de regroupement qu'ils utilisent, qui est assez intéressante. Vous pouvez l'examiner par vous-même si vous êtes vraiment intéressé. C'est beaucoup de leur code, que je cite en conséquence. Ceci implémente essentiellement la méthode de regroupement qu'ils utilisent. Et ceci est simplement l'étape d'intégration de documents. Ceci est essentiellement l'intégration et le regroupement, c'est vraiment tout. Un peu de formatage de texte, résumant les groupes ici. Et ensuite, cela exécute simplement tout ce processus récursivement. C'est vraiment tout. C'est la construction d'arbres. Donc, essentiellement, j'ai les documents bruts. Revenons et regardons les textes des documents. Donc, ce devraient être tous mes documents bruts. Donc, c'est juste. Vous pouvez le voir ici. "doc_text" est essentiellement juste le texte de tous ces documents LangChain que j'ai extraits. Et donc, j'exécute ce processus dessus ici. C'est donc cette intégration récursive, le regroupement, qui s'exécute et produit cet arbre. Voici les résultats. C'est moi qui parcours les résultats et qui ajoute essentiellement le texte du résultat à cette liste de textes. Oh, d'accord. Voici ce que je fais. Ce "leaf_text" sont tous les documents bruts, et j'ajoute à cela tous les résumés. C'est tout ce qui se passe. Et ensuite, je les indexe tous ensemble. C'est le point clé. RAG Chain. Et voilà. C'est vraiment tout ce que vous faites. Donc, en tout cas, je vous encourage à regarder cela en profondeur. C'est une technique assez intéressante. Elle fonctionne bien avec les longs contextes. Par exemple, l'un des arguments que j'ai avancés est que c'est une approche intéressante pour consulter des informations à travers un ensemble de grands documents. Comme dans ce cas particulier, mes documents individuels étaient des documents LangChain Expression Language, chacun étant de l'ordre de, vous savez, dans ce cas, la plupart d'entre eux sont inférieurs à 4 000 jetons, certains assez grands. Mais je les ai tous indexés, je les ai regroupés sans aucune division, intégrés, regroupés, construit cet arbre, et je suis parti de là. Et tout fonctionne parce que nous avons maintenant des LLM qui peuvent aller jusqu'à 100 000 ou 200 000, jusqu'à un million de jetons de contexte. Vous pouvez donc simplement faire ce processus pour de grandes portions de documents sur place, sans aucune division. C'est une approche assez agréable. Je vous encourage donc à y réfléchir, à la regarder, à regarder la vidéo de plongée approfondie si vous voulez aller plus loin. Merci. Salut, c'est Lance de LangChain. C'est la 14ème partie de notre série RAG from Scratch. Je vais parler d'une approche appelée Cold Bear. Nous avons parlé de quelques approches différentes pour l'indexation, et juste pour rappel, l'indexation se situe ici dans notre flux. Nous avons commencé initialement par la traduction de requêtes, prenant une question, la traduisant d'une manière ou d'une autre pour optimiser la récupération. Nous avons parlé de son routage vers une base de données particulière. Nous avons ensuite parlé de la construction de requêtes, passant du langage naturel au DSL ou langage spécifique au domaine pour l'une des bases de données avec lesquelles vous voulez travailler. Ce sont, vous savez, des filtres de métadonnées pour les "vector stores", ou Cypher pour les bases de données graphiques, ou SQL pour les bases de données relationnelles. C'est donc le flux. Nous avons parlé aujourd'hui, nous avons parlé de certaines approches d'indexation comme l'indexation multi-représentations. Nous avons fait une petite mention de "greet camer" dans la série sur le "chunking". Nous avons parlé d'indexation hiérarchique, et je veux inclure une approche d'intégration avancée. Nous avons beaucoup parlé des intégrations. Elles sont évidemment centrales à la recherche de similarité sémantique et à la récupération. L'un des points intéressants qui a été soulevé est que les modèles d'intégration prennent bien sûr un document, vous pouvez le voir ici en haut, et l'intègrent, le compressent essentiellement en un vecteur. C'est donc une sorte de processus de compression. Vous représentez toutes les sémantiques de ce document dans un seul vecteur. Vous faites la même chose pour votre question. Vous faites une recherche de similarité entre l'intégration de la question et l'intégration du document, afin d'effectuer la récupération. Vous prenez généralement les K intégrations de documents les plus similaires, étant donné une question, et c'est ainsi que vous le faites. Maintenant, beaucoup de gens ont dit : "Eh bien, compresser un document complet avec toute cette nuance en un seul vecteur semble un peu trop restrictif, n'est-ce pas ?" Et c'est une question juste à poser. Il y a eu des approches intéressantes pour essayer de résoudre ce problème, et l'une d'elles est cette méthode appelée Cold Bear. L'intuition est en fait assez simple. Il y a un tas de bons articles auxquels je mets un lien ici. C'est mon petit dessin pour l'expliquer, qui, je l'espère, est utile. Mais voici l'idée principale : au lieu de simplement prendre un document et de le compresser en un seul vecteur, un seul, ce que nous pourrions appeler un vecteur d'intégration, nous prenons le document, nous le divisons en jetons. Les jetons sont juste, vous savez, des unités de contenu. Cela dépend des unités de jetons que vous utilisez. Nous en avons parlé plus tôt. Vous le divisez donc en jetons et vous produisez essentiellement une intégration ou un vecteur pour chaque jeton. Et il y a une sorte de pondération positionnelle qui se produit lorsque vous faites ce processus. Vous regardez donc évidemment l'implémentation pour comprendre les détails, mais l'intuition est que vous produisez une sorte de représentation pour chaque jeton. Et vous faites la même chose pour votre question. Vous prenez votre question, vous la divisez en jetons, et vous avez une sorte de représentation ou de vecteur par jeton. Et ensuite, ce que vous faites, c'est que pour chaque jeton de la question, vous calculez la similarité avec tous les jetons du document, et vous trouvez le maximum. Vous prenez le maximum, vous le stockez, et vous faites ce processus pour tous les jetons de la question. Encore une fois, jeton deux, vous le comparez à chaque jeton du document, vous calculez le maximum, et ensuite le score final est, dans ce cas, la somme des similarités maximales entre chaque jeton de question et n'importe quel jeton de document. C'est une approche intéressante. Elle rapporte des performances très solides. La latence est certainement une question. Donc, la préparation à la production est quelque chose que vous devriez examiner, mais c'est une approche qui vaut la peine d'être mentionnée ici, car elle est assez intéressante. Et parcourons le code. Il existe une bonne bibliothèque appelée "rouille" qui permet de jouer facilement avec Cold Bear. Je l'ai installée ici. Je l'ai déjà fait, et nous pouvons utiliser l'un de leurs modèles pré-entraînés pour médiatiser ce processus. Je suis donc en train de suivre leur documentation. C'est un peu ce qu'ils ont recommandé. Je lance donc cela maintenant. J'espère que cela fonctionnera assez rapidement. Je ne suis pas sûr. J'ai déjà chargé ce modèle, donc j'espère que cela ne prendra pas trop de temps. Et oui, vous pouvez voir que c'est assez rapide. Je suis sur un Mac M2 avec 32 Go, donc juste comme contexte en termes de mon système. Ceci provient de leur documentation. Nous prenons juste une page Wikipédia. Ceci obtient un document complet sur Miyazaki. C'est donc cool. Nous allons le récupérer maintenant. Ceci est juste tiré de leur documentation. C'est essentiellement ainsi que nous créons un index. Nous fournissons le nom de l'index, la collection, la longueur maximale du document, et oui, vous devriez consulter leur documentation pour ces indicateurs. Ce sont juste les valeurs par défaut. Je vais donc créer mon index. J'obtiens donc un peu de journalisation ici, donc cela fonctionne en coulisses. Et au fait, j'ai leur documentation ouverte, donc vous pouvez suivre. Alors, voyons, oui, juste à peu près ici. Vous pouvez donc suivre ce processus d'indexation pour créer un index. Vous devez charger un modèle entraîné. Il peut s'agir de votre propre modèle pré-entraîné ou de l'un des nôtres du Hub. Et c'est un peu le processus que nous faisons en ce moment. "create_index" n'est que quelques lignes de code, et c'est exactement ce que nous faisons. Donc, ce sont mes documents, et c'est l'étape d'indexation que nous venons de parcourir. Et il semble que ce soit fait. Vous obtenez donc un tas de journalisation ici, ce qui est bien. Maintenant, voyons si cela fonctionne. Nous allons donc exécuter une recherche RAG : "Quel studio d'animation a trouvé Miyazaki ?" Définissons notre paramètre K et obtenons quelques résultats. D'accord, cela fonctionne et cool, nous obtenons des documents. Donc, vous savez, cela semble fonctionner. Maintenant, ce qui est bien, c'est que vous pouvez l'exécuter dans LangChain comme un récupérateur LangChain. Cela encapsule donc essentiellement cela comme un récupérateur LangChain, puis vous pouvez l'utiliser librement comme récupérateur dans LangChain. Il fonctionne avec tous les autres LLM et tous les autres composants comme les classeurs et ainsi de suite dont nous avons parlé. Vous pouvez donc l'utiliser directement comme récupérateur. Essayons cela. Et boum, rapide et efficace. Et nous obtenons nos documents. Encore une fois, c'est un exemple de test super simple. Vous devriez l'exécuter peut-être sur des cas plus complexes, mais c'est assez facile à mettre en place. C'est une approche d'indexation vraiment intéressante, utilisant encore une fois, comme nous l'avons dit, un algorithme très différent pour calculer la similarité des documents qui pourrait mieux fonctionner. Je pense qu'un régime intéressant à considérer serait les documents plus longs. Donc, si vous voulez des intégrations de contexte plus longues, je pense que vous devriez regarder, par exemple, les limites maximales de jetons pour cette approche, car elle partitionne le document en chaque jeton. Je serais curieux de creuser ce que sont les limites de contexte globales pour cette approche de Cold Bear, mais c'est vraiment intéressant à considérer et elle rapporte des performances très solides. Donc, encore une fois, je vous encourage à jouer avec. Et ceci est juste une introduction à la façon de se mettre en place et de commencer à expérimenter rapidement. Merci. Salut, c'est Lance de LangChain. Je vais parler de l'utilisation de LangGraph pour construire des flux RAG diversifiés et sophistiqués. Pour planter le décor, le flux RAG de base que vous pouvez voir ici commence par une question, la récupération de documents pertinents à partir d'un index, qui sont passés dans la fenêtre de contexte d'un LLM pour la génération d'une réponse ancrée dans vos documents. C'est à peu près le schéma de base, et nous pouvons voir que c'est un chemin très linéaire. En pratique, cependant, vous rencontrez souvent différents types de questions, comme quand voulons-nous réellement récupérer en fonction du contexte de la question ? Les documents récupérés sont-ils réellement bons ou non ? Et s'ils ne sont pas bons, devrions-nous les ignorer ? Et ensuite, comment boucler et réessayer la récupération avec, par exemple, une question améliorée ? Ces types de questions motivent une idée de RAG actif, qui est un processus où un LLM décide quand et où récupérer en fonction des récupérations existantes ou des générations existantes. Maintenant, quand vous pensez à cela, il y a quelques niveaux de contrôle différents que vous avez sur un LLM dans une application RAG. Le cas de base, comme nous l'avons vu avec notre chaîne, est simplement d'utiliser un LLM pour choisir la sortie d'une seule étape. Par exemple, dans le RAG traditionnel, vous lui donnez des documents et il décide de la génération, donc c'est juste une étape. Maintenant, beaucoup de flux de travail RAG utiliseront l'idée de routage, comme étant donné une question, devrais-je la router vers un "vector store" ou une base de données graphique ? Et nous avons vu cela assez souvent. Maintenant, cette nouvelle idée que je veux introduire est comment construire des flux logiques plus sophistiqués dans un pipeline RAG, qui permettent au LLM de choisir entre différentes étapes, mais spécifient toutes les transitions disponibles. Et c'est ce que nous appelons une machine à états. Il existe donc quelques architectures différentes qui ont émergé pour construire différents types de chaînes RAG, et bien sûr, les chaînes sont traditionnellement utilisées juste pour le RAG très basique, mais cette notion de machine à états est un peu plus nouvelle. Et LangGraph, que nous avons récemment publié, offre un excellent moyen de construire des machines à états pour le RAG et pour d'autres choses. Et l'idée générale est que vous pouvez organiser des flux RAG plus diversifiés et compliqués, puis les implémenter sous forme de graphes. Et cela motive cette idée plus large d'ingénierie de flux et de réflexion sur le flux de travail réel que vous souhaitez, puis de sa mise en œuvre. Et nous allons faire cela maintenant. Je vais donc utiliser un article récent appelé C-AG, "Corrective RAG", qui est une très bonne méthode pour le RAG actif qui intègre quelques idées différentes. Donc, d'abord, vous récupérez des documents, puis vous les notez. S'il y a au moins un document qui dépasse le seuil de pertinence, vous passez à la génération, vous générez votre réponse. Et il effectue cette étape de raffinement des connaissances après cela, mais ne vous en souciez pas pour l'instant, ce n'est pas essentiel pour comprendre le flux de base ici. Donc, encore une fois, vous faites une note de pertinence pour chaque document. S'il y en a un de pertinent, vous générez. Maintenant, s'ils sont tous ambigus ou incorrects, sur la base de votre noteur, vous récupérez d'une source externe. Ils utilisent la recherche web, puis ils la transmettent comme contexte pour la génération de réponses. C'est donc un flux de travail très soigné où vous effectuez la récupération, tout comme avec le RAG de base, mais ensuite vous raisonnez sur les documents. S'ils sont pertinents, allez-y, au moins un est pertinent, allez-y et générez. S'ils ne le sont pas, récupérez d'une source alternative, puis intégrez-la dans le contexte et générez votre réponse. Voyons donc comment nous implémenterions cela comme une machine à états en utilisant LangGraph. Nous ferons quelques simplifications. Nous allons d'abord décider si des documents sont pertinents. Nous allons faire la recherche web pour compléter la sortie. C'est juste une modification mineure. Nous utiliserons Tav Search pour la recherche web. Nous utiliserons l'écriture de requêtes pour optimiser la recherche pour optimiser la recherche web, mais cela suit bon nombre des intuitions de l'article principal. Petite note ici : nous avons défini la clé API Tav. Et une autre petite note : j'ai déjà défini ma clé API LangSmith, avec laquelle nous verrons qu'elle est utile un peu plus tard pour observer les traces résultantes. Maintenant, je vais indexer trois articles de blog que j'aime. J'utiliserai Chroma DB, j'utiliserai des intégrations OpenAI. Je vais exécuter cela maintenant. Cela créera un "vector store" pour moi à partir de ces trois articles de blog. Et ensuite, ce que je vais faire, c'est définir l'état. Maintenant, c'est l'objet central qui sera transmis autour de mon graphe et que je vais modifier. Et ici, je le définis. Et le point clé à noter maintenant est que c'est juste un dictionnaire, et il peut contenir des choses pertinentes pour le RAG, comme la question, les documents, la génération. Et nous verrons comment nous mettons cela à jour un peu plus tard. Mais la première chose à noter est que nous définissons notre état, et c'est ce qui sera modifié dans chaque nœud de notre graphe. Maintenant, voici vraiment le cœur de la question, et c'est la chose sur laquelle je veux zoomer un peu. Donc, quand vous passez de la simple réflexion sur les prompts à la réflexion sur les flux globaux, c'est un exercice amusant et intéressant. Je pense à cela comme, cela a été mentionné sur Twitter un peu plus, comme l'ingénierie de flux. Pensons donc à ce qui a été réellement fait dans l'article et aux modifications de notre état qui se produiront à chaque étape. Nous commençons par une question. Vous pouvez le voir tout à gauche. Et cet état est représenté comme un dictionnaire, comme nous l'avons. Nous commençons par une question. Nous effectuons une récupération à partir de notre "vector store", que nous venons de créer. Cela nous donnera des documents. C'est donc un nœud. Nous avons fait un ajustement à notre état en ajoutant des documents. C'est l'étape un. Maintenant, nous avons un deuxième nœud où nous allons noter les documents. Et dans ce nœud, nous pourrions en filtrer certains. Nous faisons donc une modification de l'état, c'est pourquoi c'est un nœud. Nous aurons donc un noteur, puis nous aurons ce que nous appellerons une arête conditionnelle. Nous avons vu que nous sommes passés de la question à la récupération. La récupération va toujours à la notation. Et maintenant, nous avons une décision : si un document est non pertinent, nous allons faire une recherche web pour le compléter. Et s'ils sont tous pertinents, nous passerons à la génération. C'est une décision logique mineure, une décision logique mineure.

Je vais faire euh si certains ne sont pas pertinents, nous allons transformer la requête et nous allons faire une recherche web et nous allons l'utiliser pour la génération. Donc c'est vraiment tout et c'est ainsi que nous pouvons penser à notre flux et à la façon dont nos états peuvent être modifiés tout au long de ce flux. Maintenant, tout ce que nous avons ensuite à faire, et j'ai trouvé que passer 10 minutes à réfléchir attentivement à votre ingénierie de flux est vraiment précieux car à partir de là, ce sont vraiment des détails d'implémentation. et c'est assez facile comme vous le verrez. Donc, en gros, je vais exécuter ce bloc de code, mais ensuite nous pourrons en parcourir une partie. Je ne vous montrerai pas tout, donc ce sera un peu ennuyeux, mais en réalité, tout ce que nous faisons, c'est que nous trouvons des fonctions pour chaque nœud qui prennent l'état et le modifient d'une manière ou d'une autre. C'est tout ce qui se passe. Donc, pensez à la récupération. Nous exécutons la récupération. Nous prenons l'état, rappelez-vous que c'est un dictionnaire. Nous obtenons notre dictionnaire d'état comme ceci. Nous extrayons une seule question clé de notre dictionnaire. Nous la passons à un récupérateur. Nous obtenons des documents et nous réécrivons l'état. Maintenant, avec la clé de documents ajoutée, c'est tout. La génération sera similaire. Nous prenons l'état. Maintenant, nous avons notre question et nos documents. Nous intégrons un prompt. Nous définissons un LLM. Nous faisons un post-traitement mineur sur les documents. Nous mettons en place une chaîne pour la récupération, euh, ou désolé, pour la génération, qui va simplement prendre notre prompt, le pomper vers un LLM, produire partiellement une chaîne et nous l'exécutons ici, en invoquant nos documents dans notre question pour obtenir notre réponse. Nous la réécrivons dans l'état. C'est tout. Et vous pouvez suivre ici pour chaque nœud. Nous définissons simplement une fonction qui effectue la modification de l'état que nous voulons faire sur ce nœud. La notation des documents sera la même. Dans ce cas, je fais une petite chose supplémentaire ici car je définis en fait un modèle de données identique pour mon notateur, de sorte que la sortie de cette chaîne de notation particulière est un oui ou un non binaire. Vous pouvez regarder le code, assurez-vous qu'il est tout partagé, et cela garantit simplement que notre sortie est très déterministe, de sorte que nous pouvons ensuite, plus bas, effectuer un filtrage logique. Donc, ce que vous pouvez voir ici, c'est que nous définissons cette valeur de recherche non et nous itérons sur nos documents. Nous les notons. Si un document est noté comme non pertinent, nous signalons cette chose de recherche comme oui. Cela signifie que nous allons effectuer une recherche web. Nous l'ajoutons ensuite à notre dictionnaire d'état à la fin. Donc, exécutez la recherche web. Maintenant, cette valeur est vraie. C'est tout. Et vous pouvez voir que nous passons par d'autres nœuds ici. Il y a le nœud de recherche web. Maintenant, c'est ici que notre seule arête conditionnelle est définie ici. C'est ici que nous décidons de générer ou non en fonction de cette clé de recherche. Donc, nous obtenons à nouveau notre état. Extraions les différentes valeurs. Nous avons donc cette valeur de recherche. Maintenant, si la recherche est oui, nous retournons le prochain non auquel nous voulons aller. Donc, dans ce cas, ce sera transformer la requête, qui ira ensuite à la recherche web, sinon nous allons à générer. Donc, ce que nous pouvons voir, c'est que nous avons présenté notre graphique, que vous pouvez voir ici, et maintenant nous définissons des fonctions pour tous ces nœuds ainsi que pour l'arête conditionnelle. Et maintenant, nous descendons. Tout ce que nous avons à faire est de le présenter à nouveau ici comme notre flux, et c'est un peu ce que vous pourriez penser comme de l'ingénierie de flux, où vous présentez simplement le graphique tel que vous l'avez dessiné, où nous avons défini notre point d'entrée comme récupérer. Nous ajoutons une arête entre récupérer et noter les documents. Nous sommes donc passés de la récupération à la notation des documents. Nous ajoutons notre arête conditionnelle en fonction de la note, soit transformer la requête, aller à la recherche web, soit simplement aller à générer. Nous créons une arête entre transformer la requête et la recherche web, puis la recherche web vers générer, et nous avons également une arête de générer à terminer, et c'est tout notre graphique. C'est tout. Nous pouvons donc simplement exécuter cela et maintenant je vais poser une question. Disons simplement, par exemple, comment fonctionne la mémoire d'agent. Essayons cela, et cela va imprimer ce qui se passe pendant que nous parcourons ce graphique. Donc, d'abord, nous allons voir la sortie de récupérer. Ce seront tous nos documents que nous avons récupérés, donc c'est bien. C'est juste de notre récupérateur. Ensuite, vous pouvez voir que nous effectuons une vérification de pertinence sur nos documents, et c'est assez intéressant, n'est-ce pas ? Vous pouvez voir que nous les notons ici. L'un est noté comme non pertinent, et d'accord, vous pouvez voir que les documents sont maintenant filtrés car nous avons supprimé celui qui n'est pas pertinent. Et parce qu'un n'est pas pertinent, nous décidons, d'accord, nous allons simplement transformer la requête et exécuter une recherche web. Et vous pouvez voir qu'après la transformation de la requête, nous réécrivons légèrement la question. Nous exécutons ensuite une recherche web. Et vous pouvez voir qu'à partir de la recherche web, elle a recherché des sources supplémentaires, que vous pouvez en fait voir ici. Elle est ajoutée comme, donc, la voici. Donc, c'est un nouveau document ajouté à partir de la recherche web, qui provient des exigences de connaissance de la mémoire. Donc, elle a essentiellement recherché une architecture IA liée à la mémoire, des résultats web. Donc, c'est bien. C'est exactement ce que nous voulons faire. Et ensuite, nous générons une réponse. Donc, c'est génial. Et cela vous montre simplement tout dans les moindres détails, mais je vais vous montrer une autre chose qui est vraiment agréable à ce sujet. Si je vais sur lsmith, j'ai ma clé API configurée. Donc, toutes mes générations sont simplement enregistrées sur lsmith, et je peux voir mon exécution de Lang graph ici. Maintenant, ce qui est vraiment cool, c'est que cela me montre tous mes nœuds. Rappelez-vous que nous avions récupérer, noter, nous avons évalué la note. Parce qu'un était non pertinent, nous avons ensuite procédé à la transformation de la requête, nous avons effectué une recherche web, nous l'avons ajoutée à notre contexte. Vous pouvez voir toutes ces étapes sont présentées ici. En fait, vous pouvez même regarder chaque notateur individuel et sa sortie. Je vais déplacer ceci légèrement vers le haut, donc vous pouvez voir les différentes notes pour les notes. D'accord, donc cette récupération particulière a été notée comme non pertinente. Donc, c'est bien, cela peut arriver dans certains cas. Et à cause de cela, nous avons effectué une transformation de requête, donc nous avons légèrement modifié la question. Comment le système de mémoire dans les agents artificiels fonctionne-t-il ? C'est juste une reformulation mineure de la question. Nous avons effectué cette recherche web Tav. C'est là qu'elle a interrogé cet article de blog particulier de medium. C'est donc comme une requête web unique. Nous pouvons la vérifier. Et ensuite, ce qui est bien, c'est que nous pouvons aller à notre étape de génération, regarder open Ai, et voici notre prompt complet. Comment le système de mémoire dans nos agents officiels fonctionne-t-il ? Et voici tous nos documents. Donc, c'est la recherche web, ainsi que nous avons toujours les morceaux pertinents qui ont été récupérés de nos articles de blog, et ensuite voici notre réponse. C'est donc vraiment tout. Vous pouvez voir comment, en passant de la notion de simplement, je vais en fait revenir à l'original, en passant de, j'essaierai de l'ouvrir un peu, oui, je peux toujours voir mon visage, la transition de la présentation de chaînes simples à des flux est une façon vraiment intéressante et utile de penser pourquoi les graphiques sont vraiment intéressants, car vous pouvez encoder des flux de raisonnement logique plus sophistiqués, mais d'une manière très propre et bien conçue où vous pouvez spécifier toutes les transitions que vous souhaitez réellement exécuter. Et je trouve en fait cette façon de penser et de construire des flux logiques vraiment intuitive. Nous avons un article de blog qui sort demain et qui discute à la fois de l'implémentation de self rag ainsi que de c rag pour deux approches rag actives différentes utilisant cette idée de machines à états et de Lang graph. Je vous encourage donc à jouer avec. Je l'ai trouvé vraiment intuitif à utiliser. J'ai également trouvé l'inspection des traces assez intuitive en utilisant Lang graph, car chaque nœud est énuméré très clairement pour vous, ce qui n'est pas toujours le cas lorsque vous utilisez d'autres types d'approches de raisonnement plus complexes, par exemple comme les agents. Donc, dans tous les cas, j'espère que cela a été utile et je vous encourage vraiment à consulter cette notion d'ingénierie de flux en utilisant Lang graph, et dans le contexte de rag, cela peut être vraiment puissant, j'espère, comme vous l'avez vu ici. Merci. Salut, c'est Lance de Langchain. Je veux parler d'un article récent que j'ai vu appelé adaptive rag, qui rassemble des idées intéressantes qui ont été abordées dans d'autres vidéos, mais cela les relie toutes d'une manière amusante. Donc, les deux grandes idées à aborder ici sont l'analyse des requêtes. Nous avons en fait fait une série rag complète à partir de zéro qui détaille chacune de ces choses, mais c'est un très bon exemple de la façon dont cela se combine avec d'autres idées dont nous avons parlé. L'analyse des requêtes est généralement le processus consistant à prendre une question d'entrée et à la modifier d'une manière ou d'une autre pour mieux optimiser la récupération. Il existe un tas de méthodes différentes pour cela. Il pourrait s'agir de la décomposer en sous-questions, il pourrait s'agir d'utiliser des techniques intelligentes comme le step-back prompting, mais c'est un peu la première étape de l'analyse des requêtes. Ensuite, vous pouvez généralement effectuer un routage. Vous routez une question vers l'une des plusieurs sources potentielles. Il pourrait s'agir d'un ou deux magasins vectoriels différents, il pourrait s'agir d'une base de données relationnelle par rapport à un magasin vectoriel, il pourrait s'agir d'une recherche web, il pourrait simplement s'agir d'un repli LLM, n'est-ce pas ? C'est donc une sorte de grande idée, l'analyse des requêtes, n'est-ce pas ? C'est un peu le front-end de votre pipeline rag. Il prend votre question, la modifie d'une manière ou d'une autre, l'envoie au bon endroit, qu'il s'agisse d'une recherche web, d'un magasin vectoriel, d'une base de données relationnelle. C'est donc le sujet un. Maintenant, le sujet deux est quelque chose qui a été abordé dans quelques autres vidéos de ce que j'appelle l'ingénierie de flux ou adaptive rag, qui est l'idée de faire des tests dans votre pipeline rag ou dans votre flux d'inférence rag pour faire des choses comme vérifier la pertinence des documents, vérifier si la réponse contient des hallucinations. Ce récent article de blog de Hamil Hussein couvre en fait l'évaluation en détail. Et l'une des choses qu'il a soulignées explicitement est en fait ce sujet. Il parle de tests unitaires et en particulier, il dit quelque chose de vraiment intéressant ici. Il dit, vous savez, contrairement aux tests unitaires typiques, vous voulez organiser ces assertions dans des endroits au-delà des tests unitaires typiques, tels que le nettoyage des données et voici le point clé, les nouvelles tentatives automatiques lors de l'inférence du modèle, c'est la chose clé sur laquelle je veux attirer votre attention. C'est une très belle approche. Nous avons parlé de quelques autres articles qui font cela, comme corrective rag, self rag, mais c'est aussi cool de le voir ici et encapsulé de cette façon. L'idée principale est que vous utilisez des tests unitaires dans votre flux pour apporter des corrections. Si votre récupération est mauvaise, vous pouvez la corriger. Si votre génération a des hallucinations, vous pouvez la corriger. Je vais donc dessiner un diagramme de dessin animé de ce que nous allons faire ici, et vous pouvez le voir ici. Nous commençons par une question. Nous avons parlé de l'analyse des requêtes. Nous allons prendre notre question et décider où elle doit aller. Et pour cet exemple jouet particulier, je vais dire soit l'envoyer à un magasin vectoriel, soit l'envoyer à une recherche web, soit simplement laisser le LLM y répondre, n'est-ce pas ? C'est donc mon comportement de repli. Ensuite, nous allons intégrer cette idée d'ingénierie de flux en ligne ou de tests unitaires où j'aurai ma récupération, soit à partir du magasin vectoriel, soit de la recherche web. Je vais ensuite demander, est-ce que c'est réellement pertinent pour la question ? Si ce n'est pas le cas, je vais en fait revenir à la recherche web. C'est donc un peu plus pertinent dans le cas où j'ai routé vers le magasin vectoriel, effectué une récupération, les documents ne sont pas pertinents, j'aurai un mécanisme de repli. Ensuite, je vais générer. Je vérifie les hallucinations dans ma génération, puis je vérifie si la réponse répond à la question. Ensuite, je retourne ma réponse. Encore une fois, nous combinons deux idées : l'analyse des requêtes, comme prendre une question, l'acheminer au bon endroit, la modifier si nécessaire, puis des tests unitaires en ligne et un retour d'information itératif sur le flux. Pour ce faire, j'ai entendu beaucoup de gens parler en ligne de Command R, une nouvelle version de modèle de Google. Il a de très bonnes propriétés que j'ai lues récemment. Premièrement, il prend bien en charge l'utilisation d'outils et il prend en charge l'écriture de requêtes dans le contexte de l'utilisation d'outils. Donc, tout cela s'accumule dans de très bonnes capacités de routage. C'est un peu une seule chose maintenant. Deuxièmement, il est petit, il a 35 milliards de paramètres. Il est en fait à poids ouvert, vous pouvez donc l'exécuter localement et je l'ai essayé, nous pouvons en parler plus tard. Et il est également rapide, servi via l'API. C'est donc un petit modèle et il est bien optimisé pour rag. J'ai entendu beaucoup de gens parler de l'utilisation de Cohere pour rag, et il a un grand contexte de 120 000 jetons. C'est donc une bonne combinaison de propriétés. Il prend en charge les outils et le routage, il est petit et rapide, il est donc rapide pour la notation, et il est bien optimisé pour rag. C'est donc vraiment un bon ajustement pour ce flux particulier où je veux faire de l'analyse et du routage de requêtes, et je veux faire des vérifications en ligne et rag. Donc, voilà. Passons maintenant au codage. J'ai un notebook comme d'habitude. J'ai fait quelques pip install. Vous pouvez voir que ce n'est rien d'exotique. J'importe Langchain, Cohere. Je configure ma clé API Cohere. Maintenant, je vais simplement configurer ce projet Langchain dans lsmith, donc toutes mes traces vont dans ce projet, et j'ai activé le traçage. J'utilise donc lsmith ici. Nous allons parcourir ce flux et faisons la première chose. Construisons un magasin vectoriel. Je vais construire un magasin vectoriel en utilisant les embeddings Cohere avec Chroma, une base de données vectorielle open source qui s'exécute localement à partir de trois pages web différentes d'un article de blog que j'aime. Il concerne donc les agents, l'ingénierie de prompts et les attaques adverses. J'ai donc maintenant un récupérateur. Je peux exécuter le récupérateur, invoquer et poser une question sur la mémoire des agents, la mémoire des agents, et voilà. Nous obtenons des documents en retour. Voilà, nous avons maintenant un récupérateur. C'est maintenant que je vais intégrer Cohere. Je veux aussi un routeur. Regardez notre flux. La première étape est cette étape de routage. Ce que je vais faire, c'est que je vais trouver deux outils : un outil de recherche web et un outil de magasin vectoriel. Dans mon préambule, il sera simplement dit que vous êtes un expert en routage des questions des utilisateurs vers le magasin vectoriel et la recherche web. Voici la clé : je lui dis ce que contient le magasin vectoriel. Encore une fois, mon index, mon magasin vectoriel contient des agents, l'ingénierie de prompts, les attaques adverses. Je répète simplement cela ici : agents, adversaires, donc il sait ce qui est dans le magasin vectoriel. Utilisez-le pour les questions sur ces sujets, sinon utilisez la recherche web. C'est donc tout. J'utilise Command R ici. Maintenant, je vais lier ces outils au modèle et attacher le préambule, et j'ai un routeur LLM structuré. Testons-le un peu pour le mettre en bac à sable. Je peux invoquer ici ma chaîne. J'ai un prompt de routeur. Je le passe au routeur LLM structuré que j'ai défini ici. Et posons quelques questions différentes comme : qui les Bears vont-ils drafter dans la draft NFL avec des types de mémoire d'agent et Salut, comment allez-vous ? Je vais lancer cela et vous pouvez voir qu'il fait une recherche web, il va au magasin vectoriel, puis il retourne en fait ce faux. C'est donc intéressant. Ceci indique en fait si cela n'utilise aucun des deux outils. Pour cette requête particulière, la recherche web ou le magasin vectoriel était inapproprié, il dira simplement : je n'ai appelé aucun de ces outils. C'est donc intéressant, nous l'utiliserons plus tard. C'est donc mon outil de routage. La deuxième chose est mon notateur, et c'est là que je veux vous montrer quelque chose de vraiment agréable qui est généralement utile pour de nombreux problèmes différents que vous pourriez rencontrer. Voici ce que je fais. Je définis un modèle de données pour ma note. Essentiellement, noter les documents. Il aura ceci est un objet pantic. C'est juste essentiellement un score binaire ici. Le champ spécifié ici : les documents sont pertinents pour la question, oui ou non. J'ai un préambule : votre notateur évalue la pertinence des documents récupérés par rapport à une question d'utilisateur, bla bla bla. Et donc, essentiellement, donnez-lui un score binaire oui ou non. J'utilise Command R, mais voici le hic : j'utilise cette chose de sorties structurées et je lui passe mon modèle de données de notation de documents. C'est donc la chose clé. Nous pouvons tester cela dès maintenant également. Il retournera un objet basé sur le schéma que je lui donne, ce qui est extrêmement utile pour toutes sortes de cas d'utilisation. Et zoomons en arrière. Nous sommes donc en fait ici. C'est l'étape du notateur. Je veux contraindre la sortie à oui ou non. Je ne veux aucun préambule, rien, car la logique que je vais construire dans ce graphique nécessitera une réponse binaire oui ou non de cette arête particulière de notre graphique. C'est pourquoi cet outil de notation est vraiment utile. Et je pose une question simulée : types de mémoire d'agent. Je fais un récupérateur. Je fais une récupération de notre magasin vectoriel. J'obtiens le texte et je le teste. J'invoque notre chaîne de récupération et de notation avec la question, le texte du document, et c'est pertinent, comme nous nous y attendions. C'est donc bien. Mais encore une fois, regardons cela un peu plus attentivement. Ce qui se passe réellement sous le capot ici. Voici l'objet pantic que nous avons passé. Voici le document en question. Je fournis essentiellement, il convertit cet objet en schéma de fonction Cohere. Il le lie au LLM. Nous passons le document et la question. Il retourne un objet, essentiellement une chaîne JSON selon notre schéma pantic. C'est tout. Et ensuite, il va simplement le parser en un objet pantic que nous obtenons à la fin. C'est donc ce qui se passe sous le capot avec cette chose de sortie structurée, mais c'est extrêmement utile. Et vous verrez que nous allons l'utiliser à plusieurs endroits. Parce que nous voulons nous assurer que dans notre flux, nous avons trois étapes de notation différentes, et chaque fois que nous voulons contraindre la sortie à oui ou non, nous utiliserons cette sortie structurée plus d'une fois. Ceci est juste ma génération. C'est donc du bon vieux rag. Assurons-nous que cela fonctionne. J'utilise la chaîne rag, le prompt rag typique, encore une fois, j'utilise Cohere pour rag. C'est assez facile. Et oui, le morceau rag fonctionne, c'est tout à fait normal, rien de fou. Je vais trouver ce repli LLM. C'est essentiellement si vous avez vu une chaîne de routeur, si elle n'utilise pas d'outil, je veux me replier et simplement me replier sur le LLM. Je vais donc construire cela comme une petite chaîne ici. D'accord, c'est juste un repli. J'ai mon préambule, vous êtes un assistant, répondez à la question en fonction de vos connaissances internes. Donc, encore une fois, ce comportement de repli est ce que nous avons ici. Donc, ce que nous avons déjà fait, c'est que nous avons défini notre pièce de routeur. Nous avons défini notre récupération de base, notre magasin vectoriel. Nous avons déjà ici. Nous avons défini notre première logique ou notre vérification de note, et nous avons défini notre repli. Et nous parcourons simplement les pièces de notre graphique et définissons chaque pièce. Je vais avoir deux autres notateurs, et ils utiliseront la même chose que nous venons de parler, un modèle de données légèrement différent, je veux dire la même sortie, mais en fait juste un peu différent, prompt, et description. Dans ce cas, la réponse est ancrée dans les faits, oui ou non. C'est mon notateur d'hallucinations, et ensuite j'ai aussi un notateur de réponse. Et j'ai également exécuté un test sur chacun d'eux, et vous pouvez voir que j'obtiens des binaires. Ces objets ont un score binaire. C'est donc un objet pantic avec un score binaire, et c'est exactement ce que nous voulons. Cool. Et j'ai un outil de recherche, donc c'est vraiment bien. Nous avons en fait parcouru et nous avons présenté. J'ai un routeur, je l'ai testé. Nous avons un magasin vectoriel testé. Nous avons testé chacun de nos notateurs ici. Nous avons également testé la génération de simplement faire du rag. Nous avons donc un tas de pièces construites ici. Nous avons une pièce de repli. Nous avons la recherche web. Maintenant, la question est : comment puis-je les assembler dans ce type de flux ? Et pour cela, j'aime utiliser Lang graph. Nous parlerons un peu de Lang graph par rapport aux agents plus tard, mais je veux vous montrer pourquoi c'est vraiment facile à faire en utilisant Lang graph. Ce qui est bien, c'est que j'ai présenté toute ma logique ici. Nous l'avons testée individuellement, et maintenant tout ce que je vais faire, c'est que je vais d'abord présenter les parties de mon graphique. Ce que vous allez remarquer ici, c'est d'abord qu'il y a un état de graphique. Cet état représente les parties clés du graphique ou les objets clés dans le graphique que nous allons modifier. C'est donc essentiellement un graphique centré sur rag. Nous allons avoir une question, une génération et des documents. Ce sont vraiment les choses principales avec lesquelles nous allons travailler dans notre graphique. Ensuite, vous verrez quelque chose d'assez intuitif, je pense. Ce que vous allez voir, c'est que nous allons essentiellement parcourir ce flux, et pour chacun de ces petits cercles, nous allons simplement trouver une fonction. Et ces petits carrés ou ces, vous pouvez penser à chaque cercle comme un nœud, et chaque diamant comme une arête ou une arête conditionnelle. C'est donc ce que nous allons faire maintenant. Nous allons présenter tous nos nœuds et arêtes, et chacun d'eux sera simplement une fonction, et vous verrez comment nous le faisons maintenant. Je vais descendre ici. J'ai défini mon état de graphique. C'est ce qui sera modifié et propagé tout au long de mon graphique. Maintenant, tout ce que je vais faire, c'est que je vais simplement trouver une fonction pour chacun de ces nœuds. Laissez-moi aller côte à côte et vous montrer le diagramme, puis montrer les nœuds à côté. Voici le diagramme. Nous avons un nœud de récupération, il représente donc notre magasin vectoriel. Nous avons un nœud de repli, c'est cette partie. Nous avons un nœud de génération, donc cela va essentiellement faire notre rag. Vous pouvez le voir là. Nous avons un nœud de notation de documents, juste ici. Et nous avons un nœud de recherche web, donc c'est ici. Cool. Maintenant, c'est là que nous définissons les arêtes. Vous pouvez voir que nos arêtes sont les parties du graphique qui prennent différentes décisions. Cette arête de routage de question, cette arête conditionnelle de base, va essentiellement prendre une question d'entrée et décider où elle doit aller. Et c'est tout ce que nous faisons ici. Elle suit ce que nous avons fait en haut, où nous l'avons testé individuellement. Rappelez-vous, nous avons essentiellement simplement invoqué ce routeur de questions, il retourne notre source. Maintenant, rappelez-vous, si les appels d'outils n'étaient pas dans la source, nous faisons notre repli. Nous l'avons montré tout en haut. Rappelez-vous, si les appels d'outils ne sont pas dans la réponse, cette chose sera simplement fausse. Cela signifie que nous n'avons ni appelé la recherche web ni notre outil de récupération. Nous allons donc simplement nous replier. Oui, ici. Et c'est juste un, vous savez, une capture au cas où un outil pourrait prendre une décision. Mais le plus intéressant ici, c'est là que nous choisissons une source de données, essentiellement. C'est la sortie de notre appel d'outil. Nous allons simplement extraire le nom de l'outil. C'est donc la source de données. Et voici, si la source de données est la recherche web, je retourne la recherche web comme prochain nœud à atteindre. Sinon, si c'est un magasin vectoriel, nous retournons le magasin vectoriel comme prochain nœud à atteindre. Qu'est-ce que cette chose de recherche ? Rappelez-vous, nous avons défini ce nœud de recherche web ici. C'est tout. Nous allons simplement aller à ce nœud. Qu'est-ce que ce magasin vectoriel ? Vous verrez ci-dessous comment nous pouvons lier ces chaînes que nous retournons de l'arête conditionnelle au nœud auquel nous voulons aller. C'est vraiment tout. Même chose ici, décider de générer. Cela va combiner ces deux arêtes conditionnelles en une seule. Et essentiellement, il va faire : s'il n'y a pas de documents, donc essentiellement, si nous avons filtré tous nos documents de ce premier test ici, alors ce que nous allons faire, c'est que nous avons décidé que tous les documents ne sont pas pertinents pour la question, et nous allons revenir à la recherche web, exactement comme nous le montrons ici. C'est donc cette partie. Sinon, nous allons à générer. C'est donc cette partie. Encore une fois, dans ces arêtes conditionnelles, vous implémentez essentiellement la logique que vous voyez dans notre diagramme ici. C'est tout ce qui se passe. Et encore une fois, cela implémente simplement les deux dernières vérifications pour les hallucinations et la pertinence de la réponse. Et, oui, voici notre notateur d'hallucinations. Nous extrayons ensuite la note. S'il y a des hallucinations, oh désolé, dans ce cas, la note signifie en fait que la réponse est ancrée. Donc, nous disons que la réponse est en fait ancrée, et ensuite nous passons à l'étape suivante. Nous passons au test suivant. C'est tout ce que cela fait. Il encapsule simplement cette logique que nous implémentons ici dans notre graphique. C'est donc tout ce qui se passe. Et définissons toutes ces choses. Bien, nous avons tout cela. Maintenant, nous pouvons descendre un peu et nous pouvons récupérer. C'est en fait là que nous assemblons tout. Tout ce qui se passe ici, c'est que vous voyez que nous avons défini toutes ces fonctions ci-dessus. Nous les ajoutons simplement comme nœuds dans notre graphique ici, puis nous construisons notre graphique ici, essentiellement en présentant le flux ou la connectivité entre nos nœuds et nos arêtes. Vous pouvez donc regarder ce notebook pour étudier un peu en détail ce qui se passe, mais franchement, ce que j'aime faire ici, c'est généralement de dessiner un graphique comme nous l'avons fait ci-dessus, puis d'implémenter le flux logique ici dans votre graphique comme nœuds et arêtes, tout comme nous le faisons ici. C'est tout ce qui se passe. Encore une fois, nous avons notre point d'entrée, c'est le routeur. C'est la sortie, c'est essentiellement la direction, voici ce que le routeur produit, et voici le prochain nœud à atteindre. C'est tout. Et ensuite, pour chaque nœud, nous appliquons comme, nous disons, quel est le flux. Donc, la recherche web va à générer après, et la récupération va à noter les documents. La notation des documents est comme une arête conditionnelle. En fonction des résultats, nous faisons soit une recherche web, soit une génération. Et ensuite, notre deuxième, nous allons de générer à, essentiellement, cette notation de génération par rapport aux documents et à la question. En fonction de la sortie, nous avons soit des hallucinations, nous régénérons, nous avons constaté que la réponse n'est pas utile, nous revenons à la recherche web, ou nous terminons. Enfin, nous avons ce repli LLM, et c'est aussi si nous allons au repli, nous terminons. Donc, ce que vous voyez ici, c'est en fait le flux logique que nous présentons dans ce graphique correspond au diagramme que nous avons présenté ci-dessus. Je vais simplement les copier et je vais ensuite revenir au diagramme et le souligner un peu plus. Voici le flux que nous avons présenté à nouveau. Voici notre diagramme, et vous pouvez les regarder côte à côte et voir comment ils correspondent essentiellement. Voici donc notre diagramme de flux, allant essentiellement de l'analyse des requêtes, c'est cette chose, ce routage de question, et vous pouvez voir la recherche web, le magasin vectoriel, le repli LLM, le repli LLM, la recherche web, le magasin vectoriel. Ce sont donc les trois options qui peuvent sortir de cette arête conditionnelle. Et voici où nous nous connectons. Si nous allons à la recherche web, alors essentiellement, nous allons ensuite à générer. C'est donc tout ce flux. Maintenant, si nous allons à récupérer, alors nous allons noter. C'est tout. Et, vous savez, cela suit comme vous pouvez le voir ici. C'est vraiment tout. C'est donc juste bien de dessiner ces diagrammes d'abord, puis il est assez rapide d'implémenter chaque nœud et chaque arête simplement comme une fonction, puis de les assembler dans un graphique, tout comme je le montre ici. Et bien sûr, nous nous assurerons que ce code est public, vous pouvez donc l'utiliser comme référence. Alors voilà. Essayons maintenant quelques questions de test différentes. Comme, quel joueur les Bears vont-ils drafter dans la draft NFL, regardons cela, et ils devraient tout imprimer pendant que nous avançons. D'accord, c'est important. Route question. Il décide simplement de router vers la recherche web. C'est bien. Il ne va pas à notre magasin vectoriel. C'est un événement actuel sans rapport avec notre magasin vectoriel. Il va à la recherche web. Et ensuite, il va à générer. C'est donc ce à quoi nous nous attendions. Donc, essentiellement, la recherche web passe à la génération. Et nous vérifions les hallucinations. Les générations sont ancrées dans les documents. Nous vérifions la génération par rapport à la question. La génération répond à la question. Les Chicago Bears devraient drafter Caleb Williams. C'est exact, c'est le consensus. Donc, cool, ça marche. Maintenant, posons une question liée à notre magasin vectoriel. Quels sont les types de mémoire d'agent ? Lançons cela. Nous routons. D'accord, nous routons vers rag. Regardez la vitesse. C'est vraiment rapide. Nous parcourons donc essentiellement rapidement cette notation de documents, déterminons qu'ils sont tous pertinents. Nous allons à la décision de générer. Nous vérifions les hallucinations. Nous vérifions la réponse par rapport à la question. Et il existe plusieurs types de mémoire stockés dans le cerveau humain. La mémoire peut également être stockée dans des agents LLM. Vous avez une mémoire de flux d'agents LLM, un modèle de récupération et un mécanisme de réflexion. Il représente donc assez raisonnablement ce qui est capturé dans l'article de blog. Maintenant, laissez-moi vous montrer autre chose qui est assez agréable. Je peux aller sur Langsmith et aller dans mes projets. Nous avons créé ce nouveau projet Cohere Adaptive Rag au début, et tout est en fait enregistré là-bas. Tout ce que nous venons de faire. Je peux donc l'ouvrir et je peux en fait parcourir toutes les étapes de mon Lang graph. Voici notre étape de récupération. Voici notre étape de notation de documents, et nous pouvons auditer la notation elle-même. Nous l'avons examinée un par un précédemment, mais c'est en fait assez agréable. Nous pouvons auditer chaque note de document individuelle pour voir ce qui se passe. Nous pouvons essentiellement parcourir. Ceci sera l'un des autres notateurs ici. Oui, donc cela va en fait être la notation des hallucinations ici. Et ensuite, ce sera la notation de la réponse ici. C'est donc vraiment tout. Vous pouvez parcourir l'ensemble du graphique. Vous pouvez étudier ce qui se passe, ce qui est en fait très utile. Il semble que cela ait très bien fonctionné. Et enfin, posons une question qui devrait aller vers ce chemin de repli en bas, comme pas du tout liée à notre magasin vectoriel, événements actuels. Et oui, salut, je vais bien. C'est donc assez sympa. Nous avons vu en peut-être 15 minutes, nous avons construit à partir de zéro un système rag semi-sophistiqué qui a des propriétés d'agent. Nous l'avons fait dans Lang graph, nous l'avons fait avec Cohere Command R. Vous pouvez voir que c'est assez rapide. En fait, nous pouvons aller sur Langsmith et regarder. Donc, tout cela a pris 7 secondes. Ce n'est pas mal. Regardons le plus récent. Donc, cela prend une seconde. Le mécanisme de repli vers le LLM prend environ 1 seconde. Regardons ici. Donc, 6 secondes pour le Lang graph initial. Ce n'est pas mal du tout. C'est assez rapide. Il effectue plusieurs vérifications. Nous faisons du routage, puis nous avons un tas de bons comportements de repli et de vérifications en ligne pour la pertinence, les hallucinations et l'utilité de la réponse. C'est donc assez bien. Je vous encourage vraiment à jouer avec le notebook. Command R est une très bonne option pour cela en raison de son utilisation d'outils, de son routage, de sa petite taille et de sa rapidité, et il est vraiment bon pour les rag. C'est une très bonne option pour les flux comme celui-ci, et je pense que vous verrez de plus en plus ce type de rag adaptatif ou auto-réfléchi, juste parce que c'est quelque chose dont de nombreux systèmes peuvent bénéficier, comme de nombreux systèmes rag de production n'ont pas nécessairement de replis, en fonction par exemple, vous savez, si les documents récupérés ne sont pas pertinents, si la réponse contient des hallucinations, et ainsi de suite. Donc, cette opportunité d'appliquer des vérifications en ligne avec rag est un très bon thème. Je pense que nous verrons de plus en plus cela, surtout à mesure que l'inférence des modèles devient de plus en plus rapide et que ces vérifications deviennent de moins en moins chères à faire dans la boucle d'inférence. Maintenant, comme dernière chose, je veux aborder le point de savoir si nous avons montré ces trucs de Lang graph, qu'en est-il des agents ? Comment pensez-vous aux agents par rapport à Lang graph ? Et je pense que la façon dont j'aime cadrer cela, c'est que Lang graph est vraiment bon pour les flux que vous avez définis de manière très claire, qui n'ont pas d'ouverture, mais dans ce cas, nous connaissons les étapes que nous voulons suivre à chaque fois. Nous voulons faire une analyse des requêtes, un routage, puis nous voulons faire trois étapes de notation, et c'est tout. Lang graph est vraiment bon pour construire des flux très fiables. C'est un peu comme mettre un agent sur des rails, et c'est vraiment bien. C'est moins flexible mais très fiable. Et vous pouvez donc utiliser des modèles plus petits et plus rapides avec Lang graph. C'est donc ce que j'aime. Nous avons vu ici Command R, un modèle de 35 milliards de paramètres fonctionne très bien avec Lang graph, assez rapidement. Nous avons pu implémenter un flux rag assez sophistiqué très rapidement en 15 minutes, en temps de l'ordre de moins de, vous savez, environ 5 à 6 secondes. C'est donc plutôt bien. Maintenant, qu'en est-il des agents ? Je pense que les agents entrent en jeu lorsque vous voulez des flux plus flexibles. Vous ne voulez pas nécessairement suivre un schéma défini a priori. Vous voulez qu'un agent soit capable de raisonner et de prendre des décisions ouvertes, ce qui est intéressant pour certains problèmes de planification à long terme. Les agents sont vraiment intéressants. Le hic, c'est que la fiabilité est un peu moins bonne avec les agents. Et donc, c'est une grande question que beaucoup de gens soulèvent, et c'est là que les LLM plus grands entrent en jeu avec, vous savez, il y a eu beaucoup de questions sur l'utilisation de petits LLM, même de modèles open source, avec des agents, et la fiabilité est un problème continu, alors que j'ai pu exécuter ces types de Lang graph avec, avec, par exemple, mraw, ou Command R est en fait à poids ouvert, vous pouvez l'exécuter localement. J'ai pu les exécuter de manière très reproductible avec des modèles open source sur mon ordinateur portable. Donc, je pense qu'il y a un compromis. Et en fait, il y a un nouveau modèle Cohere qui sort, je crois Command R plus, qui est un modèle plus grand, donc il est probablement plus adapté aux cas d'utilisation plus ouverts et plus agents. Et il y a en fait une nouvelle intégration avec Langchain qui prend en charge les agents Cohere, ce qui est assez agréable. Je pense donc qu'il vaut la peine d'expérimenter pour certains problèmes et flux de travail. Vous pourriez avoir besoin d'un raisonnement plus ouvert, auquel cas utilisez un agent avec un modèle plus grand. Sinon, vous pouvez utiliser Lang graph pour un flux plus fiable mais contraint, et il peut également utiliser des modèles plus petits, des LLM plus rapides. Ce sont donc quelques-uns des compromis à garder à l'esprit. Mais de toute façon, je vous encourage à jouer avec le notebook, à explorer par vous-même. Je pense que Command R est un très bon modèle. J'ai également expérimenté son exécution localement avec AMA. Actuellement, le modèle quantifié est comme, quantifié en deux bits, il fait environ 13 milliards, ou quelque chose comme ça. Oui, 13 Go. C'est un peu trop grand pour s'exécuter rapidement localement pour moi. L'inférence pour des choses comme rag est de l'ordre de 30 secondes. Encore une fois, ce n'est pas idéal pour une démo en direct, mais cela fonctionne. Il est disponible sur llama, donc je vous encourage à jouer avec cela. J'ai un Mac M2 32 Go. Donc, si vous avez une machine plus grande, cela pourrait absolument valoir la peine de travailler localement. Je vous encourage donc à jouer avec cela. De toute façon, j'espère que cela a été utile et intéressant. Je pense que c'est un article cool, un flux cool. Cohere Command R est une bonne option pour ce type de routage. C'est rapide, bon avec Lang graph, bon pour rag, bon pour l'utilisation d'outils. Donc, jetez un coup d'œil et répondez à tout commentaire dans les commentaires. Merci. Salut, c'est Lance de Langchain. C'est une conférence que j'ai donnée lors de deux récentes rencontres à San Francisco, intitulée "Is Rag Really Dead ?". Et j'ai pensé que, comme beaucoup de gens n'ont pas pu assister à ces rencontres, je vais simplement enregistrer cela et le mettre sur YouTube pour voir si cela intéresse les gens. Donc, nous reconnaissons tous que les fenêtres contextuelles des LLM deviennent de plus en plus grandes. Sur l'axe des x, vous pouvez voir les jetons utilisés dans le pré-entraînement, qui deviennent bien sûr de plus en plus grands également. Les modèles propriétaires se situent quelque part dans le régime des deux billions de jetons, nous ne savons pas exactement où ils se situent, et nous avons des modèles plus petits comme 52 entraînés sur beaucoup moins de jetons. Mais ce qui est vraiment notable, c'est sur l'axe des y. Il y a environ un an, les modèles de pointe étaient de l'ordre de 4 000 à 8 000 jetons, ce qui représente des dizaines de pages. Nous avons vu Claude 2 sortir avec le modèle de 200 000 jetons plus tôt, je pense que c'était l'année dernière. GPT-4 128 000 jetons, ce sont des centaines de pages. Et maintenant, nous voyons Claude 3 et Gemini sortir avec des modèles d'un million de jetons, ce sont des centaines à des milliers de pages. En raison de ce phénomène, les gens se sont interrogés : le rag est-il mort ? Si vous pouvez mettre des milliers de pages dans la fenêtre contextuelle d'un LLM, pourquoi avez-vous besoin d'un système de récupération ? C'est une bonne question qui a suscité beaucoup de débats intéressants sur Twitter. Et c'est peut-être d'abord juste pour définir ce qu'est le rag. Le rag est vraiment le processus de raisonnement et de récupération sur des morceaux d'informations qui ont été récupérés. Il commence par des documents qui sont indexés, ils sont récupérables par un certain mécanisme, généralement une sorte de recherche de similarité sémantique ou une recherche par mots-clés, d'autres mécanismes. Les documents récupérés doivent ensuite être transmis à un LLM, et le LLM raisonne à leur sujet pour ancrer la réponse à la question dans le document récupéré. C'est donc le flux général, mais le point important à souligner est qu'il s'agit généralement de plusieurs documents et qu'il implique une forme de raisonnement. L'une des questions que j'ai posées récemment est : si les LLM avec de longs contextes peuvent remplacer le rag, ils devraient être capables d'effectuer une récupération et un raisonnement multi-aiguilles à partir de leur propre contexte très efficacement. J'ai donc fait équipe avec Greg Cameron pour tester cela, et il avait déjà effectué des analyses de "needle in the haystack" (aiguille dans la botte de foin) très intéressantes, axées sur des faits uniques appelés aiguilles placées dans une botte de foin d'essais de Paul Graham. J'ai donc étendu cela pour refléter le cas d'utilisation du rag ou le contexte du rag, où j'ai pris plusieurs faits. Je l'appelle multi-aiguille. Je me suis inspiré d'un défi amusant "needle in the haystack" publié par Anthropic, où ils ont placé des ingrédients de pizza dans le contexte et ont demandé au LLM de récupérer cette combinaison d'ingrédients de pizza. J'ai fait une version de cela, et j'ai essentiellement divisé les ingrédients de pizza en trois aiguilles différentes et placé ces trois ingrédients à différents endroits dans le contexte, puis demandé au LLM de récupérer ces trois ingrédients du contexte. Encore une fois, la configuration est la question : quels sont les ingrédients secrets nécessaires pour préparer une pizza parfaite ? Les aiguilles sont les ingrédients : figues, prosciutto, fromage de chèvre. Je les place dans le contexte à des intervalles spécifiés. La façon dont ce test fonctionne est que vous pouvez essentiellement définir le pourcentage du contexte dans lequel placer la première aiguille, et les deux autres sont placées à intervalles à peu près égaux dans le contexte restant après la première. C'est donc la façon dont le test est configuré. Tout est open source d'ailleurs, le lien est ci-dessous. Les aiguilles sont placées, vous posez une question, vous invitez le LLM avec ce contexte et la question, puis il produit la réponse. Et maintenant, le framework notera la réponse, à la fois : est-ce que tous les ingrédients spécifiés sont présents dans la réponse ? Et deuxièmement, sinon, lesquels sont manquants ? J'ai exécuté un tas d'analyses sur cela avec GPT-4 et j'ai obtenu des résultats intéressants. Vous pouvez donc voir sur la gauche ce que cela regarde : différents nombres d'aiguilles placées dans une fenêtre contextuelle de 120 000 jetons pour GPT-4, et je demande à GPT-4 de récupérer une, trois ou dix aiguilles. Je lui demande également de raisonner sur ces aiguilles, c'est ce que vous pouvez voir dans ces barres rouges. Le vert, c'est juste récupérer les ingrédients, le rouge, c'est le raisonnement. Et le défi de raisonnement ici est simplement de retourner la première lettre de chaque ingrédient. Nous trouvons donc essentiellement deux choses : la performance ou le pourcentage d'aiguilles récupérées diminue par rapport au nombre d'aiguilles, ce qui est assez intuitif. Vous placez plus de faits, la performance s'aggrave. Mais cela s'aggrave aussi si vous lui demandez de raisonner. Si vous dites, retournez simplement les aiguilles, cela fonctionne un peu mieux que si vous dites, retournez les aiguilles et dites-moi la première lettre. Donc, vous superposez le raisonnement. C'est la première observation : plus de faits, c'est plus difficile, et le raisonnement, c'est plus difficile que la simple récupération. Maintenant, la deuxième question que nous posons est : où sont réellement présentes les aiguilles que nous manquons ? Vous pouvez voir sur la droite les résultats qui nous indiquent quelles aiguilles spécifiques le modèle ne parvient pas à récupérer. Nous pouvons donc voir qu'à mesure que vous passez de 1 000 jetons à 120 000 jetons sur l'axe des x, et que vous regardez l'aiguille un placée au début du document à l'aiguille dix placée à la fin, à une longueur de contexte de 1 000 jetons, vous pouvez toutes les récupérer. Encore une fois, cela correspond à ce que nous voyons ici. Petit, en fait, en fait, tout ce que je regarde, c'est 120 000 jetons, donc ce n'est vraiment pas le but. Le but est en fait un contexte plus petit, une meilleure récupération. C'est donc le point un. À mesure que j'augmente la fenêtre contextuelle, je constate qu'il y a une augmentation de l'échec à récupérer les aiguilles, ce que vous pouvez voir en rouge ici, vers le début du document. Et c'est donc un résultat intéressant. Et cela correspond en fait à ce que Greg a vu avec le cas de l'aiguille unique également. La façon de penser à cela est qu'il semble que, si vous lisez un livre, par exemple, et que je vous pose une question sur le premier chapitre, vous pourriez l'avoir oublié. Le même phénomène semble se produire ici avec la récupération, où les aiguilles vers le début.

du contexte sont sont un peu oubliés ou ne sont pas bien récupérés par rapport à ceux de la fin, donc c'est un effet que nous observons avec gbd4, il a été reproduit pas mal de fois, donc j'ai effectué neuf essais différents ici, Greg a également vu cela à plusieurs reprises avec une seule aiguille, donc cela semble être un résultat assez cohérent et il y a un point intéressant, j'ai mis cela sur Twitter et un certain nombre de personnes, euh, vous savez, ont répondu et quelqu'un m'a envoyé cet article qui est assez intéressant et il mentionne le biais de récence comme une raison possible, donc les jetons les plus informatifs pour prédire le jeton suivant, euh, vous savez, sont présents près ou récents à l'endroit où vous faites votre génération, et donc il y a un biais pour prêter attention aux jetons récents, ce qui n'est évidemment pas idéal pour le problème de récupération, comme nous l'avons vu ici, donc encore une fois, les résultats nous montrent que le raisonnement est un peu plus difficile que la récupération, plus d'aiguilles est plus difficile et les aiguilles vers le début de votre contexte sont plus difficiles à récupérer que vers la fin, ce sont trois observations principales à partir de cela, et cela est peut-être effectivement dû à ce biais de récence, donc dans l'ensemble, ce que cela vous dit, c'est qu'il faut se méfier du simple bourrage de contexte dans de longs contextes, il n'y a pas de garanties de récupération, et il y a aussi des résultats récents qui sont sortis, en fait, juste aujourd'hui, suggérant que la seule aiguille peut être trompeusement facile, euh, vous savez, il n'y a pas de raisonnement, il récupère une seule aiguille, euh, et aussi ces gars, j'ai montré ce tweet ici, montrent que dans beaucoup de ces défis d'aiguille et de Haack, y compris le mien, les faits que nous recherchons sont très différents de, euh, de la pile d'essais de Paul Graham en arrière-plan, et donc cela peut être une sorte d'artefact intéressant, ils notent qu'en effet, si l'aiguille est plus subtile, les récupérations sont pires, donc je pense qu'en gros, lorsque vous voyez ces analyses d'aiguille et de hyack très performantes présentées par les fournisseurs de modèles, vous devriez être sceptique, vous ne devriez pas nécessairement supposer que vous obtiendrez une récupération de haute qualité de ces LLM à long contexte, pour de nombreuses raisons, vous devez penser à la récupération de plusieurs faits, vous devez penser au raisonnement au-dessus de la récupération, vous devez penser à la subtilité de la récupération par rapport au contexte d'arrière-plan, car pour beaucoup de ces défis d'aiguille et de Haack, il s'agit d'une seule aiguille sans raisonnement et l'aiguille elle-même est très différente de l'arrière-plan, donc de toute façon, tout cela peut rendre le défi un peu plus facile qu'un scénario réel de récupération de faits, donc je veux juste exposer ces notes de prudence, mais vous savez, je pense qu'il est juste de dire que cela s'améliorera certainement et je pense qu'il est également juste de dire que le RAG changera et que c'est juste comme une blague presque pas très bonne, mais Frank Zappa, un musicien, a dit que le jazz n'est pas mort, il sent juste bizarre, vous savez, je pense que c'est pareil pour le RAG, le RAG n'est pas mort mais il changera, je pense que c'est le point clé ici, donc juste pour faire suite à cela, le RAG d'aujourd'hui se concentre sur la récupération précise de morceaux de documents pertinents, il est donc très axé sur la prise de documents, leur découpage d'une manière particulière, souvent en utilisant des méthodes de découpage très idiosyncratiques, des choses comme la taille des morceaux sont choisies presque arbitrairement, les intégrer, les stocker dans un index, prendre une question, l'intégrer, faire une recherche de similarité K&N pour récupérer les morceaux pertinents, vous définissez souvent un paramètre k qui est le nombre de morceaux que vous récupérez, vous effectuez souvent un filtrage ou un post-traitement sur les morceaux récupérés et vous ancrez votre réponse dans ces morceaux récupérés, il est donc très axé sur la récupération précise des bons morceaux. Dans un monde où vous avez des modèles à très long contexte, je pense qu'il y a une question légitime à se poser : est-ce vraiment l'approche la plus raisonnable ? Donc, à gauche ici, vous pouvez voir cette notion plus proche d'aujourd'hui où j'ai besoin du morceau pertinent exact, vous pouvez risquer une ingénierie excessive, vous pouvez avoir une complexité plus élevée, une sensibilité à ces paramètres étranges comme la taille des morceaux, k, et vous pouvez en effet souffrir d'un rappel plus faible car vous ne choisissez que des morceaux très précis, vous êtes lié à des modèles d'intégration très particuliers, donc, vous savez, je pense qu'à l'avenir, à mesure que les modèles à long contexte s'améliorent de plus en plus, il y a certainement des questions que vous devriez certainement remettre en question le paradigme actuel de RAG avec découpage très précis, mais d'un autre côté, je pense que le simple fait de jeter tous vos documents dans le contexte ne sera probablement pas non plus l'approche préférée, vous souffrirez d'une latence plus élevée, d'une utilisation de jetons plus élevée, je devrais noter qu'aujourd'hui, un GPD 4 de 100 000 jetons coûte 1 $ par génération, j'ai dépensé beaucoup d'argent sur le compte de LangChain sur cette analyse multiple, je ne veux pas dire à Harrison combien j'ai dépensé, donc ce n'est pas bon, n'est-ce pas ? Vous ne pouvez pas auditer la récupération, et la sécurité et l'authentification sont des problèmes si, par exemple, vous avez besoin que différents utilisateurs aient un accès différent à certains types de documents ou de morceaux récupérés dans le cas du bourrage de contexte, vous ne pouvez pas gérer la sécurité aussi facilement, donc il y a probablement un régime pré-optimal quelque part au milieu, et, vous savez, j'ai mis cela sur Twitter, je pense qu'il y a des points raisonnables soulevés, je pense que, vous savez, cette inclusion au niveau du document est probablement assez saine, les documents sont des morceaux de contexte autonomes, donc, vous savez, qu'en est-il du RAG centré sur le document, donc pas de découpage, mais juste comme opérer sur le contexte de documents complets, donc, vous savez, si vous pensez à l'avenir au paradigme RAG qui est centré sur le document, vous avez toujours le problème de prendre une question d'entrée, de la router vers le bon document, cela ne change pas, donc je pense que beaucoup de méthodes que nous pensons pour l'analyse des requêtes, prendre une question d'entrée, la réécrire d'une certaine manière pour optimiser la récupération, des choses comme le routage, prendre une question, la router vers la bonne base de données, qu'il s'agisse d'une base de données relationnelle, d'une base de données graphique, d'un magasin vectoriel, et des méthodes de construction de requêtes, par exemple, texte vers SQL, texte vers Cipher pour les graphes, ou texte vers même des filtres de métadonnées pour les magasins vectoriels, tout cela reste pertinent dans le monde où vous avez des LLM à long contexte, vous n'allez probablement pas déverser toute votre base de données SQL et la donner au LLM, vous aurez toujours des requêtes SQL, vous aurez toujours des requêtes graphiques, vous pourriez être plus permissif avec ce que vous extrayez, mais il est toujours très raisonnable de stocker la majorité de vos données structurées sous ces formes, de même avec des données non structurées comme des documents, comme nous l'avons dit auparavant, il est toujours probablement judicieux de stocker les documents indépendamment, mais simplement de viser à récupérer des documents complets plutôt que de se soucier de ces paramètres idiosyncratiques comme la taille des morceaux, et dans ce sens, il existe de nombreuses méthodes, nous en avons fait quelques-unes qui sont assez bien optimisées pour la récupération de documents, donc une que je veux signaler est ce que nous appelons l'indexation multi-représentations, et il y a en fait un très bon article à ce sujet appelé dense x retriever ou indexation de proposition, mais le point principal est simplement ceci : ce que vous faites, c'est que vous prenez votre document original, vous produisez une représentation comme un résumé de ce document, vous indexez ce résumé, n'est-ce pas, et ensuite, au moment de la récupération, vous posez votre question, vous intégrez votre question et vous utilisez simplement un résumé de haut niveau pour récupérer le bon document, vous passez le document complet au LLM pour une génération finale, donc c'est une sorte d'astuce où vous n'avez pas à vous soucier d'intégrer des documents complets dans ce cas particulier, vous pouvez utiliser des invites de résumé très descriptives pour construire des résumés descriptifs, et le problème que vous résolvez ici est simplement de me donner le bon document, c'est un problème plus facile que de me donner le bon morceau, donc c'est une approche intéressante, il existe également différentes variantes que je partage ci-dessous, l'une s'appelle le récupérateur de documents parents, où vous pourriez utiliser en principe si vous vouliez des morceaux plus petits, mais ensuite retourner des documents complets, mais de toute façon, le point est de préserver des documents complets pour la génération, mais d'utiliser des représentations comme des résumés ou des morceaux pour la récupération, donc c'est une sorte d'approche, je pense qu'elle est vraiment intéressante. L'approche deux est cette idée de raptor, c'est un article cool sorti de Stamper il y a quelque temps, et cela résout le problème de ce qui se passe si pour certaines questions j'ai besoin d'intégrer des informations de nombreux documents, donc ce que fait cette approche, c'est qu'elle prend des documents et les intègre et les regroupe, puis elle résume chaque groupe, et elle le fait récursivement jusqu'à obtenir un seul résumé de très haut niveau pour l'ensemble du corpus de documents, et ce qu'ils font, c'est qu'ils prennent cette sorte de hiérarchie d'abstraction, pour ainsi dire, de différentes synthèses de documents, et ils indexent tout cela et l'utilisent dans la récupération, et donc en gros, si vous avez une question qui puise des informations dans de nombreux documents, vous avez probablement un résumé présent et indexé qui capture cette réponse, donc c'est une astuce sympa pour consolider les informations entre les documents. Leur article rapporte en fait que dans leur cas, les documents ou les feuilles sont en fait des morceaux ou des tranches de documents, mais j'ai en fait montré, j'ai une vidéo à ce sujet et un notebook qui montrent que cela fonctionne aussi avec des documents complets, et cela, et je fais la transition pour faire cela, vous devez penser aux modèles d'intégration à long contexte, car vous intégrez des documents complets, et c'est une chose vraiment intéressante à suivre. La recherche hazy a publié un très bon article de blog à ce sujet en utilisant ce que le Monch mixer, c'est une sorte de nouvelle architecture qui tend vers un contexte plus long, ils ont un modèle d'intégration de 32 000 jetons qui est disponible sur together AI, absolument intéressant à expérimenter, je pense que c'est une tendance vraiment intéressante, donc les intégrations à long contexte s'accordent très bien avec ce genre d'idée, vous prenez des documents complets, vous les intégrez en utilisant par exemple des modèles d'intégration à long contexte, et vous pouvez construire ces arbres de résumé de documents très efficacement, donc je pense que c'est une autre astuce sympa pour travailler avec des documents complets dans le régime des LLM à long contexte. Une autre chose que je noterai, je pense qu'il y aura aussi un mouvement loin du RAG en une seule étape, alors qu'aujourd'hui le RAG, nous avons typiquement, nous découpons des documents, nous les intégrons, nous les stockons dans un index, nous effectuons la récupération, puis la génération, mais il n'y a aucune raison pour laquelle vous ne devriez pas faire de raisonnement au-dessus de la génération ou de raisonnement au-dessus de la récupération et de feedback s'il y a des erreurs, donc il y a un très bon article appelé selfrag qui rapporte cela, nous l'avons implémenté en utilisant LangGraph, cela fonctionne très bien, et l'idée est simplement de noter la pertinence de vos documents par rapport à votre question en premier, s'ils ne sont pas pertinents, vous réécrivez la question, vous pouvez faire beaucoup de choses dans ce cas, nous faisons de la réécriture de questions et nous essayons à nouveau, nous évaluons également les hallucinations, nous évaluons la pertinence de la réponse, mais de toute façon, cela fait passer le RAG d'un paradigme en une seule étape à un flux cyclique dans lequel vous effectuez diverses évaluations en aval, et tout cela est pertinent dans le régime des LLM à long contexte également, en fait, vous devriez absolument profiter par exemple des LLM de plus en plus rapides et performants pour faire cette évaluation. Des frameworks comme LangGraph vous permettent de construire ces flux qui vous permettent d'avoir un pipeline RAG plus performant et auto-réfléchi. J'ai reçu beaucoup de questions sur la latence ici, et je suis tout à fait d'accord, il y a un compromis entre la performance, la précision et la latence qui est présent ici. Je pense que la vraie réponse est que vous pouvez choisir d'utiliser des modèles très rapides, par exemple, comme Grok, où l'on voit que le GPT-35 Turbo est très rapide, ce sont des défis d'évaluation assez faciles, donc vous pouvez utiliser des LLM très, très rapides pour faire l'évaluation et par exemple, vous pouvez également limiter cela à un seul tour d'itération cyclique, donc vous pouvez limiter la latence de cette façon également. Donc, de toute façon, je pense que c'est une approche vraiment cool, toujours pertinente dans le monde alors que nous nous dirigeons vers un contexte plus long, donc c'est comme construire du raisonnement au-dessus du RAG dans les étapes de génération et de récupération. Et un point connexe, l'un des défis avec le RAG est que votre index, par exemple, vous pouvez avoir une question qui demande quelque chose qui est en dehors de la portée de votre index, et c'est toujours un problème. Donc, un article vraiment cool appelé C-RAG ou Corrective RAG est sorti il y a quelques mois, qui fait essentiellement de l'évaluation comme nous en avons parlé auparavant, et si les documents ne sont pas pertinents, vous lancez une recherche sur le web et vous retournez essentiellement les résultats de la recherche au LLM pour la génération finale, donc c'est une bonne solution de repli dans les cas où votre question est hors du domaine de votre récupérateur. Donc, encore une fois, une astuce sympa pour superposer le raisonnement sur le RAG, je pense que cette tendance continue, car vous savez, cela rend les systèmes RAG plus performants et moins fragiles face aux questions hors domaine. Donc, vous savez, c'est une autre idée sympa. Cette approche particulière, nous avons également montré qu'elle fonctionne très bien avec les modèles open source, donc je l'ai exécutée avec Mraw 7B, elle peut fonctionner localement sur mon ordinateur portable en utilisant un Llama, donc encore une fois, une approche vraiment sympa, je vous encourage à vous y intéresser, et tout cela est indépendant de la longueur du contexte du LLM, ce sont des raisonnements que vous pouvez ajouter à l'étape de récupération qui peuvent améliorer les performances globales. Et donc l'image globale ressemble à ceci, où, vous savez, je pense que le problème de router votre question vers la bonne base de données et/ou vers le bon document reste en place, l'analyse des requêtes est toujours très pertinente, le routage est toujours pertinent, la construction des requêtes est toujours pertinente, dans le régime à long contexte, il y a moins d'accent sur le découpage des documents, travailler avec des documents complets est probablement plus optimal, il existe des astuces intelligentes pour l'indexation des documents comme l'indexation multi-représentations dont nous avons parlé, l'indexation hiérarchique utilisant Raptor dont nous avons parlé sont également deux idées intéressantes pour l'indexation centrée sur le document, et ensuite le raisonnement et la génération post-récupération, sur la récupération elle-même, pour évaluer les générations elles-mêmes, vérifier les hallucinations, tout cela sont des parties intéressantes et pertinentes d'un système RAG dont je pense que nous verrons de plus en plus à mesure que nous nous éloignons d'un paradigme de réponse par invite plus naïf vers un paradigme de flux, nous le voyons déjà dans la génération de code et cela va probablement se répercuter sur le RAG également, où nous construisons des systèmes RAG qui ont un flux cyclique, opèrent sur des documents, utilisent des LLM à long contexte, et utilisent toujours le routage et l'analyse des requêtes, donc le raisonnement pré-récupération, le raisonnement post-récupération. Donc, de toute façon, c'était à peu près mon exposé, et oui, n'hésitez pas à laisser des commentaires sur la vidéo et j'essaierai de répondre à toutes les questions, mais, oui, c'est à peu près tout, merci.