{"id":463,"date":"2026-10-07T14:55:43","date_gmt":"2026-10-07T12:55:43","guid":{"rendered":"https:\/\/www.ranksignal.fr\/blog\/?p=463"},"modified":"2026-10-14T17:33:01","modified_gmt":"2026-10-14T15:33:01","slug":"qu-est-ce-qu-un-retriever","status":"publish","type":"post","link":"https:\/\/www.ranksignal.fr\/blog\/rag\/qu-est-ce-qu-un-retriever\/","title":{"rendered":"Qu&rsquo;est-ce qu&rsquo;un retriever ? Le moteur de recherche du RAG"},"content":{"rendered":"<p><\/p>\n<p>Quand tu appuies sur Ctrl+F dans ton navigateur, tu fais d\u00e9j\u00e0 de la recherche d&rsquo;information : la page est la collection de documents, le mot que tu tapes est la requ\u00eate, et le navigateur surligne ce qui correspond exactement. Un retriever fait le m\u00eame m\u00e9tier, avec deux diff\u00e9rences de taille : il ne fouille pas une page mais toute une base, et il doit retrouver ce que tu veux dire, pas seulement les lettres que tu tapes.<\/p>\n<p>\u00c0 la fin de cet article, tu sauras le d\u00e9finir en une phrase, distinguer ses grandes familles, lire quelques lignes de code qui en construisent un, et deviner ce qu&rsquo;il ne faut surtout pas lui demander.<\/p>\n<p><strong>Sommaire<\/strong><\/p>\n<ul>\n<li><a href=\"#l-essentiel-en-30-secondes\">L&rsquo;essentiel en 30 secondes<\/a><\/li>\n<li><a href=\"#une-fonction-une-requete-en-entree-un-classement-en-sortie\">Une fonction : une requ\u00eate en entr\u00e9e, un classement en sortie<\/a><\/li>\n<li><a href=\"#du-ctrl-f-au-sens-les-familles-de-retrievers\">Du Ctrl+F au sens : les familles de retrievers<\/a><\/li>\n<li><a href=\"#sous-le-capot-un-encodeur-ou-deux\">Sous le capot : un encodeur ou deux ?<\/a><\/li>\n<li><a href=\"#voir-un-retriever-dense-tourner\">Voir un retriever dense tourner<\/a><\/li>\n<li><a href=\"#quand-la-question-ne-ressemble-pas-aux-documents\">Quand la question ne ressemble pas aux documents<\/a><\/li>\n<li><a href=\"#un-retriever-ne-travaille-pas-seul\">Un retriever ne travaille pas seul<\/a><\/li>\n<li><a href=\"#ce-qu-un-retriever-ne-fait-pas\">Ce qu&rsquo;un retriever ne fait pas<\/a><\/li>\n<\/ul>\n<h2 id=\"l-essentiel-en-30-secondes\">L&rsquo;essentiel en 30 secondes<\/h2>\n<ul>\n<li>Un retriever est <strong>une fonction de recherche<\/strong> : il re\u00e7oit une requ\u00eate et renvoie une liste de documents class\u00e9s par pertinence.<\/li>\n<li>On peut le fabriquer <strong>avec des mots exacts<\/strong> (TF-IDF, BM25), avec des vecteurs de sens (embeddings), ou avec un m\u00e9lange des deux : le cadre du <a href=\"\/blog\/rag\/a-quoi-ressemble-un-systeme-rag\/\" title=\"RAG\">RAG<\/a> n&rsquo;impose aucune m\u00e9thode.<\/li>\n<li>Les <strong>embeddings contextuels<\/strong> sont les plus utilis\u00e9s en RAG, malgr\u00e9 un co\u00fbt de calcul plus \u00e9lev\u00e9 que le comptage de mots.<\/li>\n<li>Il travaille rarement seul : <strong>reformulation de la requ\u00eate, recherche hybride, reranking<\/strong> l&rsquo;entourent avant et apr\u00e8s.<\/li>\n<li>Sa notion de \u00ab proche \u00bb vient de ses donn\u00e9es d&rsquo;entra\u00eenement : <strong>proche ne veut pas dire pertinent<\/strong>, et un retriever g\u00e9n\u00e9raliste peut se tromper de domaine.<\/li>\n<\/ul>\n<h2 id=\"une-fonction-une-requete-en-entree-un-classement-en-sortie\">Une fonction : une requ\u00eate en entr\u00e9e, un classement en sortie<\/h2>\n<p>En recherche d&rsquo;information, r\u00e9cup\u00e9rer consiste \u00e0 retrouver les documents qui correspondent le mieux \u00e0 une requ\u00eate. Le <strong>retriever<\/strong> est le composant qui remplit ce r\u00f4le : une requ\u00eate entre, une liste de documents class\u00e9s par pertinence sort.<\/p>\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;6adef46bb5ce6&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"6adef46bb5ce6\" class=\"wp-block-image size-large wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2560\" height=\"1071\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_role_retriever-scaled.png\" alt=\"Une requ\u00eate utilisateur entre dans un bloc \u00ab Retriever (recherche) \u00bb qui produit une liste de r\u00e9sultats class\u00e9s du rang 1, le plus pertinent, au rang n, le moins pertinent\" class=\"wp-image-456\" srcset=\"https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_role_retriever-scaled.png 2560w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_role_retriever-300x125.png 300w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_role_retriever-1024x428.png 1024w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_role_retriever-768x321.png 768w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_role_retriever-1536x642.png 1536w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_role_retriever-2048x856.png 2048w\" sizes=\"auto, (max-width: 2560px) 100vw, 2560px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><figcaption class=\"wp-element-caption\">Regarde la sortie, \u00e0 droite du sch\u00e9ma : ce n&rsquo;est pas une r\u00e9ponse, c&rsquo;est une file d&rsquo;attente ordonn\u00e9e. Et c&rsquo;est l&rsquo;ordre qui d\u00e9cide de ce qui passe la porte, puisqu&rsquo;on ne garde ensuite que les <em>k<\/em> premiers du classement, le \u00ab top-k \u00bb.<\/figcaption><\/figure>\n\n\n<p>Ce que le retriever fouille d\u00e9pend de la collection : des documents entiers, ou des passages \u00e0 l&rsquo;int\u00e9rieur de documents, souvent accompagn\u00e9s de m\u00e9tadonn\u00e9es (taille, sujet, mots-cl\u00e9s\u2026). Retrouver le passage qui permet de r\u00e9pondre est la t\u00e2che centrale des syst\u00e8mes de questions-r\u00e9ponses. La collection est index\u00e9e une fois pour toutes ; chaque requ\u00eate est convertie en vecteur, et ce vecteur sert \u00e0 rep\u00e9rer les documents les plus pertinents, rendus dans l&rsquo;ordre.<\/p>\n<p>Une cons\u00e9quence m\u00e9rite d&rsquo;\u00eatre dite tout de suite : le mot \u00ab retriever \u00bb d\u00e9signe <strong>un r\u00f4le, pas une technique<\/strong>. Une application de ce type se compose d&rsquo;un retriever, qui trouve l&rsquo;information, et d&rsquo;un g\u00e9n\u00e9rateur, qui l&rsquo;utilise pour produire la r\u00e9ponse. La fa\u00e7on dont le retriever trouve l&rsquo;information n&rsquo;est pas impos\u00e9e ; la <a href=\"\/blog\/rag\/qu-est-ce-qu-une-recherche-vectorielle\/\" title=\"recherche par similarit\u00e9 vectorielle\">recherche par similarit\u00e9 vectorielle<\/a> est la plus courante, sans \u00eatre la seule.<\/p>\n<blockquote>\n<p><strong>\ud83d\udca1 Intuition<\/strong> Imagine un biblioth\u00e9caire \u00e0 qui tu tends une question et qui te rend une pile de fiches tri\u00e9e, la plus utile au-dessus. Toute la diff\u00e9rence entre deux retrievers tient \u00e0 sa mani\u00e8re de trier : au mot pr\u00e8s, ou au sens pr\u00e8s. C&rsquo;est l&rsquo;objet de la suite.<\/p>\n<\/blockquote>\n<h2 id=\"du-ctrl-f-au-sens-les-familles-de-retrievers\">Du Ctrl+F au sens : les familles de retrievers<\/h2>\n<p>La recherche d&rsquo;information a une longue histoire, et chaque \u00e9tape a laiss\u00e9 une famille de retrievers toujours en service.<\/p>\n<h3>Compter les mots : bool\u00e9en, TF-IDF, BM25<\/h3>\n<p>La plus simple est la <strong>recherche bool\u00e9enne<\/strong> : un document correspond si les termes de la requ\u00eate y figurent exactement, \u00e9ventuellement combin\u00e9s par AND, OR, NOT. C&rsquo;est ton Ctrl+F. Le sac de mots (<em>Bag of Words<\/em>) va un cran plus loin : chaque document devient un vecteur qui indique la pr\u00e9sence de chaque mot du vocabulaire, mais l&rsquo;ordre et le sens des mots sont ignor\u00e9s.<\/p>\n<p>Reste un d\u00e9faut : tous les mots ne se valent pas. Le <strong>TF-IDF<\/strong> pond\u00e8re un terme selon sa fr\u00e9quence dans le document et sa raret\u00e9 dans le corpus. Le TF compte les occurrences du terme dans le document, rapport\u00e9es au nombre total de termes ; l&rsquo;IDF prend le logarithme du rapport entre le nombre de documents du corpus et le nombre de ceux qui contiennent le terme. Un mot qui revient partout (\u00ab le \u00bb, \u00ab et \u00bb) p\u00e8se donc peu, un mot rare et fr\u00e9quent dans un document p\u00e8se lourd.<\/p>\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;6adef46bb61a6&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"6adef46bb61a6\" class=\"wp-block-image size-large wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2400\" height=\"1380\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_tfidf_bm25.png\" alt=\"Les formules de TF-IDF (produit de TF et d'IDF) et de BM25, qui reprend ce produit en y ajoutant les param\u00e8tres k et b et la longueur du document rapport\u00e9e \u00e0 la longueur moyenne\" class=\"wp-image-457\" srcset=\"https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_tfidf_bm25.png 2400w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_tfidf_bm25-300x173.png 300w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_tfidf_bm25-1024x589.png 1024w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_tfidf_bm25-768x442.png 768w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_tfidf_bm25-1536x883.png 1536w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_tfidf_bm25-2048x1178.png 2048w\" sizes=\"auto, (max-width: 2400px) 100vw, 2400px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><figcaption class=\"wp-element-caption\">Sur la formule de BM25, regarde le rapport |d|\/avgdl : c&rsquo;est la longueur du document compar\u00e9e \u00e0 la longueur moyenne du corpus, et c&rsquo;est lui qui ajuste le score.<\/figcaption><\/figure>\n\n\n<p>Car TF-IDF reste insensible au contexte et \u00e0 l&rsquo;ordre des mots, et rien ne l&#8217;emp\u00eache de favoriser les longs documents. <strong>BM25<\/strong> l&rsquo;am\u00e9liore en normalisant selon la longueur, pour qu&rsquo;un document long ne soit pas avantag\u00e9 artificiellement.<\/p>\n<p>Pour voir la diff\u00e9rence, prends une base minuscule de trois phrases : \u00ab Australia won the Cricket World Cup 2023 \u00bb, \u00ab India and Australia played in the finals \u00bb et \u00ab Australia won the sixth time having last won in 2015 \u00bb. Voici un retriever TF-IDF construit dessus avec LangChain, interrog\u00e9 avec le seul mot \u00ab won \u00bb. La commande <code>%pip<\/code> installe d&rsquo;abord la biblioth\u00e8que scikit-learn dont il d\u00e9pend.<\/p>\n<pre><code>%pip install --upgrade scikit-learn\n\nfrom langchain_community.retrievers import TFIDFRetriever\n\nretriever = TFIDFRetriever.from_texts(\n    [&quot;Australia won the Cricket World Cup 2023&quot;,\n     &quot;India and Australia played in the finals&quot;,\n     &quot;Australia won the sixth time having last won in 2015&quot;]\n)\nresult = retriever.invoke(&quot;won&quot;)\nprint(result)\n<\/code><\/pre>\n<p><code>from_texts<\/code> construit le retriever \u00e0 partir des trois phrases ; <code>invoke<\/code> lui soumet la requ\u00eate et renvoie la liste des documents retenus. La deuxi\u00e8me phrase ne contient pas \u00ab won \u00bb : pour un retriever qui compte des mots, elle n&rsquo;a aucune raison de passer devant les deux autres.<\/p>\n<p>Le m\u00eame mini-corpus fonctionne avec BM25. Cette fois la requ\u00eate est une vraie question en langage naturel.<\/p>\n<pre><code>%pip install --upgrade rank_bm25\n\nfrom langchain_community.retrievers import BM25Retriever\n\nretriever = BM25Retriever.from_texts(\n    [&quot;Australia won the Cricket World Cup 2023&quot;,\n     &quot;India and Australia played in the finals&quot;,\n     &quot;Australia won the sixth time having last won in 2015&quot;]\n)\nresult = retriever.invoke(&quot;Who won the 2023 Cricket World Cup?&quot;)\nprint(result)\n<\/code><\/pre>\n<p>Remarque que l&rsquo;interface ne bouge pas : seul le nom de la classe change. C&rsquo;est le signe qu&rsquo;on est bien devant un r\u00f4le, pas devant une technique. En pratique, BM25 sert souvent de premi\u00e8re r\u00e9cup\u00e9ration rapide, que l&rsquo;on fait suivre d&rsquo;un r\u00e9-ordonnancement par une m\u00e9thode plus puissante.<\/p>\n<h3>Comparer des sens : les embeddings<\/h3>\n<p>Les retrievers lexicaux ont un angle mort : si ta question dit \u00ab gagner \u00bb et que le texte dit \u00ab remporter \u00bb, aucun mot ne co\u00efncide. Les <a href=\"\/blog\/rag\/que-sont-les-embeddings\/\" title=\"embeddings\">embeddings<\/a> r\u00e9pondent \u00e0 ce probl\u00e8me en repr\u00e9sentant les textes par des vecteurs denses, b\u00e2tis pour que des textes de sens voisin soient proches.<\/p>\n<p>Il en existe deux g\u00e9n\u00e9rations. Les embeddings statiques (Word2Vec, GloVe) donnent \u00e0 chaque mot un vecteur qui capte des relations de sens (\u00ab roi \u00bb moins \u00ab homme \u00bb plus \u00ab femme \u00bb se rapproche de \u00ab reine \u00bb), mais ce vecteur ne change jamais, ce qui pose probl\u00e8me pour les mots polys\u00e9miques. Les embeddings contextuels, produits par des mod\u00e8les comme <a href=\"\/blog\/deep-learning\/le-modele-bert\/\" title=\"BERT\">BERT<\/a> ou ceux d&rsquo;OpenAI, calculent une repr\u00e9sentation qui varie avec le contexte.<\/p>\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;6adef46bb65f1&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"6adef46bb65f1\" class=\"wp-block-image size-large wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2560\" height=\"1113\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_embeddings_statiques_contextuels-scaled.png\" alt=\"Deux colonnes : avec des embeddings statiques, \u00ab Apple \u00bb produit un seul vecteur quel que soit le contexte, fruit et entreprise confondus ; avec des embeddings contextuels, le vecteur diff\u00e8re selon la question pos\u00e9e\" class=\"wp-image-458\" srcset=\"https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_embeddings_statiques_contextuels-scaled.png 2560w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_embeddings_statiques_contextuels-300x130.png 300w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_embeddings_statiques_contextuels-1024x445.png 1024w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_embeddings_statiques_contextuels-768x334.png 768w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_embeddings_statiques_contextuels-1536x668.png 1536w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch04_embeddings_statiques_contextuels-2048x890.png 2048w\" sizes=\"auto, (max-width: 2560px) 100vw, 2560px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><figcaption class=\"wp-element-caption\">Compare les deux colonnes : \u00e0 gauche, le fruit et l&rsquo;entreprise partagent un seul vecteur ; \u00e0 droite, la question d\u00e9cide lequel des deux sens est retenu. Les embeddings contextuels co\u00fbtent plus cher en calcul, mais ce sont <strong>les plus utilis\u00e9s en RAG<\/strong>.<\/figcaption><\/figure>\n\n\n<p>Une fois les vecteurs obtenus, le classement est le m\u00eame pour toutes les familles. Pour une requ\u00eate <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.ranksignal.fr\/wp-content\/ql-cache\/quicklatex.com-ac7da57d7f507262338bb5168feb3e06_l3.png\" class=\"ql-img-inline-formula quicklatex-auto-format\" alt=\"&#113;\" title=\"Rendered by QuickLaTeX.com\" height=\"12\" width=\"8\" style=\"vertical-align: -4px;\"\/> et un document <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.ranksignal.fr\/wp-content\/ql-cache\/quicklatex.com-4e8716946f6a868f015e0d62f28bc540_l3.png\" class=\"ql-img-inline-formula quicklatex-auto-format\" alt=\"&#100;\" title=\"Rendered by QuickLaTeX.com\" height=\"12\" width=\"10\" style=\"vertical-align: 0px;\"\/> sous forme de vecteurs, le score le plus courant est la similarit\u00e9 cosinus :<\/p>\n<p><p class=\"ql-center-displayed-equation\" style=\"line-height: 41px;\"><span class=\"ql-right-eqno\"> &nbsp; <\/span><span class=\"ql-left-eqno\"> &nbsp; <\/span><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.ranksignal.fr\/wp-content\/ql-cache\/quicklatex.com-2a97282718d9f730400dfd77f8b7656b_l3.png\" height=\"41\" width=\"135\" class=\"ql-img-displayed-equation quicklatex-auto-format\" alt=\"&#92;&#91;&#92;&#99;&#111;&#115;&#40;&#113;&#44;&#100;&#41;&#61;&#92;&#102;&#114;&#97;&#99;&#123;&#113;&#92;&#99;&#100;&#111;&#116;&#32;&#100;&#125;&#123;&#124;&#113;&#124;&#92;&#44;&#124;&#100;&#124;&#125;&#46;&#92;&#93;\" title=\"Rendered by QuickLaTeX.com\"\/><\/p><\/p>\n<p>Ici <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.ranksignal.fr\/wp-content\/ql-cache\/quicklatex.com-6e2658b72bd6f173b7cad9a2711b25c8_l3.png\" class=\"ql-img-inline-formula quicklatex-auto-format\" alt=\"&#113;&#92;&#99;&#100;&#111;&#116;&#32;&#100;\" title=\"Rendered by QuickLaTeX.com\" height=\"16\" width=\"31\" style=\"vertical-align: -4px;\"\/> est le produit scalaire des deux vecteurs, et <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.ranksignal.fr\/wp-content\/ql-cache\/quicklatex.com-3b299991d0a1e490980a5cf738fd1fa5_l3.png\" class=\"ql-img-inline-formula quicklatex-auto-format\" alt=\"&#124;&#113;&#124;\" title=\"Rendered by QuickLaTeX.com\" height=\"19\" width=\"14\" style=\"vertical-align: -5px;\"\/>, <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.ranksignal.fr\/wp-content\/ql-cache\/quicklatex.com-34541aff80970010fc6807bc553ca7bc_l3.png\" class=\"ql-img-inline-formula quicklatex-auto-format\" alt=\"&#124;&#100;&#124;\" title=\"Rendered by QuickLaTeX.com\" height=\"19\" width=\"15\" style=\"vertical-align: -5px;\"\/> leurs normes (leurs longueurs). Diviser par les normes retire la longueur du calcul : il ne reste que l&rsquo;angle entre les deux vecteurs, et l&rsquo;on classe les documents selon ce score.<\/p>\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;6adef46bb6a18&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"6adef46bb6a18\" class=\"wp-block-image size-large wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"1429\" height=\"1222\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b3_ch01_similarite_cosinus.png\" alt=\"Deux vecteurs a et b formant un angle th\u00eata, avec un cosinus d'environ 0,63 ; un vecteur 2b, de m\u00eame direction que b mais deux fois plus long, est trac\u00e9 en pointill\u00e9s\" class=\"wp-image-459\" srcset=\"https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b3_ch01_similarite_cosinus.png 1429w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b3_ch01_similarite_cosinus-300x257.png 300w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b3_ch01_similarite_cosinus-1024x876.png 1024w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b3_ch01_similarite_cosinus-768x657.png 768w\" sizes=\"auto, (max-width: 1429px) 100vw, 1429px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><figcaption class=\"wp-element-caption\">Le vecteur en pointill\u00e9s a le double de la longueur de b mais pointe dans la m\u00eame direction : son cosinus avec a est inchang\u00e9. L&rsquo;angle compte, pas la longueur.<\/figcaption><\/figure>\n\n\n<p>Un d\u00e9tail qui relie les deux familles : dans sa forme la plus simple, un retriever peut tr\u00e8s bien calculer des TF-IDF pour tous les documents, puis le cosinus entre chacun et la requ\u00eate, et classer selon ce score. La m\u00e9canique du classement est donc la m\u00eame ; ce qui change, c&rsquo;est <strong>la mani\u00e8re de fabriquer les vecteurs<\/strong>.<\/p>\n<h3>Le reste de la famille<\/h3>\n<p>Les embeddings contextuels suffisent pour b\u00e2tir un syst\u00e8me de base, mais d&rsquo;autres m\u00e9thodes existent :<\/p>\n<ul>\n<li>la r\u00e9cup\u00e9ration <em>sparse apprise<\/em> (SPLADE, DeepCT) et la r\u00e9cup\u00e9ration <em>dense<\/em> (DPR, ANCE) ;<\/li>\n<li>la r\u00e9cup\u00e9ration qui combine sparse et dense (ColBERT) ;<\/li>\n<li>le <em>cross-encoder<\/em>, qui compare directement la paire requ\u00eate-document ;<\/li>\n<li>la r\u00e9cup\u00e9ration par graphes, qui interroge les relations entre entit\u00e9s plut\u00f4t que des vecteurs de texte.<\/li>\n<\/ul>\n<p>Tu n&rsquo;as pas \u00e0 tout coder : LangChain propose des retrievers d\u00e9j\u00e0 int\u00e9gr\u00e9s. Ce sont les bases vectorielles elles-m\u00eames (FAISS, Pinecone, Milvus, Weaviate, ces deux derniers offrant une recherche hybride), les services des fournisseurs cloud (Amazon Kendra, Azure AI Search, Google Vertex AI Search) ou des connecteurs vers des ressources web (Wikip\u00e9dia, Arxiv, AskNews).<\/p>\n<h2 id=\"sous-le-capot-un-encodeur-ou-deux\">Sous le capot : un encodeur ou deux ?<\/h2>\n<p>Comment un retriever dense calcule-t-il son score ? Deux architectures s&rsquo;opposent, et leur diff\u00e9rence explique une bonne part de ce qui suit.<\/p>\n<p>Avec un <strong>encodeur unique<\/strong>, on donne au mod\u00e8le la requ\u00eate et le document en m\u00eame temps. Il peut alors faire jouer l&rsquo;auto-attention entre tous leurs mots, et la sortie est un score de similarit\u00e9. C&rsquo;est pr\u00e9cis, mais le texte entier (requ\u00eate plus document) doit tenir dans la fen\u00eatre du mod\u00e8le, 512 jetons pour BERT, et il faut repasser la requ\u00eate avec chacun des documents du corpus : le co\u00fbt devient prohibitif.<\/p>\n<p>Le <strong>bi-encodeur<\/strong> coupe le probl\u00e8me en deux : un encodeur lit la requ\u00eate, un autre lit chaque document. Les vecteurs des documents sont calcul\u00e9s une fois et rang\u00e9s dans une base ; \u00e0 chaque requ\u00eate, il ne reste qu&rsquo;\u00e0 comparer son vecteur \u00e0 ceux de la base par le cosinus. C&rsquo;est bien plus rapide, mais moins pr\u00e9cis, car on perd une partie des interactions entre les termes de la requ\u00eate et ceux du document.<\/p>\n<table>\n<thead>\n<tr>\n<th>Architecture<\/th>\n<th>Ce qu&rsquo;elle lit<\/th>\n<th>Atout<\/th>\n<th>Limite<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Encodeur unique<\/td>\n<td>Requ\u00eate et document ensemble<\/td>\n<td>Interaction fine entre leurs mots<\/td>\n<td>Il faut le repasser sur tout le corpus<\/td>\n<\/tr>\n<tr>\n<td>Bi-encodeur<\/td>\n<td>Requ\u00eate d&rsquo;un c\u00f4t\u00e9, chaque document de l&rsquo;autre<\/td>\n<td>Vecteurs de documents calcul\u00e9s d&rsquo;avance, recherche rapide<\/td>\n<td>Interactions mot \u00e0 mot perdues<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;6adef46bb6e2a&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"6adef46bb6e2a\" class=\"wp-block-image size-large wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2560\" height=\"796\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b2_ch02_architecture_rag_vectoriel-scaled.png\" alt=\"Une question passe par un mod\u00e8le d'embedding, puis interroge un index vectoriel Neo4j qui renvoie des documents similaires transmis \u00e0 un LLM\" class=\"wp-image-460\" srcset=\"https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b2_ch02_architecture_rag_vectoriel-scaled.png 2560w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b2_ch02_architecture_rag_vectoriel-300x93.png 300w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b2_ch02_architecture_rag_vectoriel-1024x319.png 1024w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b2_ch02_architecture_rag_vectoriel-768x239.png 768w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b2_ch02_architecture_rag_vectoriel-1536x478.png 1536w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b2_ch02_architecture_rag_vectoriel-2048x637.png 2048w\" sizes=\"auto, (max-width: 2560px) 100vw, 2560px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><figcaption class=\"wp-element-caption\">Sur ce sch\u00e9ma, le retriever est tout ce qui se trouve entre la question et les \u00ab documents similaires \u00bb : un mod\u00e8le d&#8217;embedding, puis un index. Le LLM n&rsquo;arrive qu&rsquo;apr\u00e8s.<\/figcaption><\/figure>\n\n\n<p>Le choix de l&rsquo;encodeur n&rsquo;est pas anodin. Il p\u00e8se sur le co\u00fbt (un embedder est un Transformer : plus il est grand, plus le calcul co\u00fbte, et un encodeur ferm\u00e9 facture son API \u00e0 l&rsquo;usage), sur le stockage (plus les vecteurs sont grands, plus il co\u00fbte), sur la latence, et sur la performance. Certains encodeurs sont sp\u00e9cialis\u00e9s (finance, m\u00e9decine, science, code) et certains g\u00e8rent une centaine de langues. Un bon point de d\u00e9part est le classement MTEB de Hugging Face, mais ses r\u00e9sultats sont souvent auto-d\u00e9clar\u00e9s et peuvent surestimer les capacit\u00e9s d&rsquo;un mod\u00e8le si des donn\u00e9es de test ont fui dans son entra\u00eenement : <strong>teste plusieurs mod\u00e8les sur tes propres donn\u00e9es<\/strong>.<\/p>\n<p>Reste \u00e0 stocker ces vecteurs. FAISS n&rsquo;est pas con\u00e7u pour cr\u00e9er, modifier ou supprimer des donn\u00e9es : il convient si la base est immuable. Les bases SQL qui acceptent les vecteurs passent mal \u00e0 l&rsquo;\u00e9chelle. Les bases d\u00e9di\u00e9es sont en g\u00e9n\u00e9ral la meilleure solution pour la performance, gr\u00e2ce \u00e0 la recherche approch\u00e9e des plus proches voisins, dont HNSW sacrifie un peu de pr\u00e9cision mais va bien plus vite qu&rsquo;une recherche exacte comme l&rsquo;indexation Flat.<\/p>\n<h2 id=\"voir-un-retriever-dense-tourner\">Voir un retriever dense tourner<\/h2>\n<p>Voici la fonction de r\u00e9cup\u00e9ration d&rsquo;un agent qui recommande des films. Les descriptions de films ont \u00e9t\u00e9 d\u00e9coup\u00e9es en passages, transform\u00e9es en vecteurs par un encodeur <code>embedder<\/code> et rang\u00e9es dans une collection Chroma, <code>collection<\/code>. Au moment de la requ\u00eate, on r\u00e9utilise <strong>le m\u00eame encodeur<\/strong> pour la question.<\/p>\n<pre><code>def retrieve_documents(query, collection, top_k=5):\n    query_embedding = embedder.encode(query).tolist()\n    results = collection.query( query_embeddings=[query_\nembedding], n_results=top_k )\n    chunks = []\n    titles = []\n    for document in results['metadatas'][0]:\n        chunks.append(document['chunk'])\n        titles.append(document['original_title'])\n    return chunks, titles\n<\/code><\/pre>\n<p>Lis la fonction comme une d\u00e9finition de retriever : elle prend une requ\u00eate, un nombre <code>top_k<\/code>, et rend une liste de passages avec leurs titres, rang\u00e9s du plus proche au plus lointain. <code>results['metadatas'][0]<\/code> d\u00e9signe les m\u00e9tadonn\u00e9es de notre unique requ\u00eate, d&rsquo;o\u00f9 l&rsquo;on extrait le texte du passage et le titre du film d&rsquo;origine.<\/p>\n<p>Une question en langage courant passe donc par exactement la m\u00eame porte que le mot \u00ab won \u00bb de tout \u00e0 l&rsquo;heure : requ\u00eate en entr\u00e9e, liste class\u00e9e en sortie. Seule la d\u00e9finition de \u00ab proche \u00bb a chang\u00e9.<\/p>\n<h2 id=\"quand-la-question-ne-ressemble-pas-aux-documents\">Quand la question ne ressemble pas aux documents<\/h2>\n<p>Le retriever compare une requ\u00eate \u00e0 des documents. Si les deux ne sont pas formul\u00e9s de la m\u00eame fa\u00e7on, la bonne r\u00e9ponse peut dormir au fond de la liste. On agit alors sur la requ\u00eate avant de la lui confier, souvent avec l&rsquo;aide d&rsquo;un LLM.<\/p>\n<ul>\n<li><strong>La d\u00e9composition<\/strong> d\u00e9coupe une question complexe en sous-requ\u00eates ind\u00e9pendantes. Aucun passage ne r\u00e9pond peut-\u00eatre \u00e0 \u00ab qui a invent\u00e9 le t\u00e9l\u00e9graphe et le t\u00e9l\u00e9phone ? \u00bb, mais chacune des deux moiti\u00e9s trouvera le sien.<\/li>\n<li><em>La r\u00e9\u00e9criture<\/em> reformule un \u00e9nonc\u00e9 en requ\u00eate de recherche exploitable : \u00ab je n&rsquo;arrive pas \u00e0 envoyer d&rsquo;e-mails depuis mon t\u00e9l\u00e9phone \u00bb devient \u00ab \u00e9tapes de d\u00e9pannage pour r\u00e9soudre les probl\u00e8mes d&rsquo;envoi d&rsquo;e-mails sur smartphone \u00bb.<\/li>\n<li><em>L&rsquo;expansion<\/em> ajoute des termes ou g\u00e9n\u00e8re plusieurs variantes de la requ\u00eate (cinq, par exemple), ce qui augmente le rappel. Mal ma\u00eetris\u00e9e, elle peut r\u00e9duire la pr\u00e9cision et diluer l&rsquo;intention d&rsquo;origine.<\/li>\n<\/ul>\n<p>La plus surprenante est <strong>HyDE<\/strong> (<em>Hypothetical Document Embeddings<\/em>). On demande au LLM d&rsquo;\u00e9crire une r\u00e9ponse hypoth\u00e9tique \u00e0 la question, sans consulter la base, puis on utilise le vecteur de cette r\u00e9ponse pour la recherche. L&rsquo;id\u00e9e : m\u00eame imparfaite, une r\u00e9ponse invent\u00e9e ressemble davantage aux vrais passages pertinents que la question brute.<\/p>\n<pre><code>original_query = &quot;How does climate change affect polar bears?&quot;\nsystem_prompt = &quot;You are an expert in climate change and arctic life.&quot;\nhyde_prompt = f&quot;Generate an answer to the question: {original_query}&quot;\n\nfrom openai import OpenAI\nclient = OpenAI()\nresponse = client.chat.completions.create(\n    model=&quot;gpt-4o-mini&quot;,\n    messages=[\n        {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: system_prompt},\n        {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: hyde_prompt}\n    ]\n)\nhy_answer = response.choices[0].message.content\n\nembeddings = OpenAIEmbeddings(model=&quot;text-embedding-3-large&quot;)\nhyde = embeddings.embed_query(hy_answer)\n<\/code><\/pre>\n<p>Le code fait deux choses : le LLM <code>gpt-4o-mini<\/code> r\u00e9dige <code>hy_answer<\/code>, puis <code>embed_query<\/code> convertit cette r\u00e9ponse en vecteur <code>hyde<\/code>. C&rsquo;est ce vecteur, et non celui de la question, qui sert \u00e0 interroger l&rsquo;index. <code>OpenAIEmbeddings<\/code> est la classe de la biblioth\u00e8que <code>langchain_openai<\/code> d\u00e9j\u00e0 crois\u00e9e dans les exemples de retrievers int\u00e9gr\u00e9s.<\/p>\n<p>Le LLM n&rsquo;a pas besoin de conna\u00eetre la vraie r\u00e9ponse, ce n&rsquo;est m\u00eame pas le but : il sait seulement reconna\u00eetre dans la question des motifs pertinents, et ce sont eux qui font remonter les bons passages. HyDE aide surtout quand le rappel est faible, ou quand les documents viennent d&rsquo;un domaine que l&rsquo;encodeur n&rsquo;a jamais vu.<\/p>\n<h2 id=\"un-retriever-ne-travaille-pas-seul\">Un retriever ne travaille pas seul<\/h2>\n<p>Quand le retriever est entour\u00e9, ses d\u00e9fauts se compensent.<\/p>\n<h3>Mots et sens, ensemble<\/h3>\n<p>Les retrievers lexicaux et denses ont des angles morts oppos\u00e9s : certains passages ne sont pertinents que parce qu&rsquo;ils contiennent un mot-cl\u00e9 pr\u00e9cis (un nom de produit, de personne), alors que tous les passages qui contiennent ce mot ne le sont pas, surtout pour une requ\u00eate qui vise un concept. La <a href=\"\/blog\/rag\/qu-est-ce-qu-une-recherche-hybride\/\" title=\"recherche hybride\">recherche hybride<\/a> combine les deux et choisit combien de passages prendre \u00e0 chaque type de recherche ; c&rsquo;est l&rsquo;un des composants les plus utilis\u00e9s dans les pipelines actuels.<\/p>\n<p>Dans l&rsquo;impl\u00e9mentation vue en exemple, la fonction calcule l&#8217;embedding de la requ\u00eate, interroge ChromaDB d&rsquo;un c\u00f4t\u00e9 et Elasticsearch de l&rsquo;autre, puis concat\u00e8ne les deux listes de r\u00e9sultats. On peut aussi aiguiller la requ\u00eate avant de chercher : s&rsquo;il y a des mots-cl\u00e9s pr\u00e9cis, une recherche par mots-cl\u00e9s, sinon une recherche s\u00e9mantique. La r\u00e8gle ci-dessous en est la version la plus simple, o\u00f9 <code>&quot;specific_keyword&quot;<\/code> est un mot-cl\u00e9 fictif \u00e0 remplacer par le tien.<\/p>\n<pre><code>def advanced_query_routing(query):\n    &quot;&quot;&quot;\n    Determines the retrieval method based on the presence of specific\nkeywords in the query.\n    Args:\n        query (str): The user's query.\n    Returns:\n        str: 'textual' if the query requires text-based retrieval,\n'vector' otherwise.\n    &quot;&quot;&quot;\n    if &quot;specific_keyword&quot; in query:\n        return &quot;textual&quot;\n    else:\n        return &quot;vector&quot;\n<\/code><\/pre>\n<h3>Chercher large, puis trier<\/h3>\n<p>Voici un dilemme : augmenter <em>k<\/em> fait monter le rappel de la r\u00e9cup\u00e9ration, mais donne trop de mati\u00e8re au LLM. Le <strong>reranking<\/strong> le r\u00e9sout en deux temps. D&rsquo;abord une r\u00e9cup\u00e9ration classique d&rsquo;un grand nombre de passages ; ensuite un second mod\u00e8le, le reranker, r\u00e9ordonne cette pr\u00e9s\u00e9lection et ne garde que les <em>k<\/em> meilleurs.<\/p>\n<p>Le reranker prend en compte l&rsquo;information contrast\u00e9e avec la requ\u00eate et lit chaque passage <em>en contexte<\/em> avec elle, ce que ne fait pas un bi-encodeur. On en rencontre plusieurs types :<\/p>\n<ul>\n<li>les <strong>cross-encodeurs<\/strong> (comme BGE), qui lisent la requ\u00eate et un passage \u00e0 la fois et renvoient une similarit\u00e9 entre 0 et 1 ;<\/li>\n<li>les rerankers <em>multi-vecteurs<\/em> (comme ColBERT), moins gourmands en calcul, car l&rsquo;interaction entre les deux textes intervient en fin de traitement ;<\/li>\n<li>les <em>LLM<\/em> utilis\u00e9s comme rerankers : <em>pointwise<\/em> (un document \u00e0 la fois), <em>pairwise<\/em> (deux documents, lequel est le plus pertinent ?) ou <em>listwise<\/em> (une liste \u00e0 classer, avec un risque de co\u00fbt \u00e9lev\u00e9) ;<\/li>\n<li>des LLM ajust\u00e9s au classement, comme RankT5.<\/li>\n<\/ul>\n<p>Le cross-encodeur n&rsquo;est donc rien d&rsquo;autre que l&rsquo;encodeur unique de tout \u00e0 l&rsquo;heure : trop lent pour balayer un corpus entier, il trouve sa place sur une pr\u00e9s\u00e9lection courte. Apr\u00e8s le reranking, on peut encore filtrer les passages dont la similarit\u00e9 est sous un seuil, qui ne contiennent pas certains mots-cl\u00e9s, ou qui sont plus anciens qu&rsquo;une date.<\/p>\n<h2 id=\"ce-qu-un-retriever-ne-fait-pas\">Ce qu&rsquo;un retriever ne fait pas<\/h2>\n<p><strong>Il ne distingue pas toujours \u00ab proche \u00bb de \u00ab pareil \u00bb.<\/strong> La similarit\u00e9 entre deux embeddings vient du fait que des mots apparaissent dans la m\u00eame fen\u00eatre de contexte : le mod\u00e8le ne distingue donc pas les synonymes des antonymes. Cette limite est d\u00e9crite pour les embeddings de mots, mais elle rappelle que \u00ab proche \u00bb n&rsquo;est pas \u00ab pertinent \u00bb.<\/p>\n<p><strong>Il ignore ton domaine.<\/strong> Les benchmarks g\u00e9n\u00e9ralistes mesurent une performance g\u00e9n\u00e9rale, qui peut \u00eatre mauvaise dans ton domaine ou pour ta t\u00e2che. Quand le mod\u00e8le d&#8217;embeddings et le mod\u00e8le de g\u00e9n\u00e9ration sont d\u00e9velopp\u00e9s et ajust\u00e9s s\u00e9par\u00e9ment, leurs r\u00e9sultats peuvent \u00eatre incoh\u00e9rents dans les domaines sp\u00e9cialis\u00e9s ; les ajuster conjointement pour un domaine de niche corrige ce d\u00e9calage. L&rsquo;entra\u00eenement peut prendre trois formes : ajuster d&rsquo;abord le retriever puis le geler (<em>retriever-first<\/em>, adapt\u00e9 quand la pr\u00e9cision de la recherche prime, comme dans un moteur de recherche juridique), laisser un LLM superviser l&rsquo;entra\u00eenement du retriever (<em>LLM-first<\/em>), ou entra\u00eener les deux ensemble de bout en bout.<\/p>\n<p><strong>Il ne cherche pas que du texte.<\/strong> Pour des contenus m\u00ealant image, son et vid\u00e9o, la r\u00e9cup\u00e9ration d\u00e9pend de la strat\u00e9gie d&#8217;embeddings : similarit\u00e9 classique avec des embeddings partag\u00e9s, r\u00e9cup\u00e9ration multi-vecteurs qui interroge chaque espace d&#8217;embeddings propre \u00e0 une modalit\u00e9, ou r\u00e9cup\u00e9ration textuelle suivie de la r\u00e9cup\u00e9ration des fichiers bruts associ\u00e9s.<\/p>\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;6adef46bb7255&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"6adef46bb7255\" class=\"wp-block-image size-large wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2100\" height=\"1800\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch08_embeddings_multimodal.png\" alt=\"Un espace d'embeddings partag\u00e9 o\u00f9 \u00ab chien \u00bb en texte, une photo de chien et un aboiement sont proches, tout comme \u00ab voler \u00bb en texte et une vid\u00e9o de vol\" class=\"wp-image-461\" srcset=\"https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch08_embeddings_multimodal.png 2100w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch08_embeddings_multimodal-300x257.png 300w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch08_embeddings_multimodal-1024x878.png 1024w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch08_embeddings_multimodal-768x658.png 768w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch08_embeddings_multimodal-1536x1317.png 1536w, https:\/\/www.ranksignal.fr\/wp-content\/uploads\/2026\/10\/b1_ch08_embeddings_multimodal-2048x1755.png 2048w\" sizes=\"auto, (max-width: 2100px) 100vw, 2100px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><figcaption class=\"wp-element-caption\">Les points proches sur ce sch\u00e9ma repr\u00e9sentent des contenus de m\u00eame sens mais de natures diff\u00e9rentes : un mot, une photo et un son peuvent voisiner dans le m\u00eame espace.<\/figcaption><\/figure>\n\n\n<p><strong>Il ne s&rsquo;am\u00e9liore pas seul.<\/strong> Chacun de ses d\u00e9fauts a sa rustine, que tu as d\u00e9j\u00e0 crois\u00e9e dans cet article :<\/p>\n<table>\n<thead>\n<tr>\n<th>Ce que le retriever rate<\/th>\n<th>Ce qu&rsquo;on ajoute<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Requ\u00eate complexe, faible rappel<\/td>\n<td>Transformation de la requ\u00eate : d\u00e9composition, r\u00e9\u00e9criture, expansion<\/td>\n<\/tr>\n<tr>\n<td>Mots-cl\u00e9s pr\u00e9cis non retrouv\u00e9s<\/td>\n<td>Recherche hybride : BM25 plus vecteurs, score pond\u00e9r\u00e9<\/td>\n<\/tr>\n<tr>\n<td>Types de requ\u00eates tr\u00e8s vari\u00e9s<\/td>\n<td>Routage de la requ\u00eate vers le bon outil<\/td>\n<\/tr>\n<tr>\n<td>Passages pertinents perdus par un top-k arbitraire<\/td>\n<td>Reranking<\/td>\n<\/tr>\n<tr>\n<td>Documents d&rsquo;un autre domaine<\/td>\n<td>R\u00e9ponse hypoth\u00e9tique (HyDE) ou questions hypoth\u00e9tiques par passage<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<blockquote>\n<p><strong>\ud83d\udccc \u00c0 retenir<\/strong> Un retriever est une fonction qui transforme une requ\u00eate en liste class\u00e9e. Mots exacts, vecteurs ou graphe changent la fa\u00e7on de trier, jamais le contrat : de l&rsquo;ordre d\u00e9pend tout ce qui passe ensuite.<\/p>\n<\/blockquote>\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Un retriever re\u00e7oit une question et renvoie des passages class\u00e9s par pertinence. D\u00e9couvre ses familles (mots, vecteurs, hybride), du code et ses limites.<\/p>\n","protected":false},"author":1,"featured_media":462,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-463","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-rag"],"_links":{"self":[{"href":"https:\/\/www.ranksignal.fr\/blog\/wp-json\/wp\/v2\/posts\/463","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ranksignal.fr\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ranksignal.fr\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ranksignal.fr\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ranksignal.fr\/blog\/wp-json\/wp\/v2\/comments?post=463"}],"version-history":[{"count":4,"href":"https:\/\/www.ranksignal.fr\/blog\/wp-json\/wp\/v2\/posts\/463\/revisions"}],"predecessor-version":[{"id":802,"href":"https:\/\/www.ranksignal.fr\/blog\/wp-json\/wp\/v2\/posts\/463\/revisions\/802"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.ranksignal.fr\/blog\/wp-json\/wp\/v2\/media\/462"}],"wp:attachment":[{"href":"https:\/\/www.ranksignal.fr\/blog\/wp-json\/wp\/v2\/media?parent=463"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ranksignal.fr\/blog\/wp-json\/wp\/v2\/categories?post=463"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ranksignal.fr\/blog\/wp-json\/wp\/v2\/tags?post=463"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}