<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Recherche hybride - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Recherche hybride - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/fr/search-labs/blog/category/hybrid-search</link>
    </image>
    <link>https://www.elastic.co/fr/search-labs/blog/category/hybrid-search</link>
    <atom:link href="https://www.elastic.co/fr/search-labs/rss/category/hybrid-search.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[fr]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 16:09:26 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Base vectorielle Elasticsearch : transférez en quelques minutes, scalez à un coût abordable jusqu'à des centaines de milliards]]></title>
    <description><![CDATA[Les aspects complexes de la recherche hybride, déjà traités, avec des paramètres par défaut optimisés, des modèles Jina AI natifs et tiers, et une inférence GPU gérée, le tout prêt à l'emploi. Créez des applications d'IA rapides et scalables, pas de l'infrastructure.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch est l’une des plateformes les plus largement déployées au monde pour les charges de travail vectorielles, soutenant la recherche sémantique, la génération augmentée de récupération (RAG) et les recommandations pour des entreprises telles que GitHub, Docusign, Seismic et bien d’autres. Aujourd’hui, nous annonçons Elasticsearch Vector Database, une nouvelle offre sans serveur optimisée pour les applications basées sur des vecteurs. Vous apportez vos documents et vos requêtes, et nous nous chargeons des plongements, du réglage de l’index et de l’infrastructure. De plus, nous la maintenons abordable et scalable. </p><p>Pour les nouveaux utilisateurs, c’est le moyen le plus rapide de mettre en place une recherche vectorielle de haute qualité. Si vous utilisez déjà Elasticsearch, la nouvelle offre permet de bénéficier de la recherche vectorielle sur la plateforme où se trouvent déjà vos données, sans aucun nouveau système à adopter. La base vectorielle Elasticsearch prend en charge divers scénarios, de l’ancrage d’un grand modèle de langage (LLM) à l’ajout de fonctionnalités de récupération et de mémoire pour un agent IA, en passant par le traitement de centaines de milliards de vecteurs. <a href="https://cloud.elastic.co/registration?onboarding_token=vector">Lancez un nouveau projet</a> et commencez en quelques minutes.</p><h2>Un seul moteur pour tous les cas d'utilisation vectoriels</h2><p>La base vectorielle Elasticsearch est conçue pour tous ceux qui créent des applications à l'aide de vecteurs :</p><ul><li><p><strong>RAG :</strong> récupérez le contexte adapté pour votre LLM grâce à la récupération vectorielle dense et clairsemée, ou optez pour la recherche hybride combinant à la fois la récupération vectorielle et lexicale. La qualité de votre génération s’améliore avec la qualité de votre récupération.</p></li><li><p><strong>Agents IA :</strong> offrez aux agents une récupération rapide et filtrée des documents et de la mémoire conversationnelle, avec les faibles latences qu’exigent les boucles d’agents à étapes multiples.</p></li><li><p><strong>Recherche sémantique :</strong> faites correspondre selon le sens, et non par mots-clés, avec un seul type de champ et sans aucun code de pipeline.</p></li><li><p><strong>Recommandations et similarité :</strong> trouvez les plus proches voisins parmi des produits, des images ou tout autre contenu dont vous disposez, à grande échelle.</p></li></ul><h2>Tout ce dont votre charge de travail vectorielle a besoin, optimisé et prêt à l'emploi</h2><p>Créer une application vectorielle implique de relier plusieurs éléments distincts : configurer et héberger des modèles d’embedding, indexer vos documents via ces modèles, stocker efficacement les vecteurs, appliquer le modèle d’embedding à chaque requête, effectuer des correspondances avec le stockage de vecteurs, et enfin, récupérer les documents correspondant aux résultats. La base vectorielle Elasticsearch gère tout cela pour vous, sans configuration ni paramétrage supplémentaire.</p><h3>Indexation vectorielle avec le mode d’indexation vectordb_document</h3><p>Le mode d’indexation <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-vectordb-document-mode">vectordb_document</a>, une nouvelle configuration d’index spécialement conçue pour les charges de travail axées sur les vecteurs, est activé par défaut, vous bénéficiez ainsi des paramètres que choisiraient des experts. Voici ce que cela active :</p><ul><li><p><strong>bfloat16 par défaut :</strong> les vecteurs sont stockés à la moitié de la taille de float32 avec un impact négligeable sur le rappel, réduisant votre empreinte disque d’environ la moitié avant même que la quantification n’entre en jeu.</p></li><li><p><strong>Vecteurs sources exclus :</strong> dans Elasticsearch, vos plongements figurent déjà dans les structures d’index utilisées pour la recherche ; conserver une deuxième copie brute dans _source ne fait qu’augmenter le stockage et ralentir la récupération des résultats. Nous excluons le doublon afin que les réponses soient renvoyées plus rapidement et que vous stockiez moins.</p></li><li><p><strong>Les bons fichiers préchargés dans le cache :</strong> les structures de données auxquelles les requêtes vectorielles accèdent en premier sont préchauffées en mémoire à l’avance, de sorte que votre première (et votre millième) requête soit ultra-rapide.</p></li><li><p><strong>Fusion parallèle :</strong> la fusion regroupe les segments dans des structures vectorielles mieux organisées, ce qui améliore à la fois le rappel et la latence, et l’exécution de ces fusions en multithread vous permet d’y parvenir plus rapidement.</p></li></ul><h3>Stockage vectoriel, compression et optimisation automatique</h3><ul><li><p>Vos vecteurs sont compressés automatiquement.<a href="https://www.elastic.co/fr/search-labs/blog/better-binary-quantization-lucene-elasticsearch"> La quantification binaire optimisée (BBQ)</a> réduit l’empreinte mémoire des vecteurs jusqu’à 32 fois tout en préservant le rappel, et DiskBBQ réduit encore davantage les besoins en mémoire pour les charges de travail à grande échelle.<a href="https://www.elastic.co/fr/search-labs/blog/vector-quantization-auto-calibration-diskbbq"> </a></p></li><li><p>Optez pour<a href="https://www.elastic.co/fr/search-labs/blog/vector-quantization-auto-calibration-diskbbq"> l’auto-étalonnage</a>, qui ajuste la quantification de chaque segment en fonction de vos données et la réajuste à chaque fusion à mesure que les données dérivent. Lors des tests sur 18 ensembles de données, le nombre de requêtes par seconde (QPS) s’est amélioré en moyenne de 16,7 %, avec des gains de rappel pour la plupart d’entre eux.</p></li></ul><h3>Embeddings sur une inférence GPU gérée</h3><ul><li><p>Générez des embeddings grâce aux <a href="https://www.elastic.co/fr/jina-search-models">modèles natifs d’embedding et de reclassement de Jina AI</a>, ou importez des modèles tiers, le tout sur des GPU gérés via <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service (EIS)</a>, sans aucun serveur de modèles à gérer. Ou optez pour l’auto-hébergement, si vous préférez votre propre infrastructure.</p></li><li><p>Le type de champ <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><strong>semantic_text</strong></a> gère automatiquement la segmentation et l’embedding, ainsi que l’interrogation, ce qui représente la voie la plus simple vers la recherche sémantique sur le marché. </p></li></ul><h3>Recherche hybride et recherche vectorielle filtrée</h3><ul><li><p>La <a href="https://www.elastic.co/fr/elasticsearch/hybrid-search">recherche hybride</a> est intégrée, combinant la récupération en texte intégral et vectorielle dans une seule requête. Combinez les résultats grâce à la fusion des rangs réciproques (RRF) ou à tout autre mécanisme de combinaison de votre choix. La recherche vectorielle est généralement la partie de la recherche hybride la plus difficile à bien configurer. Avec la base vectorielle Elasticsearch, vous maîtrisez la situation et l’ensemble de votre stack hybride s’améliore. </p></li><li><p>Avec la <a href="https://www.elastic.co/fr/search-labs/blog/filtered-hnsw-knn-search">recherche vectorielle filtrée</a>, appliquez des filtres de métadonnées dans le cadre même de la récupération vectorielle, et non après coup, ce qui dégrade le rappel.</p></li></ul><h3>Entreprise dès le premier jour</h3><p>Vous bénéficiez également du contrôle d’accès basé sur les rôles (RBAC), du logging d’audit, et des certifications de conformité qui font généralement défaut aux bases vectorielles pure-play.</p><h2>Abordable à grande échelle et prévisible</h2><p>La base vectorielle Elasticsearch est conçue pour rester abordable à mesure de votre croissance : les compressions BBQ et DiskBBQ, qui maintiennent un stockage linéaire et une faible utilisation de la mémoire, permettent un scaling vers des centaines de milliards de vecteurs sans faire exploser votre facture. Et ce que vous payez est basé sur des chiffres que vous connaissez déjà : la quantité de données que vous stockez, ce que vous indexez, ainsi que la capacité de recherche dont vous avez besoin. Estimez votre nombre de documents et les dimensions de vos vecteurs, ainsi que votre charge de requêtes, et vous pourrez déterminer ce que vous paierez avant de créer le projet. Vous pouvez également comprendre votre facture ligne par ligne à la fin du mois. Il n’y a pas d’unités de calcul opaques, ni de frais imprévus pour les opérations en arrière-plan.</p><h2>Comment démarrer avec la base vectorielle Elasticsearch</h2><h3>Créez un projet de Base vectorielle sans serveur</h3><p>Créez un nouveau <a href="https://cloud.elastic.co/registration?onboarding_token=vector">projet de base vectorielle sans serveur dans Elastic Cloud</a>. Dirigez vos données vers le point de terminaison, et vous êtes prêt à indexer.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt556cbdfba551f248/6aa10b4332b53038406d321a/image1.png" alt="Elastic Cloud Serverless project types: Elasticsearch, Vector Database, Observability and Security" /><h3>Créer un index à l’aide de semantic_text</h3><p>Le mode d’index vectoriel gère la configuration vectorielle. L’utilisation de semantic_text signifie que la configuration des embeddings et du chunking est gérée pour vous, tout comme la configuration de l’index, sur une inférence GPU gérée, sans aucun pipeline d’embedding à créer.</p>PUT my-vectors
{
"mappings": {
"properties": {
"description": { "type": "semantic_text" }
    }
  }
}<h3>Ingérer des documents</h3><p>Indexez du texte, et les embeddings sont générés pour vous.</p>POST /my-vectors/_doc
{
  "id": "park_rocky-mountain",
  "title": "Rocky Mountain",
  "description": "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra."
}<h3>Exécuter une requête de recherche sémantique</h3><p>Interrogez le même champ sémantique que vous venez de créer :</p>GET /my-vectors/_search
{
  "query": {
    "semantic": {
      "field": "description",
      "query": "a mountain range in the middle of north america"
    }
  }
}<p>Et vous obtenez des résultats :</p>{
  "took": 80,
  "hits": {
    "max_score": 0.7792325,
    "hits": [
      {
        "_index": "my-vectors",
        "_score": 0.7792325,
        "_source": {
          "id": "park_rocky-mountain",
          "title": "Rocky Mountain",
          "description": "Bisected north to south by the Continental Divide, ..."
        }
      }
    ]
  }
}<p>La recherche sémantique n’est qu’un début. Exécutez des requêtes entièrement textuelles ou combinez les deux dans des requêtes hybrides. Vous pouvez même créer vos propres requêtes vectorielles pour bénéficier d’un contrôle total. Suivez le <a href="https://www.elastic.co/docs/solutions/vector-database/vector-full-text-search">guide de démarrage rapide de la recherche sémantique</a> dans la documentation pour obtenir toutes les instructions.</p><h2>Quelle est la suite pour la recherche vectorielle dans Elasticsearch</h2><p>Nous travaillons déjà sur les prochaines améliorations :</p><ul><li><p><strong>Meilleure prise en charge de la mutualisation :</strong> si vos données doivent rester séparées par entité, nous vous donnons les moyens de le faire plus rapidement et avec moins de code.</p></li><li><p><strong>Optimisation automatique des index :</strong> d’un « tout nouvel index » à « entièrement optimisé », avec le moins d’ajustements possible.</p></li><li><p><strong>Améliorations continues de l’infrastructure :</strong> ajustement continu des paramètres et de l’infrastructure de la base vectorielle afin que vous bénéficiiez toujours du meilleur débit et des réponses les plus rapides.</p></li></ul><h2>Essayez la base vectorielle Elasticsearch sur Elastic Cloud Serverless</h2><p>Passez d’un projet vide à une requête vectorielle hybride et filtrée en quelques minutes, avec des réglages par défaut prêts pour la production qui s’ajustent pour vous. Créez des applications d'IA rapides et scalables, pas de l'infrastructure.</p><p>Commencez sur <a href="https://cloud.elastic.co/registration?onboarding_token=vector">Elastic Cloud Serverless</a>, ou plongez dans la <a href="https://www.elastic.co/docs/solutions/vector-database">documentation complète </a>et la <a href="https://www.elastic.co/docs/api/doc/elastic-cloud-serverless/group/endpoint-vectordb-projects">référence de l’API.</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-database-rag-serverless</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-database-rag-serverless</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[Elastic Cloud Serverless]]></category>
    <category><![CDATA[Recherche hybride]]></category>
    <dc:creator><![CDATA[Dustin Coates]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4def84aae6aff861/6aa10ab1ee57e53d9b05253c/cover.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Comment mesurer et améliorer le rappel de recherche Elasticsearch : de 0,43 à 0,75 avec la recherche hybride]]></title>
    <description><![CDATA[Découvrez comment mesurer et améliorer le rappel de recherche dans Elasticsearch en combinant la recherche lexicale BM25 avec les embeddings vectoriels de Jina AI, en utilisant l’API rank_eval pour valider l’amélioration avec des données chiffrées.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/docs/solutions/search/full-text">La recherche lexicale</a> utilisant <a href="https://www.elastic.co/blog/practical-bm25-part-1-how-shards-affect-relevance-scoring-in-elasticsearch">l’algorithme de classement BM25</a> est peu coûteuse, rapide et très efficace pour de nombreuses requêtes. Mais elle présente un inconvénient : les requêtes qui ne partagent pas de jetons avec vos documents. Dans cet article, vous allez mesurer exactement les points faibles de la BM25. Nous utiliserons <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval">l’API d’évaluation de classement</a> (<code>rank_eval</code>) d’Elasticsearch et comblerons cet écart en ajoutant <a href="https://www.elastic.co/search-labs/es/blog/jina-embeddings-v3-elastic-inference-service">des embeddings Jina AI</a> via <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service</a> (EIS). Vous verrez le score de rappel passer de <code>0.43</code> à <code>0.75</code> et vous comprendrez pourquoi.</p><h2>Qu'est-ce que le rappel ?</h2><p>Le <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall">rappel</a> mesure, sur une échelle allant de <code>0</code> à <code>1</code>, le nombre de documents réellement souhaités par vos utilisateurs qui apparaissent quelque part dans vos résultats de recherche. Si une requête doit faire apparaître trois produits et que votre recherche ne renvoie que deux d’entre eux dans le top 10, <code>recall@10 = 0.67</code> pour cette requête. C’est une métrique basée sur des ensembles : la position des documents pertinents dans ces <em>k</em> résultats n’a pas d’importance. Un document pertinent en position 10 compte autant qu’un document en position 1. Un taux de rappel élevé signifie que vous ne perdez pas de résultats pertinents.</p><p>
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ffd147b13705680/6a170a6fe8fbce11a539fc22/b13af2a5d0ca055535d8bfe3dfe4b3d1093ee6da-1457x796.png" alt="Diagramme de Venn illustrant le mode de calcul de Recall@10 en montrant le chevauchement entre tous les documents pertinents et les 10 meilleurs résultats obtenus par BM25, soit un score Recall@10 de 0,40." /><p>Le diagramme montre deux ensembles : tous les documents pertinents (à gauche) et ce que BM25 a réellement récupéré (les 10 premiers, à droite). Seules les intersections comptent pour le rappel, <code>prod_1</code> et <code>prod_2</code> ont été trouvés, tandis que <code>prod_3</code>, <code>prod_4</code> et <code>prod_6</code> ont été totalement manqués. Résultat : <code>Recall@10 = 2/5 = </code><strong><code>0.40</code></strong>.</p><h2>Produits requis</h2><p>Entrons dans le vif du sujet pour mieux comprendre le fonctionnement du rappel. Cette démonstration utilise Python. Vous pouvez la suivre dans le cahier d’accompagnement (<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/relevance-tuning-improving-recall-adding-vectors/notebook.ipynb">notebook.ipynb</a>), où chaque bloc de code est une cellule prête à être exécutée.</p><p>Le code fourni utilise les éléments suivants :</p><ul><li><p>Elasticsearch 9.3+</p></li><li><p>Python 3.10+</p></li></ul>pip install elasticsearch pandas plotly python-dotenv<ul><li><p>Un fichier <code>.env</code> avec vos identifiants Elasticsearch</p></li></ul>ELASTICSEARCH_URL=https://your-cluster-url
ELASTICSEARCH_API_KEY=your-api-key<h2>L’ensemble de données</h2><p>Nous utiliserons un catalogue de produits de 1 000 articles, couvrant des catégories telles que les chaussures, l’électronique, les outils, et bien d’autres.</p><p>Chaque document comporte quatre champs :</p><p>Champ</p><p>Type</p><p>`title`</p><p>Texte</p><p>description</p><p>Texte</p><p>marque</p><p>mot-clé</p><p>Catégorie</p><p>mot-clé</p><p>L’ensemble de données est chargé à partir de <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/relevance-tuning-improving-recall-adding-vectors/dataset.csv"><code>dataset.csv</code></a>.</p><h2>La puissance et les limites de la recherche lexicale</h2><p>BM25 est l’algorithme de classement par défaut d’Elasticsearch et de la plupart des moteurs de recherche. Il attribue des scores aux documents en fonction de la fréquence d’apparition de vos termes de requête dans ceux-ci, ajustée en fonction de la longueur du document et de la fréquence de ces termes dans l’ensemble de l’index. Vous disposez d’<a href="https://www.elastic.co/docs/reference/text-analysis/analyzer-reference">analyseurs</a> en plus : normalisation des minuscules, troncature et suppression des mots vides. Une requête pour « chaussures de course » correspondra à « Chaussures de course » et probablement aussi à « courir ».</p><p>Cette méthode fonctionne bien pour une grande catégorie de requêtes :</p><ul><li><p>« chaussures de course » associe immédiatement les produits dont le titre correspond exactement à ces termes.</p></li><li><p>L’expression « enceinte Bluetooth » fait apparaître des produits audio portables, car les termes apparaissent tels quels.</p></li></ul><p>Les résultats sont déterministes et explicables : un document est bien classé parce que les termes de la requête y apparaissent. La pertinence du débogage est simple.</p><h3>Les cas d’échec</h3><p>Essayons maintenant ces requêtes sur le même catalogue :</p><ul><li><p><strong>« Routine de soins de la peau » :</strong> Le mot « routine » n’apparaît dans aucun titre de produit. BM25 peut correspondre partiellement à la requête « soins de la peau », mais les sérums pour le visage, les huiles corporelles et les crèmes hydratantes sont décrits à l’aide de termes comme « vitamine C », « rétinol » ou « éclaircissant », dont aucun ne correspond à la requête. Les produits qui forment une routine de soins de la peau complète sont dispersés dans l’index sans aucun élément commun permettant de les regrouper.</p></li></ul>ID: B06XX6DS3P, Score: 9.0552, Title: Replenix Retinol Smooth + Tighten Body Lotion - Collagen-Boosting, Regenerating Anti-Aging Body Cream, Reduces Appearance of Stretch Marks, 6.7 oz.

  ID: B08XMPKJ1L, Score: 5.2699, Title: Bio-Oil Skincare Body Oil (Natural) Serum for Scars and Stretchmarks, Face and Body Moisturizer Hydrates Skin, with Organic Jojoba Oil and Vitamin E, For All Skin Types, 6.7 oz

  ID: B01CY764KQ, Score: 5.0057, Title: Nike Up Or Down Men Deodorant - Pack of 2 | Long-Lasting Fragrance, Body Spray Combo for Men | Deodorant for Active Living | Nike Men's Deo Set | Ultimate Odor Protection | Grooming Essentials | Signature Nike Scent | High-Performance Men's Deodorant<ul><li><p><strong>« Accessoires de voyage pour animaux de compagnie » :</strong> il s’agit d’un regroupement de cas d’utilisation, et non d’une catégorie de produits. Un sac kangourou pour chien, un siège auto pour animal et une caisse de voyage sont tous pertinents, mais leurs descriptions parlent de portabilité, de sécurité et de confort plutôt que d’« accessoires de voyage ». BM25 trouve des correspondances pour le terme « animal de compagnie » au sens large, mais ne comporte aucun signal permettant de distinguer les produits spécifiques aux voyages du reste du catalogue pour animaux de compagnie.</p></li></ul>ID: B0BVV7BKTW, Score: 7.4371, Title: Large Foldable Travel Duffel Bag with Shoes Compartment

ID: B07TNPHYNV, Score: 6.6455, Title: 40 Pieces Christmas Bronze Jingle Bells Craft Small Bells

ID: B08R8FRW53, Score: 6.6335, Title: CUBY Dog and Cat Sling Carrier
ID: B08QMCQYGM, Score: 6.5259, Title: YTFGGY Whiteboard Pinstripe Tape 6 Rolls 1/8"
ID: B0CP3LQSWM, Score: 6.2994, Title: Portable Dog Water Bottle 32 Oz<p>Il s'agit d'un <strong>problème de rappel</strong>. Les documents pertinents se trouvent dans votre index. BM25 ne peut tout simplement pas les trouver car les mots de l'utilisateur et les mots du document ne correspondent pas suffisamment étroitement.</p><p>L'ajout de synonymes est utile pour les cas connus. Mais vous ne pouvez pas énumérer toutes les façons dont un utilisateur pourrait exprimer une intention. C'est là que les vecteurs entrent en jeu.</p><h2>Pourquoi il est conseillé de mesurer le rappel</h2><p>Avant de résoudre un problème, il faut le quantifier.</p><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall"><strong>Recall@k</strong></a> mesure combien de documents vos utilisateurs souhaitent réellement voir apparaître dans vos résultats de recherche. Au sens strict :</p>Recall@k = (relevant documents found in top k) / (total relevant documents)<p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-precision"><strong>Precision@k</strong></a> mesure les k premiers résultats et combien sont réellement pertinents :</p>Precision@k = (relevant documents in top k) / k<p>Une grande précision garantit la qualité des résultats obtenus. Dans le commerce électronique, l’absence d’un produit pertinent (faible taux de rappel) est souvent pire que l’affichage d’un résultat légèrement imparfait (précision moindre), car un produit caché est une vente perdue.</p><p>L’API d’Elasticsearch <code>rank_eval</code> vous permet de mesurer les deux de manière systématique. Vous fournissez une liste de requêtes, chacune avec un ensemble de documents évalués, et Elasticsearch calcule les métriques pour vous pour l’ensemble des requêtes.</p><h2>Configuration de l'évaluation</h2><p>L’API <code>rank_eval</code> nécessite un <strong>ensemble de données d’évaluations</strong> : un mappage des requêtes vers les documents pertinents pour chacune d’elles, accompagné d’un grade de pertinence (0 = non pertinent, 1 = pertinent, 2 = très pertinent).</p><p>Dans le cahier, il s’agit de la <a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr#learning-to-rank-judgement-list">liste des jugements</a> :</p>judgments = [
    # Query 1: "running shoes" BM25 handles well (tokens appear in product titles) 
    {"query_id": "q1", "doc_id": "B09NQJFRW6", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B08JMD4LMM", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B08VRJ6F2Q", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B07S8NRRWR", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B01HD620I8", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B07DX86321", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B0968YVLQ8", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B093QJ39ZS", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B096FGSC39", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B01GVQWVV2", "grade": 1, "query": "running shoes"},

    # Query 2: "skincare routine" intent-based, "routine" never appears in product titles
    {"query_id": "q2", "doc_id": "B08XMPKJ1L", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B0BN3WQB92", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B0BT7B7P5T", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B00NPA2WEY", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B06XX6DS3P", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B07PDRD1KT", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B074J7869B", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B08JV31QW4", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B00K3TVJMQ", "grade": 1, "query": "skincare routine"},

    # Query 3: "study desk setup" intent-based, products are desks/stands/organizers
    {"query_id": "q3", "doc_id": "B08CS35J2T", "grade": 2, "query": "study desk setup"},
    {"query_id": "q3", "doc_id": "B09B3LFDXJ", "grade": 2, "query": "study desk setup"},
    {"query_id": "q3", "doc_id": "B07W58LMND", "grade": 1, "query": "study desk setup"},
    {"query_id": "q3", "doc_id": "B0CHYDX91L", "grade": 1, "query": "study desk setup"},

    # Query 4: "pet travel accessories" use-case grouping, products are carriers/crates/seats
    {"query_id": "q4", "doc_id": "B08R8FRW53", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B01MYUYX33", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B003C5RKE4", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B09GF8GBF6", "grade": 1, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B0CP3LQSWM", "grade": 1, "query": "pet travel accessories"},
]<p>Le mélange est intentionnel : <code>q1</code> est une requête que BM25 gère bien (jetons exacts dans les titres des produits), tandis que <code>q2</code>, <code>q3</code>, et <code>q4</code> sont des requêtes basées sur l’intention où l’intention de l’utilisateur est exprimée sous forme de concept plutôt que de mots-clés spécifiques sur les produits.</p><h2>Mesurer le rappel de référence du BM25</h2><p>Commencez par configurer le client Elasticsearch et indexez les données textuelles brutes :</p>import os
import json
import pandas as pd
import plotly.graph_objects as go
from elasticsearch import Elasticsearch, helpers
from dotenv import load_dotenv

load_dotenv()

es = Elasticsearch(
    os.getenv("ELASTICSEARCH_URL"),
    api_key=os.getenv("ELASTICSEARCH_API_KEY")
)

INDEX_NAME = "ecommerce-products"<p>Maintenant, créez la requête <code>rank_eval</code> pour BM25. Chaque requête dans la liste combine une requête avec ses notations :</p>judgments_df = pd.DataFrame(judgments)

bm25_requests = []
for query_id, query_text in (
    judgments_df[["query_id", "query"]].drop_duplicates().values
):
    relevant_docs = judgments_df[judgments_df["query_id"] == query_id]
    ratings = [
        {"_index": INDEX_NAME, "_id": row["doc_id"], "rating": row["grade"]}
        for _, row in relevant_docs.iterrows()
    ]

    bm25_requests.append({
        "id": query_id,
        "request": {
            "query": {
                "multi_match": {
                    "query": query_text,
                    "fields": ["title", "description"]
                }
            }
        },
        "ratings": ratings,
    })

bm25_eval = {
    "requests": bm25_requests,
    "metric": {"recall": {"k": 10, "relevant_rating_threshold": 1}},
}

bm25_result = es.rank_eval(index=INDEX_NAME, body=bm25_eval)
print("BM25 Recall@10:", bm25_result.body["metric_score"])<p>Voici le résultat.</p>BM25 Recall@10: 0.43<p><code>0.43</code> Signifie que sur l’ensemble des quatre requêtes, BM25 ne trouve que 43 % des documents qu’il devrait trouver. Le problème se situe principalement dans les requêtes basées sur l’intention : « routine de soins de la peau » ne trouve pas les sérums pour le visage et les huiles corporelles car le mot « routine » n’apparaît jamais dans les titres des produits, et « accessoires de voyage pour animaux de compagnie » renvoie des produits pour animaux de compagnie hors sujet tout en ne trouvant pas les cages et les caisses de transport dont la description précise les fonctionnalités de portabilité et de sécurité plutôt que d’« accessoires de voyage ».</p><p>Ceci est notre référence. Nous avons maintenant un chiffre à battre.</p><h2>Ajout de la recherche vectorielle avec les embeddings Jina</h2><p><a href="https://www.elastic.co/docs/solutions/search/vector"><code>Vector search</code></a> encode les documents et les requêtes sous forme de vecteurs de grande dimension, composés de centaines ou de milliers de valeurs numériques, chacune encodant une fonctionnalité spécifique des données représentées. Les documents ayant une signification similaire se retrouvent proches les uns des autres dans l’espace vectoriel, même s’ils ne partagent aucun mot. « Équipement de gym » et « kit d’haltères » seront proches l’un de l’autre, car les concepts sont liés. J’ai choisi Elasticsearch comme base vectorielle, car il prend en charge la recherche hybride, ce qui me permet de bénéficier d'emblée à la fois d’une compréhension sémantique et d’une précision par mot-clé.</p><p><a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">EIS</a> inclut une prise en charge prête à l’emploi pour l’intégration de modèles via son <a href="https://www.elastic.co/docs/api/doc/elasticsearch/group/endpoint-inference">API d’inférence</a>.</p><h3>Étape 1 : utiliser les embeddings Jina v5 comme point de terminaison d’inférence</h3>INFERENCE_ENDPOINT_ID = ".jina-embeddings-v5-text-small"<p>Si votre cluster dispose de ressources GPU (disponibles dans Elastic Cloud et Elasticsearch 9.3+), les embeddings sont générés sur GPU, ce qui est nettement plus rapide que l’inférence CPU, et supprime le compromis de performance qui rendait auparavant l’utilisation des vecteurs coûteuses à grande échelle.</p><p>Pourquoi spécifiquement les embeddings Jina ? <a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text">jina-embeddings-v5-text</a> est un modèle multilingue (plus de 119 langues) avec une fenêtre de contexte de 32 000 jetons et une prise en charge <a href="https://arxiv.org/abs/2106.09685">des adaptateurs Low-Rank Adaptation (LoRA)</a> spécifiques à la tâche. Il fonctionne bien pour les courtes descriptions de produits prêtes à l’emploi. En savoir plus sur le modèle <code>jina-embeddings-v5-text</code> <a href="https://huggingface.co/jinaai/jina-embeddings-v5-text-small">ici</a>.</p><h3>Étape 2 : créer l’index avec un champ sémantique</h3>index_mappings = {
    "mappings": {
        "properties": {
            "title": {"type": "text", "copy_to": "semantic_field"},
            "description": {"type": "text", "copy_to": "semantic_field"},
            "brand": {"type": "keyword"},
            "category": {"type": "keyword"},
            "semantic_field": {
                "type": "semantic_text",
                "inference_id": INFERENCE_ENDPOINT_ID,
            },
        }
    }
}

if not es.indices.exists(index=INDEX_NAME):
    es.indices.create(index=INDEX_NAME, body=index_mappings)
    print(f"Created index: {INDEX_NAME}")<p>Le type de champ <a href="https://www.elastic.co/docs/solutions/search/semantic-search/semantic-search-semantic-text"><code>semantic_text</code></a> est ici la clé. C’est une abstraction de niveau supérieur par rapport à <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector"><code>dense_vector</code></a> : vous le dirigez vers un point de terminaison d’inférence, et Elasticsearch se charge de générer automatiquement les plongements.</p><p>La propriété <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/copy-to"><code>copy_to</code></a> sur <code>title</code> et <code>description</code> signifie que le contenu des deux champs est transmis à <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><code>semantic_field</code></a> pour intégration, de sorte qu’un seul vecteur capture la représentation complète du produit.</p><h3>Étape 3 : indexer les produits</h3>def bulk_index(products, index_name):
    actions = []
    for product in products:
        doc_id = product.get("_id")
        source = {k: v for k, v in product.items() if k != "_id"}
        action = {"_index": index_name, "_source": source}
        if doc_id:
            action["_id"] = doc_id
        actions.append(action)

    success, failed = helpers.bulk(es, actions, raise_on_error=False)
    if failed:
        for error in failed:
            print(f"Error: {error}")
    else:
        print(f"Successfully indexed {success} documents")

bulk_index(products, INDEX_NAME)<p>Au moment de l’indexation, Elasticsearch appelle le point de terminaison d’inférence pour chaque document et stocke le vecteur d’intégration résultant dans <code>semantic_field</code>. Aucun code supplémentaire n’est nécessaire de votre côté.</p><h2>Recherche hybride : combinaison de BM25 et de vecteurs avec RRF</h2><p>L'ajout de vecteurs améliore le taux de rappel, mais le recours exclusif aux vecteurs risque d'entraîner une perte de précision pour les requêtes en correspondance exacte. Les résultats pour « chaussures de course » devraient toujours classer en premier les correspondances exactes. La recherche hybride conserve la composante lexicale spécifiquement pour préserver cette précision.</p><p>La recherche hybride avec la <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">Fusion des rangs réciproques</a> (RRF) combine le meilleur des deux :</p><ul><li><p>BM25 gère les requêtes exactes et quasi-exactes avec une grande précision.</p></li><li><p>La recherche sémantique gère les requêtes basées sur l’intention et multilingues avec un rappel élevé.</p></li><li><p>RRF fusionne les deux listes classées en un seul classement.</p></li></ul><p>La formule RRF attribue à chaque document un score basé sur son classement dans chaque liste de résultats :</p>score = sum(1 / (rank_constant + rank))<p>Un document bien classé dans les deux listes obtient un score combiné plus élevé. Le paramètre <code>rank_constant</code> détermine le poids attribué aux documents moins bien classés.</p>hybrid_requests = []

for query_id, query_text in (
    judgments_df[["query_id", "query"]].drop_duplicates().values
):
    relevant_docs = judgments_df[judgments_df["query_id"] == query_id]
    ratings = [
        {"_index": INDEX_NAME, "_id": row["doc_id"], "rating": row["grade"]}
        for _, row in relevant_docs.iterrows()
    ]

    hybrid_requests.append({
        "id": query_id,
        "request": {
            "retriever": {
                "rrf": {
                    "retrievers": [
                        {
                            "standard": {
                                "query": {
                                    "multi_match": {
                                        "query": query_text,
                                        "fields": ["title", "description"],
                                    }
                                }
                            }
                        },
                        {
                            "standard": {
                                "query": {
                                    "match": {
                                        "semantic_field": {"query": query_text}
                                    }
                                }
                            }
                        },
                    ],
                    "rank_window_size": 50,
                    "rank_constant": 5,
                }
            }
        },
        "ratings": ratings,
    })

hybrid_eval = {
    "requests": hybrid_requests,
    "metric": {"recall": {"k": 10, "relevant_rating_threshold": 1}},
}

hybrid_result = es.rank_eval(index=INDEX_NAME, body=hybrid_eval)
print("Hybrid Recall@10:", hybrid_result.body["metric_score"])<p>Voici le résultat.</p>Hybrid Recall@10: 0.75<p>Hybrid s’améliore nettement par rapport à BM25 (<code>0.43</code>) et préserve la précision des requêtes de correspondance exacte, comme « chaussures de course ».</p><h2>Résultats : avant et après</h2><p>Voici un comparatif complet des trois approches :</p>methods = {
    "BM25 (Lexical)": bm25_requests,
    "Hybrid (BM25 + Vectors)": hybrid_requests,
}

recall_metric = {"recall": {"k": 10, "relevant_rating_threshold": 1}}

comparison_data = []
for method_name, requests in methods.items():
    result = es.rank_eval(
        index=INDEX_NAME,
        body={"requests": requests, "metric": recall_metric}
    )
    comparison_data.append({
        "method": method_name,
        "recall@10": result.body["metric_score"]
    })

comparison_df = pd.DataFrame(comparison_data)
print(comparison_df.to_string(index=False))<p>Voici le résultat.</p><p>Méthode</p><p>Recall@10</p><p>BM25 (Lexical)</p><p>0,43</p><p>Hybride (BM25 + vecteurs)</p><p>0,75</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5a1d72b57056fe64/6a170a71c1e8a56c58f882ab/e49f6c10516b0a48a0ad75962c6590ee07311407-700x500.png" alt="Graphique en barres comparant Recall@10 entre la recherche lexicale BM25 et la recherche hybride combinant BM25 avec des vecteurs, montrant que la recherche hybride atteint un rappel significativement plus élevé." /><p>Répartition des données par requête :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt871347f754c866d0/6a170a73839dfa40abdcfeb4/40e36dcb7b34cbf4649c512bcb60cef60f1778a6-700x500.png" alt="Graphique en barres groupées comparant Recall@10 entre la recherche lexicale BM25 et la recherche hybride pour quatre requêtes de produits, illustrant que la recherche hybride obtient systématiquement de meilleurs résultats que la recherche lexicale pour chaque requête." /><h2>Conclusion</h2><p>Tout au long de cet article, nous avons vu que la recherche lexicale BM25 est fiable lorsque les utilisateurs tapent des requêtes exactes, mais qu’elle perd en rappel lorsqu’ils recherchent par intention plutôt que par mots-clés. En utilisant <code>rank_eval</code>, nous avons établi une base reproductible pour mesurer cet écart avec des nombres réels. Ensuite, nous avons ajouté un champ <code>semantic_text</code> alimenté par les embeddings Jina et relancé l’évaluation. Le résultat : la recherche hybride a amélioré le rappel de <code>0.43</code> à <code>0.75</code>, tout en préservant la précision des requêtes à correspondance exacte, bien que la marge réelle dépende de la composition de vos requêtes.</p><p>Le modèle s’étend au-delà de cet exemple : collectez les jugements à partir des requêtes réelles de vos utilisateurs, exécutez <code>rank_eval</code> comme référence, ajoutez <code>semantic_text</code>, puis mesurez à nouveau. Vous saurez exactement ce qui s’est amélioré et de combien.</p><h2>Étapes suivantes</h2><ul><li><p>Découvrez la recherche de rappel et de recherche vectorielle : <a href="https://www.elastic.co/search-labs/blog/recall-vector-search-quantization">quantification du rappel et de la recherche vectorielle</a> par Jeff Vestal</p></li><li><p>Ajoutez le reclassement pour une précision encore meilleure sur les meilleurs résultats</p></li><li><p>Consultez <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">la documentation sur la recherche hybride avec Elasticsearch</a></p></li><li><p>En savoir plus sur <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html"><code>rank_eval</code></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html">l’API</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-relevance-tuning-improve-recall</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-relevance-tuning-improve-recall</guid>
    <category><![CDATA[Recherche hybride]]></category>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt37c9d2971b5a2db3/6a170a75cf4f254223b2d149/492c9b5432a2b9e40cebb3b60f0df019a8c7bf6d-1280x720.png" length="0" type="image/png"/>
    <pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Résolution d'entités avec Elasticsearch, partie 4 : le défi ultime]]></title>
    <description><![CDATA[Relever et évaluer les problématiques de réconciliation d’entités dans un ensemble de données complexe et varié, dont la structure interdit l’usage de méthodes simplifiées ou de contournements.]]></description>
    <content:encoded><![CDATA[<p>Nous avons maintenant vu la résolution intelligente des entités implémentée de deux manières. Les deux approches commencent de la même manière : préparation et extraction des entités, suivies de la récupération des candidats avec Elasticsearch. À partir de là, nous évaluons ces candidats en utilisant un grand modèle de langage (LLM), soit par génération JSON basée sur des invites, soit par appel de fonction, et nous demandons au modèle de fournir une explication transparente de son jugement.</p><p>Comme nous l’avons vu dans l’<a href="https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-function-calling">article précédent</a>, cette régularité, permise par l’appel de fonctions, constitue la pierre angulaire de la fiabilité du système, bien au-delà d’un simple gain d’efficacité. Une fois que nous avons éliminé les erreurs structurelles de la boucle d'évaluation, les résultats sur les scénarios standards (tels que ceux du jeu de données de niveau 4) se sont considérablement améliorés.</p><p>Il reste cependant une interrogation manifeste à laquelle il nous faut répondre :</p><p><em>Cette méthode est-elle toujours viable lorsque les données et les processus s’avèrent véritablement désordonnés ?</em></p><p>En pratique, ce ne sont pas les cas élémentaires qui mettent en défaut les systèmes de réconciliation d’entités. La résolution d’entités s’effondre dès que les noms se heurtent à la diversité des langues, des contextes culturels, des alphabets, des périodes historiques ou des structures administratives différentes. Le système s’effondre quand l’identification repose sur des titres honorifiques, des changements de noms de sociétés ou des translittérations aléatoires, et que seul le contexte environnant permet d’identifier l’entité physique derrière la mention textuelle.</p><p>Donc, pour le dernier billet de cette série, nous avons soumis le système à ce que nous avons appelé <strong>le défi ultime</strong>.</p><h2>Qu’est-ce qui fait de ce test le « défi ultime » ?</h2><p>Nous avons soumis le système à des tests progressifs, en employant des ensembles de données de plus en plus sophistiqués au fil des étapes de validation. Au moment d’atteindre le palier 4, le système gérait déjà des données hybrides mêlant appellations familières, titres honorifiques et variantes linguistiques, exigeant une analyse contextuelle fine. Les tests ont prouvé la pertinence de l’architecture globale, tout en révélant que des erreurs de structure de données, comme des syntaxes JSON incorrectes, bridaient artificiellement les performances de récupération.</p><p>Avec les appels de fonctions en place, nous avions enfin une base stable. Cela nous a donné l'occasion de poser une question plus intéressante :</p><p><em>Un pipeline unifié peut-il gérer </em><em><strong>plusieurs types</strong></em><em> de problèmes de résolution d'entités simultanément ?</em></p><p>Cet ensemble de données de test a été élaboré spécifiquement pour mettre à l’épreuve cette variable critique, en ne laissant aucune place à l’approximation.</p><p>Au lieu de se concentrer sur une seule difficulté (comme les surnoms ou la translittération), cet ensemble de données combine <strong>plus de 50 types de défis distincts</strong>, notamment :</p><ul><li><p>Conventions de dénomination culturelles.</p></li><li><p>Références basées sur les titres.</p></li><li><p>Relations commerciales et changements historiques de nom.</p></li><li><p>Mentions multilingues et systèmes d’écriture croisés.</p></li><li><p>Des défis complexes qui combinent plusieurs des éléments ci-dessus.</p></li></ul><p>L'essentiel, ce n'est pas d'optimiser pour un cas d'utilisation restreint. Il s'agit de vérifier si le <em>modèle de conception</em> tient la route lorsque les règles changent d'une entité à l'autre.</p><h2>L'ensemble de données en un coup d’œil</h2><p>L'ensemble de données du défi ultime consiste en :</p><ul><li><p><strong>50 entités</strong>, couvrant des personnes, des organisations et des institutions.</p></li><li><p><strong>~60 articles</strong>, dont la structure et la complexité linguistique varient.</p></li><li><p><strong>51 catégories de défis distinctes</strong>, regroupées globalement en :</p><ul><li><p>Conventions de dénomination culturelles.</p></li><li><p>Titres et contexte professionnel.</p></li><li><p>Relations commerciales et organisationnelles.</p></li><li><p>Les défis du multilinguisme et de la translittération.</p></li><li><p>Scénarios combinés et cas limites.</p></li></ul></li></ul><p>Plus tôt dans cette série, nous avons vu que l’utilisation de l’IA générative (GenAI) pour créer des jeux de données peut s’avérer être une arme à double tranchant. Sans elle, il serait extrêmement difficile de rassembler des données de test suffisamment vastes et diversifiées. Toutefois, sans un contrôle rigoureux, le modèle incline naturellement vers la simplification des cas de test.</p><p>On a remarqué, lors d’un premier passage de génération, que l’IA avait inséré des mentions telles que « le président russe » comme synonymes directs dans la fiche d’identité de Vladimir Poutine. Bien que cela paraisse logique à première vue, une telle approche invalide le test en supprimant la nécessité de comprendre le contexte pour identifier l’entité. Que se passe-t-il si l’article traite de la Russie des années 1990 ? L’objectif est que l’intelligence du moteur réside dans sa capacité d’inférence contextuelle plutôt que dans une simple table de correspondance statique.</p><p>C'est pourquoi cet ensemble de données a été délibérément conçu pour que les <strong>raccourcis ne fonctionnent pas</strong>. Les alias ne sont pas explicitement énumérés lorsque le système est censé en déduire la signification. Les phrases descriptives ne sont pas pré-liées à des entités. Les bonnes correspondances dépendent souvent du contexte de l'article, et pas seulement du texte local.</p><p><strong>Remarque importante :</strong> bien que nous démontrions les capacités du système dans divers scénarios, il s'agit toujours d'un prototype éducatif. Les systèmes de production gérant la surveillance d'entités sanctionnées dans le monde réel nécessiteraient une validation supplémentaire, des contrôles de conformité, des pistes d'audit et une gestion spécialisée pour les cas d'utilisation sensibles.</p><h2>Pourquoi ces scénarios sont difficiles</h2><p>Dès le premier article de cette série, nous avons introduit un exemple simple mais ambigu : « La nouvelle mise à jour de Swift est arrivée ! » Le défi réside dans le fait que « Swift » peut renvoyer à plusieurs entités du monde réel, selon le contexte. Cet exemple illustre une vérité plus profonde : le langage naturel est intrinsèquement ambigu.</p><p>La résolution d’entités n’est donc pas seulement un problème de correspondance de chaînes de caractères. Nous utilisons instinctivement notre bagage culturel et le contexte immédiat pour interpréter les références, une opération mentale si fluide qu’elle nous semble totalement naturelle.</p><p>Voici quelques cas courants :</p><ul><li><p>L’expression « le président » est une coquille vide si elle n’est pas ancrée dans une géographie et une époque données.</p></li><li><p>Le nom d’une entreprise peut désigner une société mère, une filiale ou une ancienne marque, selon la date à laquelle l’article a été rédigé.</p></li><li><p>Le nom d’une personne peut apparaître dans des ordres différents, des alphabets variés ou des translittérations diverses, selon la langue et la culture.</p></li><li><p>La même phrase peut légitimement faire référence à des entités différentes dans des contextes différents, et le système doit être en mesure de <em>rejeter</em> les correspondances avec autant d'assurance qu'il les accepte.</p></li></ul><p>Aucun système de règles figées ne peut, à lui seul, traiter l’intégralité de ces nuances de manière satisfaisante. Cette approche explique pourquoi ce prototype applique une séparation des préoccupations aussi stricte :</p><ul><li><p>Elasticsearch réduit l'espace réservé aux candidats de manière efficace et transparente.</p></li><li><p>Le LLM n’est utilisé que là où un jugement est requis, et il est contraint de justifier sa décision.</p></li><li><p>La récupération et le raisonnement demeurent des étapes distinctes.</p></li></ul><p>Cette distinction devient encore plus importante à mesure que la diversité des types de défis augmente.</p><h2>Comment le système gère la diversité sans recourir à des cas particuliers</h2><p>L'un des résultats les plus intéressants de cette évaluation est ce qui <em>n’a pas</em> changé :</p><ul><li><p>Nous <strong>n'avons pas</strong> ajouté de logique spéciale pour les noms japonais.</p></li><li><p>Nous <strong>n'avons pas</strong> ajouté de règles personnalisées pour les patronymes arabes.</p></li><li><p>Nous n'avons <strong>pas</strong> ajouté de mapping codé en dur pour les noms d'entreprises historiques.</p></li></ul><p>À la place, le système s’est appuyé sur les mêmes ingrédients fondamentaux présentés plus tôt dans cette série :</p><ul><li><p>Entités enrichies par le contexte et indexées pour la recherche sémantique.</p></li><li><p>La récupération hybride (exacte, alias et sémantique) dans Elasticsearch.</p></li><li><p>Un ensemble restreint et bien défini de candidats.</p></li><li><p>Le jugement du LLM est contraint par l’appel de fonctions et des schémas minimaux.</p></li></ul><p>Cela suggère que la flexibilité du système provient de la <strong>représentation et de l'architecture</strong>, et non d'une collection de règles qui ne cesse de croître.</p><p>Lorsque le système réussit, c’est parce que les bons candidats ont été récupérés et que le LLM dispose d’assez de contexte pour expliquer pourquoi une référence correspond (ou non) à une entité spécifique.</p><h2>Résultats : Comment s’est-il comporté ?</h2><p>Sur l’ensemble de données du défi ultime, le système a produit les résultats globaux suivants :</p><ul><li><p><strong>Précision :</strong> ~91 %</p></li><li><p><strong>Rappel :</strong> ~86 %</p></li><li><p><strong>Score F1 :</strong> ~89 %</p></li><li><p><strong>Taux d'acceptation des LLM :</strong> ~72 %</p></li></ul><h3>Performances selon les types de défis</h3><p>L’analyse des résultats par type de défi révèle des forces et des limites bien précises :</p><p><strong>Les performances les plus solides (un score F1 de 100 %)</strong> ont été observées dans des domaines tels que :</p><ul><li><p>Appariement entre différents systèmes d’écriture (entités commerciales en cyrillique, coréen ou chinois).</p></li><li><p>Scénarios en hébreu (patronymes, titres professionnels, titres religieux, translittération).</p></li><li><p>Hiérarchies d’entreprises (aérospatiale, industrie diversifiée, conglomérats multidivisionnels).</p></li><li><p>Titres professionnels (académiques, militaires, politiques, religieux).</p></li><li><p>Scénarios japonais combinés impliquant plusieurs systèmes d'écriture.</p></li></ul><p><strong>Une performance solide (score F1 de 80 à 99 %)</strong> a été enregistrée dans les catégories suivantes :</p><ul><li><p>Personnalités politiques internationales (98 %).</p></li><li><p>Changements de noms historiques (90 %).</p></li><li><p>Hiérarchies d’entreprise complexes (89 %).</p></li><li><p>Noms de sociétés japonais (93 %).</p></li><li><p>Translittération entre différents systèmes d’écriture (86 %).</p></li><li><p>Patronymes arabes (86 %).</p></li></ul><p>Les <strong>domaines les plus difficiles</strong> sont les suivants :</p><ul><li><p>Translittération avancée (chinois, coréen) : 0 % F1.</p></li><li><p>Certains scénarios japonais (titres honorifiques, ordre des noms, variations du système d’écriture) : ~67 % F1.</p></li><li><p>Quelques scénarios en arabe (noms d'entreprises, références institutionnelles) : ~40 % F1.</p></li></ul><p>Ce qui importe ici, c’est de comprendre <em>pourquoi</em> le système a éprouvé des difficultés dans ces cas précis. Les échecs n’étaient pas dus à une défaillance de l’approche globale, mais à des limitations de composants spécifiques, tout particulièrement le modèle de vecteurs denses utilisé pour la recherche sémantique dans certains scénarios multilingues.</p><p>La recherche et le jugement étant clairement séparés, il n’est pas nécessaire de réécrire le système pour améliorer les performances. L’intégration d’un modèle d’embedding multilingue plus performant, l’enrichissement du contexte des entités ou l’affinement des stratégies de récupération amélioreraient les résultats dans ces catégories sans modifier l’architecture de noyau.</p><p>Du point de vue architectural, c’est le véritable indicateur de réussite.</p><h2>Ce que ces résultats nous révèlent sur l'architecture du système</h2><p>Si l'on considère l'ensemble de la série, quelques tendances se dégagent :</p><ul><li><p><strong>La préparation est plus importante qu'une combinaison intelligente. </strong>L’enrichissement des entités avec leur contexte dès le départ réduit considérablement l’ambiguïté par la suite.</p></li><li><p><strong>Les LLM sont bien plus précieux en tant que juges qu’en tant qu’outils de recherche. </strong>Leur demander d'expliquer <em>pourquoi</em> une correspondance est logique est bien plus efficace que de leur demander de rechercher.</p></li><li><p><strong>La fiabilité permet la précision. </strong>L'appel de fonction n'a pas seulement nettoyé le JSON ; il a débloqué la récupération qui était déjà latente dans l'étape de récupération.</p></li><li><p><strong>La généralisation l’emporte sur la spécialisation. </strong>Un petit nombre d’abstractions bien choisies a permis de gérer des dizaines de types de défis sans avoir recours à une logique personnalisée.</p></li></ul><p>Cette approche explique pourquoi le prototype s’appuie nativement sur Elasticsearch tout en limitant l’usage des modèles de langage à une stricte nécessité. L’objectif n’est pas de se substituer aux moteurs de recherche classiques, mais d’apporter une couche d’explication quand la compréhension du contexte est cruciale.</p><h2>Conclusions</h2><p>L’enjeu final n’était pas d’atteindre des statistiques idéales, mais de s’attaquer à une interrogation bien plus essentielle :</p><p><em>Une architecture transparente, axée sur rechercher et assistée par LLM, peut-elle gérer l'ambiguïté des entités du monde réel sans s'effondrer en règles ou en boîtes noires ?</em></p><p>Pour ce prototype pédagogique, la réponse est oui, avec des réserves explicites concernant la mise en production, la conformité, la surveillance et la qualité des données. Si vous concevez des systèmes devant justifier <em>pourquoi</em> une correspondance d’entités a été établie, ce modèle mérite une attention toute particulière. J’espère que cette série de publications a démontré que la résolution d’entités n’a rien d’un processus mystérieux. Avec une séparation adéquate des responsabilités, la résolution d’entités devient un processus que l’on peut analyser, mesurer et améliorer.</p><p>Ce travail suggère également un modèle d’architecture plus large. On voit apparaître ici un glissement méthodologique important par rapport à l’architecture RAG classique. Au lieu de laisser la recherche alimenter directement la génération, nous introduisons une étape d’évaluation explicite. Le LLM est d’abord utilisé pour juger et vérifier la pertinence des candidats récupérés, et seuls les résultats approuvés sont autorisés à enrichir la génération. Vous pouvez voir cela comme un « Generation-Augmented Retrieval-Augmented Generation with Evaluation », ou GARAGE, parce que tout le monde adore les bons acronymes.</p><p>Quels autres cas d'utilisation pourraient bénéficier de ce modèle ? Les systèmes exigeant de la confiance, de la transparence et un raisonnement défendable sont des candidats naturels pour ce modèle. Les travaux futurs dans ce domaine s’annoncent tout aussi passionnants que les résultats présentés ici, et j’ai hâte de voir comment la communauté s’en emparera pour la suite.</p><h2>Prochaines étapes : À vous de jouer</h2><p>Envie de voir comment ce système relève le défi le plus complexe ? Consultez le <a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,05_ultimate_challenge_v3.ipynb,-Initial%20public%20lab"><strong>carnet de notes Ultimate Challenge</strong></a> pour une présentation complète avec des implémentations réelles, des explications détaillées et des exemples pratiques.</p><p>Le pipeline complet de résolution d'entités démontre les concepts fondamentaux et l'architecture nécessaires à une utilisation en production. Cette structure sert de fondation pour bâtir des outils de veille médiatique capables d’identifier des entités et de justifier chaque correspondance, garantissant ainsi la traçabilité des informations extraites.
</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/entity-resolution-elasticsearch-llm-challenges</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/entity-resolution-elasticsearch-llm-challenges</guid>
    <category><![CDATA[IA]]></category>
    <category><![CDATA[Recherche hybride]]></category>
    <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc58be329ffebcd60/6a17043e47d49c0bc62d88ab/70fb0ff949f6db9ac9b8a28ecb4329ab915ebf46-720x420.png" length="0" type="image/png"/>
    <pubDate>Fri, 13 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Résolution d’entités avec Elasticsearch et les LLM, partie 2 : mise en correspondance d’entités avec le jugement des LLM et la recherche sémantique]]></title>
    <description><![CDATA[Utiliser la recherche sémantique et le jugement transparent des LLM pour la résolution d’entités dans Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>Dans<a href="https://www.elastic.co/search-labs/blog/entity-resolution-llm-elasticsearch"> la Partie 1</a>, nous avons préparé notre liste de surveillance et extrait les mentions d'entités. Nous sommes maintenant prêts à répondre à la question clé : à quelle entité une mention renvoie-t-elle réellement ? Revenons à l’exemple présenté dans le premier article de cette série, qui expliquait pourquoi nous avons besoin de la résolution d’entités : « The Swift update is here ! » Imaginons que ce titre soit accompagné d’un peu plus de contexte :</p><ol><li><p>La nouvelle mise à jour de Swift est arrivée ! Les développeurs sont impatients de tester les nouvelles fonctionnalités.</p></li><li><p>La nouvelle mise à jour de Swift est arrivée ! Le nouvel album sortira le mois prochain.</p></li></ol><p>Avec ce contexte supplémentaire, nous devrions être en mesure d’associer le nom « Swift » à la bonne entité.</p><p>Dans l'<a href="https://www.elastic.co/search-labs/blog/entity-resolution-llm-elasticsearch">article précédent</a>, nous avons constitué notre liste de surveillance et enrichi les entités avec un contexte supplémentaire. En reprenant nos exemples ci-dessus, nous devons disposer au minimum des deux entités suivantes dans la liste : Taylor Swift et le langage de programmation Swift. Nous avons également expliqué comment extraire les mentions d’entités à partir d’un texte. Dans ces deux exemples, la mention extraite serait « Swift ». Avec ces éléments en place — la liste de surveillance enrichie et les entités extraites — nous sommes enfin prêts à introduire la vedette du moment : la mise en correspondance des entités.</p><p><strong>Rappel :</strong> il s’agit d’un prototype pédagogique conçu pour illustrer les concepts de mise en correspondance d’entités. En production, les systèmes peuvent utiliser différents grands modèles de langage (LLM), des règles de correspondance personnalisées, des pipelines d’évaluation spécialisés ou encore des approches d’ensemble combinant plusieurs stratégies de correspondance.</p><h2>Le problème : pourquoi la mise en correspondance est complexe</h2><p>Le langage humain est une chose remarquable. L’une de ses caractéristiques les plus intéressantes est sa créativité sans fin. Nous pouvons générer et comprendre un nombre infini de nouvelles phrases. Dès lors, est-il surprenant que les correspondances exactes soient rares en résolution d’entités ? Les auteurs s’efforcent d’être créatifs dès qu’ils le peuvent. Il serait vite fastidieux de devoir écrire et lire les noms complets chaque fois qu’une entité est mentionnée. Ainsi, si les correspondances exactes sont simples, la réalité est que nous avons besoin d’une approche plus sophistiquée de la résolution d’entités : une approche suffisamment robuste pour gérer au moins une partie de la créativité sans limite des auteurs humains. C’est pourquoi nous décomposons le problème en deux étapes : utiliser Elasticsearch pour récupérer des candidats plausibles à grande échelle, puis recourir à un LLM pour déterminer si ces candidats renvoient réellement à la même entité du monde réel.</p><h2>La solution : une mise en correspondance en trois étapes avec un jugement LLM transparent</h2><p>Nous vivons un changement de paradigme dans notre manière d’utiliser les ordinateurs. Tout comme l’essor d’Internet nous a fait passer d’une informatique localisée à un réseau mondialement connecté, l’IA générative transforme en profondeur la façon dont le contenu, le code et l’information sont créés. En réalité, le prototype pédagogique qui accompagne cette série a été presque entièrement « vibe codé » à l’aide d’un LLM, avec des instructions soigneusement rédigées par l’auteur. Cela ne signifie pas que les LLM atteignent — ou atteindront — le niveau de productivité propre au langage humain, mais cela veut dire que nous disposons désormais d’une ressource puissante pour faciliter la résolution d’entités.</p><p>Un schéma courant avec l'IA générative est la génération augmentée par récupération (RAG). Ici, <em>récupération</em> signifie que l’on récupère des candidats d’entités (et non que l’on génère des réponses), et que le LLM est utilisé exclusivement pour évaluer les correspondances et en expliquer la logique. Bien que je <em>puisse</em> demander à un LLM de prendre en charge l’ensemble du processus de résolution d’entités, de bout en bout, cette approche serait coûteuse, tant en temps qu’en ressources financières. La RAG aide les LLM à accomplir leur tâche en leur fournissant du contexte de manière plus efficace, ce qui leur permet de contribuer plus efficacement à la résolution d’entités.</p><p>Pour la partie récupération de la RAG, nous faisons à nouveau appel à Elasticsearch. Nous identifions d’abord des correspondances potentielles en combinant la correspondance exacte, la correspondance sur des alias et la recherche hybride, qui associe recherche par mots-clés et recherche sémantique. Une fois ces correspondances potentielles identifiées, nous les transmettons à un LLM pour évaluation. Le LLM agit comme évaluateur final des correspondances. Nous demandons également au LLM d’expliquer son raisonnement, un élément différenciant important par rapport à d’autres systèmes de résolution d’entités. Sans ces explications, la résolution d’entités reste une boîte noire ; avec elles, nous pouvons comprendre pourquoi une correspondance est pertinente.</p><h2>Concepts clés : mise en correspondance en trois étapes, recherche hybride et jugement LLM transparent</h2><p><strong>Qu’est-ce que la mise en correspondance en trois étapes ?</strong> Au début de ce projet, nous avons émis l’hypothèse que la recherche sémantique jouerait un rôle clé dans le système, mais toutes les correspondances ne nécessitent pas un niveau de recherche aussi sophistiqué. Afin de trouver des correspondances efficacement, nous adoptons une approche progressive du problème. Tout d’abord, nous vérifions les correspondances exactes à l’aide de la recherche par mots-clés. Si nous trouvons une telle correspondance, le travail est terminé et nous pouvons passer à l’étape suivante. Si la correspondance exacte échoue, nous passons à la correspondance par alias. Dans le prototype, la correspondance par alias est également effectuée à l’aide d’une correspondance exacte sur des mots-clés, par souci de simplicité. En production, cette étape peut être enrichie par des règles de normalisation, de translittération, de correspondance approximative (fuzzy matching) ou par des tables d’alias maintenues. Si, après ces deux premières étapes, aucune correspondance potentielle n’a été trouvée, nous faisons appel à la recherche sémantique via la recherche hybride d’Elasticsearch, utilisant la méthode Reciprocal Rank Fusion (RRF).</p><p><strong>Qu’est-ce que la recherche hybride ?</strong> Dans Elasticsearch, nous pouvons utiliser la recherche sémantique pour identifier des correspondances pertinentes en tenant compte du contexte. Elasticsearch est largement utilisé pour la recherche vectorielle et la récupération hybride. La similarité sémantique est puissante pour capter le sens, mais elle ne remplace pas le filtrage structuré (par exemple, par plages temporelles, emplacements ou identifiants). Elle est souvent inutile lorsqu’une correspondance exacte est disponible. Elasticsearch s’est d’abord imposé grâce à la recherche lexicale, particulièrement efficace lorsque la recherche sémantique n’est pas adaptée. Pour tirer pleinement parti des deux approches, nous combinons la recherche lexicale et la recherche sémantique au sein d’une requête hybride unique. Nous fusionnons ensuite les résultats afin d’identifier les correspondances les plus probables à l’aide de la méthode RRF. Dans le prototype, les deux premiers résultats deviennent des correspondances potentielles pouvant être soumises à l’évaluation du LLM.</p><p><strong>Pourquoi faire appel au jugement LLM ?</strong> Les jugements et explications fournis par le LLM permettent à notre système de gérer l’ambiguïté et le contexte de manière transparente. C’est essentiel pour des cas comme « le président », qui peut désigner plusieurs entités selon le contexte. Cela permet également de gérer efficacement les surnoms et les variations culturelles. Enfin, lorsque nous traitons des tâches critiques — comme l’identification d’entités figurant sur des listes de sanctions — il est indispensable de comprendre pourquoi une correspondance a été acceptée afin de pouvoir faire confiance au système. Point essentiel : le LLM ne parcourt pas l’intégralité du corpus. Il évalue uniquement le petit ensemble de candidats renvoyé par Elasticsearch.</p><h2>Résultats concrets : mise en correspondance avec raisonnement du LLM</h2><p>Un défi majeur pour toute tâche de traitement automatique du langage naturel est la création d’un document de référence, une « answer key » indiquant quels sont les résultats attendus. Sans cela, il est quasiment impossible d’évaluer la performance d’un système sur une tâche donnée. Or, la création d’un tel document peut s’avérer laborieuse. Pour le prototype de résolution d’entités, nous avons de nouveau fait appel à l'IA générative afin de générer des données sur lesquelles nous pourrions effectuer des tests.</p><p>Nous avons d’abord défini plusieurs types de défis, comme les surnoms et la translittération, puis demandé au LLM de créer une collection hiérarchisée de jeux de données, devenant progressivement plus volumineux et plus complexes pour le système. La création des jeux de données s’est révélée moins simple qu’on aurait pu l’espérer. Le LLM avait une forte tendance à « tricher » en rendant la bonne réponse trop facile à trouver. Par exemple, l’un des types de défis portait sur le contexte sémantique. Ce type incluait des cas tels que faire correspondre « auteur russe » à « Leo Tolstoy ». Le LLM a incorrectement défini « auteur russe » comme un alias de « Leo Tolstoy », ce qui supprimait la nécessité d’une recherche hybride pour identifier la correspondance.</p><p>Après plusieurs refactorisations pour corriger ce type de problèmes, nous disposions de cinq niveaux d’ensembles de données. Les niveaux 1 à 4 devenaient progressivement plus volumineux et intégraient davantage de types de défis. Le niveau 5 constituait le « défi ultime », composé des exemples les plus complexes issus de tous les types de défis. L’ensemble des données de test est disponible dans un <a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/comprehensive_evaluation">répertoire d’évaluation complet</a>.</p><p>Pour évaluer notre approche de résolution d’entités basée sur des prompts, nous avons concentré notre analyse sur le jeu de données de niveau 4. Il est important de noter que l’évaluation a été menée dans le cadre d’une expérience contrôlée afin de nous concentrer sur la qualité de mise en correspondance des entités. Les données de la liste de correspondances ont été préalablement enrichies avec du contexte, et les entités ont été extraites de l’article en amont. Cela a permis de s’assurer que l’évaluation se concentrait sur la correspondance plutôt que sur la précision de l’extraction. Cela isole la qualité de correspondance ; les performances de bout en bout dépendraient en outre du rappel d'extraction et de la qualité d'enrichissement.</p><h3>Ensemble de données d’évaluation</h3><p>L'ensemble de données d'évaluation de niveau 4 fournit un test complet des capacités du système : [1]</p><ul><li><p><strong>Entités de la liste de surveillance :</strong> 66 entités couvrant différents types (personnes, organisations, lieux).</p></li><li><p><strong>Articles de test :</strong> 69 articles couvrant des scénarios réels de résolution d’entités.</p></li><li><p><strong>Correspondances attendues :</strong> 206 correspondances attendues pour l'ensemble des articles.</p></li><li><p><strong>Types de défis : </strong>15 types de défis différents mettant à l'épreuve divers aspects de la résolution d'entités.</p></li></ul><p>Les types de défis inclus dans l’ensemble de données sont les suivants :</p><ul><li><p><strong>Surnoms :</strong> « Bob Smith » → « Robert Smith » (sept articles).</p></li><li><p><strong>Titres et titres honorifiques : « Dr. »</strong> Sarah Williams » → « Sarah Williams » (cinq articles).</p></li><li><p><strong>Contexte sémantique :</strong> « auteur russe » → « Leo Tolstoy » (huit articles).</p></li><li><p><strong>Noms multilingues :</strong> traitement des noms dans différentes écritures (six articles).</p></li><li><p><strong>Entités commerciales :</strong> variations de noms d’entreprises (sept articles).</p></li><li><p><strong>Références exécutives : </strong>« PDG de Microsoft » → « Satya Nadella » (cinq articles).</p></li><li><p><strong>Dirigeants politiques :</strong> références basées sur un titre (cinq articles).</p></li><li><p><strong>Initiales :</strong> « J. Smith » → « John Smith » (trois articles).</p></li><li><p><strong>Variations dans l’ordre des noms :</strong> différentes conventions d’ordre des noms (trois articles).</p></li><li><p><strong>Noms tronqués :</strong> correspondances partielles de noms (trois articles).</p></li><li><p><strong>Découpage des noms :</strong> noms séparés dans le texte (trois articles).</p></li><li><p><strong>Espaces ou tirets manquants :</strong> variations de mise en forme (deux articles).</p></li><li><p><strong>Translittération :</strong> correspondance de noms entre différents systèmes d’écriture (deux articles).</p></li><li><p><strong>Défis combinés :</strong> plusieurs défis dans un même article (six articles).</p></li><li><p><strong>Cas d’entreprise complexes :</strong> relations hiérarchiques entre entités commerciales (cinq articles).</p></li></ul><p>Examinons comment la résolution d’entités basée sur des prompts s’est comportée.</p><h3>Performance globale</h3><p>Les résultats montrent un fort potentiel pour l’évaluation des correspondances assistée par LLM, mais ils mettent également en évidence un problème significatif de fiabilité. Chaque paire candidate doit être évaluée par le LLM. Des erreurs dans la sortie structurée peuvent réduire la précision et le rappel, même lorsque la phase de récupération fonctionne correctement.</p><p>Métrique</p><p>Valeur</p><p>Précision</p><p>83,8 %</p><p>Rappel</p><p>62,6 %</p><p>Score F1</p><p>71,7 %</p><p>Nombre total de correspondances trouvées</p><p>344</p><p>Taux d'acceptation des LLM</p><p>44,8 %</p><p>Taux d'erreur</p><p>30,2 %</p><h3>Le problème du taux d'erreur</h3><p>Rappelons que la première étape du prototype consiste à créer des paires de correspondances potentielles à l’aide d’Elasticsearch. Chacune de ces correspondances potentielles doit ensuite être évaluée par le LLM. Pour traiter efficacement l’ensemble de ces correspondances, nous regroupons les appels au LLM par lots. Cela réduit les coûts d’API et la latence, mais augmente également le risque d’obtenir un JSON mal formé en sortie. À mesure que la taille des lots augmente, le JSON devient plus long et plus complexe, ce qui accroît la probabilité que le LLM génère un JSON invalide. C’est de là que provient le taux d’erreur de 30 %. Dans cette évaluation, nous avons utilisé une taille de lot de cinq correspondances par requête. Même avec cette taille de lot conservatrice, nous constatons toujours des échecs d'analyse JSON, ce qui fausse considérablement les résultats de l'évaluation.</p><h2>Prochaine étape : optimiser l’intégration des LLM</h2><p>Maintenant que nous avons mis en correspondance des entités à l’aide de la recherche sémantique et du jugement d’un LLM, nous disposons d’un pipeline complet de résolution d’entités. Cependant, cette approche introduit un nouveau mode de défaillance : le jugement du modèle peut être correct, mais sa sortie inutilisable. Nous pouvons optimiser l’intégration du LLM afin d’améliorer la fiabilité et la rentabilité. Dans le prochain article, nous verrons comment utiliser le function calling pour produire une sortie structurée, garantissant une structure et un typage sûrs, tout en réduisant les erreurs et les coûts.</p><h2>Essayez par vous-même</h2><p>Envie de voir la mise en correspondance d’entités en action ? Consultez le <a href="https://github.com/jesslm/entity-resolution-lab-public/tree/main/notebooks#:~:text=5%20minutes%20ago-,03_entity_matching_v3.ipynb,-Initial%20public%20lab">carnet de notes sur l'appariement des entités</a> pour une présentation complète avec des implémentations réelles, des explications détaillées et des exemples pratiques. Le carnet vous montre exactement comment faire correspondre les entités à l'aide de la recherche en trois étapes, de la recherche hybride avec RRF et du jugement raisonné basé sur le LLM.</p><p><strong>Rappel :</strong> il s’agit d’un prototype pédagogique conçu pour illustrer les concepts. Lors de la mise en œuvre d’un système en production, tenez compte de facteurs supplémentaires tels que la sélection du modèle, l’optimisation des coûts, les exigences en matière de latence, la validation de la qualité, la gestion des erreurs et la supervision — des aspects qui ne sont pas couverts dans ce prototype à visée pédagogique.</p><h2>Remarques</h2><ol><li><p>Ces ensembles de données sont synthétiques et conçus à des fins pédagogiques. Ils reflètent des défis réels, mais ne représentent aucun domaine de production spécifique.</p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-semantic-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-entity-resolution-llm-semantic-search</guid>
    <category><![CDATA[IA]]></category>
    <category><![CDATA[Recherche hybride]]></category>
    <dc:creator><![CDATA[Jessica Moszkowicz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltefc59243d9990405/6a17056ab339d5778f769ebf/473ca4357c7d60f690edbd2a844acda169aca9c3-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 26 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Garantir une précision sémantique avec un score minimum]]></title>
    <description><![CDATA[Améliorez la précision sémantique en utilisant des seuils de score minimum. Cet article présente des exemples concrets de recherche sémantique et hybride. ]]></description>
    <content:encoded><![CDATA[<p>La recherche sémantique a ouvert un monde d'opportunités pour améliorer la pertinence des recherches. Les modèles clairsemés et denses de haute qualité, tels qu'ELSER, E5 et Jina Embedding v4, renvoient des résultats pertinents en fonction du sens des mots, plutôt que de la correspondance de mots-clés. Cependant, la recherche sémantique renvoie parfois des résultats non pertinents en fin de liste ou pour des requêtes dont l'index ne contient aucun résultat pertinent. Cette caractéristique des modèles clairsemés et denses peut induire les utilisateurs en erreur ou gaspiller des jetons précieux pour les grands modèles de langage (LLM).</p><p>Dans cet article, vous apprendrez comment utiliser le paramètre de score minimum pour augmenter la précision de vos résultats de recherche sémantique. Si vous souhaitez tester les exemples fournis dans cet article de blog, accédez au <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/ensuring-semantic-precision-with-minimum-score/ensuring_semantic_precision_with_minimum_score.ipynb">notebook Jupyter associé</a>.</p><h2>Contexte : précision et rappel</h2><p>En matière de pertinence de recherche, la <em>précision </em>et le <em>rappel </em>sont des concepts clés. Nous encourageons vivement les lecteurs qui ne les connaissent pas encore à se familiariser avec ces concepts. Voici un résumé.</p><ul><li><p><strong>Précision</strong> : la fraction des résultats de recherche renvoyés qui sont pertinents pour l'utilisateur.</p></li><li><p><strong>Rappel</strong> : la fraction de tous les documents pertinents du corpus inclus dans l'ensemble des résultats de recherche.</p></li></ul><p>En d'autres termes, la précision consiste à <strong>ne renvoyer </strong>que les résultats pertinents, tandis que le rappel consiste à <strong>renvoyer tous </strong>les résultats pertinents. Comme vous pouvez l'imaginer, ces deux exigences sont souvent contradictoires. La recherche sémantique a généralement un rappel très élevé, mais peut être à la peine en termes de précision. Poursuivez votre lecture pour découvrir comment contourner ce problème.</p><h2>Présentation du paramètre de score minimum</h2><p>Le paramètre "min_score" nous permet d'améliorer la précision en fixant un score minimum, ce qui tronquera le résultat en supprimant toutes les correspondances dont le score est inférieur au seuil défini. Voici un exemple simple :</p>GET search-movies/_search
{
  "retriever": {
    "linear": {
      "min_score": 4,
      "retrievers": [
        ...
      ]
    }
  }
}<h2>Normalisation du score</h2><p>Définir un score minimum est une bonne chose ; cependant, tous les modèles sémantiques ne renvoient pas un score adapté à un seuil statique. ELSER, par exemple, renvoie un score qui n'est pas limité. <a href="https://huggingface.co/intfloat/e5-small#faq">Certains</a> scores de modèles denses sont fortement regroupés et n'ont de sens que dans le contexte de la requête spécifique.</p><p>Pour la plupart des cas de recherche sémantique, nous recommandons d'utiliser une approche de normalisation avant d'appliquer le "min_score". La normalisation garantit que le score du document se situe dans un intervalle défini. Les extracteurs Elasticsearch proposent deux <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/linear-retriever#linear-retriever-normalizers">normalisateurs</a> de ce type, "l2_norm" et "minmax". Le plus couramment utilisé est "minmax", car il est facile à comprendre et fonctionne bien dans de nombreux scénarios. Voici les principales propriétés de "minmax" :</p><ul><li><p>Les scores des documents sont distribués entre 0 et 1.</p></li><li><p>Le document ayant le score le plus élevé est toujours noté 1.</p></li><li><p>Le document ayant obtenu le score le plus bas est toujours noté 0.</p><ul><li><p>Cela peut le rendre moins adapté à la recherche par mots-clés. Voir la section "Recherche hybride" pour plus de détails.</p></li></ul></li></ul><p>Voici un exemple de requête sémantique normalisée avec <code>min_score</code>. La taille de la fenêtre de classement a été augmentée à 500 pour nous permettre de renvoyer une liste plus longue de résultats de recherche, en commençant à 100.</p>GET search-movies/_search
{
  "size": 100,
  "_source": [
    "title", "overview"
  ],
  "retriever": {
    "linear": {
      "rank_window_size": 500,
      "min_score": 0.25,
      "retrievers": [
        {
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "overview_vector",
                  "query": "superhero movie"
                }
              }
            }
          }
        }
      ]
    }
  }
}<p>La taille a été définie sur une valeur plus élevée que celle habituellement observée en production. Cela nous permet de contrôler la qualité des résultats de recherche et de les optimiser.</p><h2>Recherche hybride utilisant l'extracteur linéaire</h2><p>Pour la recherche hybride, l'approche la plus simple consiste à normaliser tous les scores, à leur attribuer des pondérations et à appliquer un score minimal. Notez qu'en choisissant des pondérations dont la somme est égale à 1, le score total reste compris entre 0 et 1. Cela facilite l'interprétation des scores finaux et l'ajustement de <code>min_score</code>. Voici un exemple :</p>GET search-movies/_search
{
  "size": 100,
  "_source": ["title", "overview","keywords"],
  "retriever": {
    "linear": {
      "rank_window_size": 500,
      "min_score": 0.25,
      "retrievers": [
        {
          "weight": 0.6,
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "overview_vector",
                  "query": "superhero movie"
                }
              }
            }
          }
        },
        {
          "weight": 0.4,
          "normalizer": "minmax",
          "retriever": {
            "standard": {
              "query": {
                "multi_match": {
                  "query": "superhero movie",
                  "fields": ["overview","keywords", "title"],
                  "type": "cross_fields",
                  "minimum_should_match": "2"
                }
              }
            }
          }
        }
      ]
    }
  }
}<h2>Recherche hybride à l'aide de la RRF</h2><p>Avec le BM25, nous contrôlons souvent la précision par d'autres moyens, par exemple en utilisant l'opérateur <code>AND</code> ou <code>minimum_should_match</code>. De plus, les requêtes composées de termes uniques, précis et rares entraîneront naturellement des résultats de recherche peu nombreux, souvent tous très pertinents. Cela peut causer les problèmes suivants :</p><ul><li><p>Les résultats situés plus loin dans le classement reçoivent un score normalisé faible dans l'extracteur BM25, même si le score BM25 absolu est proche des meilleurs résultats.</p></li><li><p>Si l'on ajoute un score BM25 très faible au score sémantique, le total peut être considéré comme le score sémantique.</p></li><li><p>L'absence de contribution au score BM25 peut entraîner la suppression du document par le <code>min_score threshold</code>.</p></li></ul><p>Comme solution, nous pouvons plutôt utiliser la fusion des rangs réciproques (RRF) pour combiner les résultats BM25 et sémantiques. RRF contourne la difficulté de comparer les scores de différents algorithmes de recherche en se concentrant plutôt sur la position dans chaque ensemble de résultats. Dans ce scénario, le <code>min_score</code> est uniquement appliqué à l'extracteur sémantique.</p>GET search-movies/_search
{
  "_source": ["title", "overview","keywords"],
  "retriever": {
    "rrf": {
      "rank_window_size": 500,
      "retrievers": [
        {
          "linear": {
            "rank_window_size": 500,
            "min_score": 0.25,
            "retrievers": [
              {
                "normalizer": "minmax",
                "retriever": {
                  "standard": {
                    "query": {
                      "semantic": {
                        "field": "overview_vector",
                        "query": "superhero movie"
                      }
                    }
                  }
                }
              }
            ]
          }
        },
        {
          "standard": {
            "query": {
              "multi_match": {
                "query": "superhero movie",
                "fields": ["overview", "keywords","title"],
                "type": "cross_fields",
                "minimum_should_match": "2"
              }
            }
          }
        }
      ]
    }
  }
}<h2>Conclusion</h2><p>En utilisant <code>min_score</code>, nous avons montré comment réduire le nombre de faux positifs dans nos ensembles de résultats causés par le fort rappel des algorithmes de recherche sémantique. Pour en savoir plus sur les extracteurs, veuillez consulter cet <a href="https://www.elastic.co/search-labs/blog/elasticsearch-retrievers">article de blog</a> et la <a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">documentation d'Elasticsearch</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/semantic-precision-minimum-score</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/semantic-precision-minimum-score</guid>
    <category><![CDATA[Pertinence]]></category>
    <category><![CDATA[Recherche hybride]]></category>
    <dc:creator><![CDATA[Mattias Brunnert]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4a3fba607900049/6a170e0fcdacbf8fe17d2a7a/8b3b5910abfe16d48d309341a0027008b16c4340-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Créer un connecteur ChatGPT avec Elasticsearch pour interroger les issues GitHub]]></title>
    <description><![CDATA[Découvrez comment créer un connecteur ChatGPT personnalisé et déployer un serveur MCP Elasticsearch qui utilise la rechercher hybride pour interroger les issues GitHub internes.]]></description>
    <content:encoded><![CDATA[<p>OpenAI a récemment annoncé la fonctionnalité <a href="https://help.openai.com/en/articles/11487775-connectors-in-chatgpt">connecteurs personnalisés</a> pour ChatGPT sur les plans Pro/Business/Entreprise et Edu. En plus des connecteurs prêts à l'emploi pour accéder aux données sur Gmail, GitHub, Dropbox, etc., il est possible de créer des connecteurs personnalisés en utilisant des serveurs MCP.</p><p>Les connecteurs personnalisés vous donnent la possibilité de combiner vos connecteurs ChatGPT existants avec des sources de données supplémentaires comme Elasticsearch pour obtenir des réponses complètes.</p><p>Dans cet article, nous allons créer un serveur <a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a> qui connecte ChatGPT à un index Elasticsearch contenant des informations sur les issues GitHub internes et les requêtes pull. Cela permet de répondre aux requêtes en langage naturel en utilisant vos données Elasticsearch.</p><p>Nous déploierons le serveur MCP en utilisant <a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a> sur Google Colab avec ngrok pour obtenir une URL publique à laquelle ChatGPT peut se connecter, éliminant ainsi le besoin d'une configuration complexe de l'infrastructure.</p><p>Pour un aperçu complet de MCP et de son écosystème, reportez-vous à la section <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">État actuel du MCP</a>.</p><h2>Prérequis</h2><p>Avant de commencer, vous aurez besoin des éléments suivants :</p><ul><li><p>Cluster Elasticsearch (8.X ou supérieur)</p></li><li><p>Clé API Elasticsearch avec accès en lecture à votre index</p></li><li><p>Compte Google (pour Google Colab)</p></li><li><p>Compte ngrok (fonctionne avec le niveau gratuit)</p></li><li><p>Compte ChatGPT avec un forfait Pro/Entreprise/Business ou Edu</p></li></ul><h2>Comprendre les exigences du connecteur ChatGPT MCP</h2><p>Les connecteurs ChatGPT MCP nécessitent l'implémentation de deux outils : <code>search</code> et <code>fetch</code>. Pour plus de détails, consultez <a href="https://platform.openai.com/docs/mcp#create-an-mcp-server">OpenAI Docs</a>.</p><h3><a href="https://platform.openai.com/docs/mcp#search-tool">Outil de recherche</a></h3><p>Renvoie une liste de résultats pertinents depuis votre index Elasticsearch en fonction d'une requête utilisateur.</p><h4>Ce qu'il reçoit :</h4><ul><li><p>Une chaîne unique contenant la requête en langage naturel de l'utilisateur.</p></li><li><p>Exemple : "Recherchez les issues liées à la migration d'Elasticsearch."</p></li></ul><h4>Ce qu'il renvoie : </h4><ul><li><p>Un objet avec une clé <code>result</code> contenant un tableau d'objets de résultats. Chaque résultat inclut :</p><ul><li><p><code>id</code> - Identifiant de document unique</p></li><li><p><code>title</code> - Titre de l'issue ou de la PR</p></li><li><p><code>url</code> - Lien vers l'issue/la PR</p></li></ul></li></ul><h4>Dans notre implémentation :</h4>return {
    "results": [
        {
            "id": "PR-612",
            "title": "Fix memory leak in WebSocket notification service",
            "url": "https://internal-git.techcorp.com/pulls/612"
        },
        # ... more results
    ]
}<h3><a href="https://platform.openai.com/docs/mcp#fetch-tool">Outil de récupération</a></h3><p>Récupère le contenu complet d'un document spécifique.</p><h4>Ce qu'il reçoit :</h4><ul><li><p>Chaîne unique contenant l'ID du document Elasticsearch extrait du résultat de la recherche</p></li><li><p>Exemple : "Donnez-moi les détails de la PR-578."</p></li></ul><h4>Ce qu'il renvoie :</h4><ul><li><p>Objet de document complet contenant :</p><ul><li><p><code>id</code> - Identifiant de document unique</p></li><li><p><code>title</code> - Titre de l'issue ou de la PR</p></li><li><p><code>text</code> - Description complète du problème/PR et détails</p></li><li><p><code>url</code> - Lien vers l'issue/la PR</p></li><li><p><code>type</code> - Type de document (issue, pull_request)</p></li><li><p><code>status</code> - Statut actuel (ouvert, en cours, résolu)</p></li><li><p><code>priority</code> - Niveau de priorité (faible, moyen, élevé, critique)</p></li><li><p><code>assignee</code> - Personne en charge de l'issue/la PR</p></li><li><p><code>created_date</code> - Date de création</p></li><li><p><code>resolved_date</code> - Date de résolution (le cas échéant)</p></li><li><p><code>labels</code> - Balises associées au document</p></li><li><p><code>related_pr</code> - ID de la requête pull associée</p></li></ul></li></ul>return {
    "id": "PR-578",
    "title": "Security hotfix: Patch SQL injection vulnerabilities",
    "text": "Description: CRITICAL SECURITY FIX for ISSUE-1889. Patches SQL...",
    "url": "https://internal-git.techcorp.com/pulls/578",
    "type": "pull_request",
    "status": "closed",
    "priority": "critical",
    "assignee": "sarah_dev",
    "created_date": "2025-09-19",
    "resolved_date": "2025-09-19",
    "labels": "security, hotfix, sql",
    "related_pr": null
}<p><strong>Remarque</strong> : Cet exemple utilise une structure plate où tous les champs se trouvent au niveau racine. Les exigences d'OpenAI sont flexibles et prennent également en charge les objets de métadonnées imbriqués.</p><h2>Issues GitHub et ensemble de données de PR</h2><p>Pour ce tutoriel, nous allons utiliser un ensemble de données interne de GitHub contenant des issues et des requêtes pull. Ceci représente un scénario dans lequel vous souhaitez interroger des données privées et internes via ChatGPT.</p><p>L'ensemble de données est accessible <a href="https://gist.github.com/TomasMurua/4e7bbdf7a7ebbdffaa663c43578d934a">ici</a>. Et nous mettrons à jour l'index des données à l'aide de l'<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-bulk">API Bulk</a>.</p><p>Cet ensemble de données comprend :</p><ul><li><p>Issues avec description, état, niveau de priorité et personnes en charge</p></li><li><p>Requêtes pull avec modifications de code, révisions et informations de déploiement</p></li><li><p>Relations entre les issues et les PR (p. ex., la PR-578 corrige l'ISSUE-1889)</p></li><li><p>Étiquettes, dates et autres métadonnées</p></li></ul><h3>Mappings de l'index</h3><p>L'index utilise les <a href="https://www.elastic.co/docs/manage-data/data-store/mapping">mappings</a> suivants pour prendre en charge la recherche hybride avec <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-elser">ELSER</a>. Le champ <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">text_semantic</a> est utilisé pour la recherche sémantique, tandis que les autres champs permettent la recherche par mot-clé.</p>{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "title": {
        "type": "text"
      },
      "text": {
        "type": "text"
      },
      "text_semantic": {
        "type": "semantic_text",
        "inference_id": ".elser-2-elasticsearch"
      },
      "url": {
        "type": "keyword"
      },
      "type": {
        "type": "keyword"
      },
      "status": {
        "type": "keyword"
      },
      "priority": {
        "type": "keyword"
      },
      "assignee": {
        "type": "keyword"
      },
      "created_date": {
        "type": "date",
        "format": "iso8601"
      },
      "resolved_date": {
        "type": "date",
        "format": "iso8601"
      },
      "labels": {
        "type": "keyword"
      },
      "related_pr": {
        "type": "keyword"
      }
    }
  }
}<h2>Créer le serveur MCP</h2><p>Notre serveur MCP implémente deux outils conformes aux spécifications d'OpenAI qui utilisent la recherche hybride pour combiner la sémantique et la correspondance de texte pour de meilleurs résultats.</p><h3>Outil de recherche</h3><p>Utilise la recherche hybride avec <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">RRF</a> (fusion des rangs réciproques) qui combine la recherche sémantique avec la correspondance de texte :</p>@mcp.tool()
    async def search(query: str) -&gt; Dict[str, List[Dict[str, Any]]]:
        """
        Search for internal issues and PRs using hybrid search (semantic + text with RRF).
        Returns list with id, title, and url per OpenAI spec.
        """
        if not query or not query.strip():
            return {"results": []}

        logger.info(f"Searching for: '{query}'")

        try:
            # Hybrid search with RRF (Reciprocal Rank Fusion)
            response = es_client.search(
                index=ELASTICSEARCH_INDEX,
                size=10,
                source=["id", "title", "url", "type", "priority"],
                retriever={
                    "rrf": {
                        "retrievers": [
                            {
                                # Semantic search with ELSER
                                "standard": {
                                    "query": {
                                        "semantic": {
                                            "field": "text_semantic",
                                            "query": query
                                        }
                                    }
                                }
                            },
                            {
                                # Text search (BM25) for keyword matching
                                "standard": {
                                    "query": {
                                        "multi_match": {
                                            "query": query,
                                            "fields": [
                                                "title^3",
                                                "text^2",
                                                "assignee^2",
                                                "type",
                                                "labels",
                                                "priority"
                                            ],
                                            "type": "best_fields",
                                            "fuzziness": "AUTO"
                                        }
                                    }
                                }
                            }
                        ],
                        "rank_window_size": 50,
                        "rank_constant": 60
                    }
                }
            )

            results = []
            if response and 'hits' in response:
                for hit in response['hits']['hits']:
                    source = hit['_source']
                    results.append({
                        "id": source.get('id', hit['_id']),
                        "title": source.get('title', 'Unknown'),
                        "url": source.get('url', '')
                    })

            logger.info(f"Found {len(results)} results")
            return {"results": results}

        except Exception as e:
            logger.error(f"Search error: {e}")
            raise ValueError(f"Search failed: {str(e)}")<h3>Points clés :</h3><ul><li><p><strong>Recherche hybride avec RRF</strong> : combine la recherche sémantique (ELSER) et la recherche de texte (BM25) pour de meilleurs résultats.</p></li><li><p><strong>Requête à correspondance multiple</strong> : <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-multi-match-query">Recherches sur plusieurs champs</a> avec boosting (title^3, text^2, assignee^2). Le symbole caret (^) multiplie les scores de pertinence, en privilégiant les correspondances dans les titres plutôt que dans le contenu.</p></li><li><p><strong>Fuzzy matching (correspondance approximative)</strong> : <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/common-options#fuzziness"><code>fuzziness: AUTO</code></a> gère les fautes de frappe et d'orthographe en autorisant les correspondances approximatives.</p></li><li><p><strong>Ajustement des paramètres RRF :</strong></p><ul><li><p><code>rank_window_size: 50</code> - Spécifie le nombre de résultats principaux de chaque récupérateur (sémantique et texte) pris en compte avant la fusion.</p></li><li><p><code>rank_constant: 60</code> - Cette valeur détermine l'influence des documents dans chaque ensemble de résultats sur le classement final.</p></li></ul></li><li><p><strong>Ne renvoie que les champs obligatoires</strong> : <code>id</code>, <code>title</code>, <code>url</code> conformément à la spécification d'OpenAI, et évite d'exposer inutilement des champs supplémentaires.</p></li></ul><h3>Outil de récupération</h3><p>Récupère les détails du document par ID de document, s'il existe :</p>@mcp.tool()
    async def fetch(id: str) -&gt; Dict[str, Any]:
        """
        Retrieve complete issue/PR details by ID.
        Returns id, title, text, url.
        """
        if not id:
            raise ValueError("ID is required")

        logger.info(f"Fetching: {id}")

        try:
            # Search by the 'id' field (not _id) since IDs are stored as a field
            response = es_client.search(
                index=ELASTICSEARCH_INDEX,
                body={
                    "query": {
                        "term": {
                            "id": id  # Search by your custom 'id' field
                        }
                    },
                    "size": 1
                }
            )

            if not response or not response['hits']['hits']:
                raise ValueError(f"Document with id '{id}' not found")

            hit = response['hits']['hits'][0]
            source = hit['_source']

            result = {
                "id": source.get('id', id),
                "title": source.get('title', 'Unknown'),
                "text": source.get('text', ''),
                "url": source.get('url', ''),
                "type": source.get('type', ''),
                "status": source.get('status', ''),
                "priority": source.get('priority', ''),
                "assignee": source.get('assignee', ''),
                "created_date": source.get('created_date', ''),
                "resolved_date": source.get('resolved_date', ''),
                "labels": source.get('labels', ''),
                "related_pr": source.get('related_pr', '')
            }

            logger.info(f"Fetched: {result['title']}")
            return result

        except Exception as e:
            logger.error(f"Fetch error: {e}")
            raise ValueError(f"Failed to fetch '{id}': {str(e)}")<h3>Points clés :</h3><ul><li><p><strong>Recherche par champ d'ID de document</strong> : utilise une requête de terme sur le champ personnalisé <code>id</code></p></li><li><p><strong>Renvoie le document complet</strong> : inclut le champ complet <code>text</code> avec tout le contenu</p></li><li><p><strong>Structure plate</strong> : tous les champs au niveau racine, correspondant à la structure de document d'Elasticsearch.</p></li></ul><h2>Déployer sur Google Colab</h2><p>Nous utiliserons Google Colab pour exécuter notre serveur MCP et ngrok pour l'exposer publiquement afin que ChatGPT puisse s'y connecter.</p><h3>Étape 1 : Ouvrir le notebook Google Colab</h3><p>Accédez à notre notebook préconfiguré <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/elasticsearch-chatgpt-connector">Elasticsearch MCP pour ChatGPT</a>.</p><h3>Étape 2 : Configurer vos identifiants</h3><p>Vous aurez besoin de trois informations :</p><ul><li><p><strong>URL Elasticsearch</strong> : l'<a href="https://www.elastic.co/docs/deploy-manage/deploy/cloud-enterprise/connect-elasticsearch">URL de votre cluster Elasticsearch</a>.</p></li><li><p><strong>Clé API Elasticsearch</strong> : <a href="https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys">clé API</a> avec accès en lecture à votre index.</p></li><li><p><strong>Jeton d'authentification ngrok</strong> : jeton gratuit fourni par <a href="https://ngrok.com/">ngrok</a>. Nous utiliserons ngrok pour exposer l'URL du MCP à l'Internet afin que ChatGPT puisse s'y connecter.</p></li></ul><h4>Obtenir votre token ngrok</h4><ol><li><p>Créez un compte gratuit sur <a href="https://ngrok.com/">ngrok</a></p></li><li><p>Accédez à votre <a href="https://dashboard.ngrok.com/">tableau de bord ngrok</a></p></li><li><p>Copier votre jeton d'authentification</p></li></ol><h4>Ajouter des secrets à Google Colab</h4><p>Dans le notebook Google Colab :</p><ol><li><p>Cliquez sur l'<strong>icône clé</strong> dans la barre latérale gauche pour ouvrir <strong>Secrets</strong>.</p></li><li><p>Ajoutez ces trois secrets :</p></li></ol>ELASTICSEARCH_URL=https://your-cluster.elastic.com:443
ELASTICSEARCH_API_KEY=your-api-key
NGROK_TOKEN=your-ngrok-token<p>3. Activer l'accès aux notebooks pour chaque secret</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5acae97b386277f8/6a17f08f5ea30f74c964b6c2/d5dd6ac19fe816a562c6351fdb0f11369da0e877-609x321.jpg" alt="Ajouter des secrets à Google Colab" /><h3>Étape 3 : Exécuter le notebook</h3><ol><li><p>Cliquez sur <strong>Runtime</strong> (Exécution) puis sur <strong>Run all</strong> (Tout exécuter) pour exécuter toutes les cellules</p></li><li><p>Attendez que le serveur démarre (environ 30 secondes)</p></li><li><p>Recherchez l'URL publique de ngrok dans la sortie</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdd11aacf2deab67c/6a17f091e8fbce81f13a1a41/f185100e8869624bc9e1c7b2b4eb32785e2d89e7-1189x283.png" alt="" /><p>4. La sortie affichera quelque chose comme :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8891d917fdbaaf48/6a17f092abe0f208c7dfeaf6/e02e625e91ed9136454e4401b184575fb03a336e-1052x465.jpg" alt="La sortie de l'exécution d'un notebook dans Google Colab" /><h2>Se connecter à ChatGPT</h2><p>Nous allons maintenant connecter le serveur MCP à votre compte ChatGPT.</p><ol><li><p>Ouvrez ChatGPT et accédez aux <strong>Paramètres</strong>.</p></li><li><p>Accédez à <strong>Connectors</strong> (Connecteurs).Si vous utilisez un compte Pro, vous devez activer le <a href="https://platform.openai.com/docs/guides/developer-mode">mode développeur</a> dans les connecteurs.</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt95efdcb2c39307e7/6a17f094abe0f24d8edfeafa/32c02192912fc0e7e5a52e9399077ba7ae3b4901-739x715.png" alt="Connexion du serveur MPC à un compte ChatGPT" /><p><em>Si vous utilisez ChatGPT Enterprise ou Business, vous devez publier le connecteur sur votre espace de travail.</em></p><p>3. Cliquez sur <strong>Create</strong> (Créer).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4c8fc8dd6033918/6a17f095631730de19585b7b/15c53e5ccc381108a9dc0052cca05bf0fc97679a-755x683.png" alt="Ajouter un connecteur à ChatGPT" /><p><em><strong>Remarque</strong></em><em> : Dans les espaces de travail Business, Entreprise et Edu, seuls les propriétaires, les administrateurs et les utilisateurs ayant activé l'option correspondante (pour Entreprise/Edu) peuvent ajouter des connecteurs personnalisés. Les utilisateurs ayant un rôle de membre standard ne peuvent pas ajouter de connecteurs personnalisés eux-mêmes.</em></p><p><em>Une fois qu'un connecteur est ajouté et activé par un propriétaire ou un utilisateur administrateur, il devient accessible à tous les membres de l'espace de travail.</em></p><p>4. Saisissez les informations requises et votre URL ngrok se terminant par <code>/sse/</code>. Notez le "/" après "sse". Cela ne fonctionnera pas sans cet élément :</p><ul><li><p><strong>Nom :</strong> Elasticsearch MCP</p></li><li><p><strong>Description</strong> : MCP personnalisé pour la recherche et la récupération d'informations GitHub internes.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd716ad0beeeb1d35/6a17f09714d90c11cc79b6d7/162a85705cc8ac48a3f2f665551d513e0719f93d-479x684.png" alt="Créer un connecteur MCP Elastic " /><p>5. Appuyez sur <strong>Créer</strong> pour enregistrer le MCP personnalisé.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt857794237d7d3b5a/6a17f0983e03d729b74f2d54/97eb5fb0a32b86bfadfb35561f698616f217c049-913x629.png" alt="Sauvegarder le connecteur MCP personnalisé en cliquant sur Créer" /><p>La connexion est instantanée si votre serveur est en cours d'exécution. Aucune authentification supplémentaire n'est requise, car la clé API Elasticsearch est configurée sur votre serveur.</p><h2>Tester le serveur MCP</h2><p>Avant de poser des questions, vous devez sélectionner le connecteur que ChatGPT doit utiliser.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1602c48878dc7a9/6a17f09a6df731cca90a0fff/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" alt="Sélection du connecteur que ChatGPT doit utiliser" /><h3>Prompt 1 : Recherchez les issues</h3><p>Demandez : "<strong>Recherchez les issues liées à la migration d'Elasticsearch</strong>", puis confirmez l'appel à l'outil d'action.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c204ceacf897f61/6a17f09c9da390fb1de4657d/cfd781acbff8cd7c8095bbe29224f8b26d581f77-650x375.png" alt="Demandez à ChatGPT « Trouve les problèmes liés à la migration d'Elasticsearch » et confirmez l'appel de l'outil Actions." /><p>ChatGPT appellera l'outil <code>search</code> avec votre requête. Vous pouvez voir qu'il recherche des outils disponibles et se prépare à appeler l'outil Elasticsearch, et confirme auprès de l'utilisateur avant de prendre toute action sur l'outil.</p><h4>Demande d'appel d'outil :</h4>{
  "query": "Elasticsearch migration issues"
}<h4>Réponse de l'outil :</h4>{
  "results": [
    {
      "id": "PR-598",
      "title": "Elasticsearch 8.x migration - Application code changes",
      "url": "https://internal-git.techcorp.com/pulls/598"
    },
    {
      "id": "ISSUE-1712",
      "title": "Migrate from Elasticsearch 7.x to 8.x",
      "url": "https://internal-git.techcorp.com/issues/1712"
    },
    {
      "id": "RFC-045",
      "title": "Design Proposal: Microservices Migration Architecture",
      "url": "https://internal-git.techcorp.com/rfcs/045"
    }
    // ... 7 more results
  ]
}<p>ChatGPT traite les résultats et les présente dans un format conversationnel naturel.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1b4378e7d26b4ad0/6a17f09ddbb4ff4de1fb57bf/9d5b6cff85c7e54ccc2584b8ae96d45495fae8c1-923x1352.png" alt="Comment ChatGPT traite les résultats de la demande d'appel d'outil et de la réponse de l'outil" /><h3>En coulisses</h3><h4>Prompt : "Recherchez les issues liées à la migration d'Elasticsearch"</h4><p>1. Appels de ChatGPT <code>search(“Elasticsearch migration”)</code></p><p>2. Elasticsearch effectue une recherche hybride.</p><ul><li><p>La <strong>recherche sémantique</strong> comprend des concepts tels que "mise à niveau" et "<em>compatibilité des versions</em>".</p></li><li><p>La <strong>recherche de texte</strong> trouve des correspondances exactes pour "<em>Elasticsearch</em>" et "migration".</p></li><li><p><strong>RRF</strong> combine et classe les résultats des deux approches</p></li></ul><p>3. Renvoie les 10 événements les plus pertinents avec <code>id</code>, <code>title</code>, <code>url</code></p><p>4. ChatGPT identifie "<em>ISSUE-1712: migrate from Elasticsearch 7.x to 8.x</em>" (Migrer d'Elasticsearch 7.x vers 8.x) comme résultat le plus pertinent.</p><h3>Prompt 2 : Obtenez tous les détails</h3><p>Demandez : <em><strong>"Donnez-moi les détails de l'ISSUE-1889"</strong></em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8d1a53db8bfe8326/6a17f09f445de966104d021a/5c0db5245535ce67a36056e61e135bddc97ce496-934x629.png" alt="ChatGPT comprend que vous souhaitez obtenir des informations détaillées sur un problème spécifique, appelle l'outil fetch et confirme auprès de l'utilisateur avant d'entreprendre toute action sur l'outil." /><p>ChatGPT comprend que vous souhaitez obtenir des informations détaillées sur un problème spécifique, appelle l'outil <code>fetch</code> et confirme auprès de l'utilisateur avant d'entreprendre des actions sur l'outil.</p><h4>Demande d'appel d'outil :</h4>{
  "id": "ISSUE-1889"
}<h4>Réponse de l'outil :</h4>{
  "id": "ISSUE-1889",
  "title": "SQL injection vulnerability in search endpoint",
  "text": "Description: Security audit identified SQL injection vulnerability in /api/v1/search endpoint. User input from query parameter is not properly sanitized before being used in raw SQL query. Severity: HIGH - Immediate action required Affected Code: - File: services/search/query_builder.py - Line: 145-152 - Issue: String concatenation used instead of parameterized queries Investigation: - @security_team_alice: Confirmed exploitable with UNION-based injection - @sarah_dev: Checking all other endpoints for similar patterns - @john_backend: Found 3 more instances in legacy codebase Remediation: - Rewrite using SQLAlchemy ORM or parameterized queries - Add input validation and sanitization - Implement WAF rules as additional layer - Security regression tests Comments: - @tech_lead_mike: Stop all other work, this is P0 - @sarah_dev: PR-578 ready with fixes for all 4 vulnerable endpoints - @alex_devops: Deployed hotfix to production 2025-09-19 at 14:30 UTC - @security_team_alice: Verified fix, conducting full pentest next week Resolution: All vulnerable endpoints patched. Added pre-commit hooks to catch raw SQL queries. Security training scheduled for team.",
  "url": "https://internal-git.techcorp.com/issues/1889",
  "type": "issue",
  "status": "closed",
  "priority": "critical",
  "assignee": "sarah_dev",
  "created_date": "2025-09-18",
  "resolved_date": "2025-09-19",
  "labels": "security, vulnerability, bug, sql",
  "related_pr": "PR-578"
}<p>ChatGPT synthétise les informations et les présente clairement.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt560958fa3bd212d0/6a17f0a0faa91355ba93c974/410f19f213e94fc4e3c47eeef6e04b69e0c86159-602x462.png" alt="Comment ChatGPT synthétise les informations et les présente " /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcccf35a584e8373b/6a17f0a2505ac3471cad8c2e/54d8ffa117628a1e3afc317c3ab75d4f7731d7ab-767x1600.png" alt="Comment ChatGPT présente les informations" /><h3>En coulisses</h3><h4>Prompt : "Donnez-moi les détails de l'ISSUE-1889"</h4><ol><li><p>Appels ChatGPT <code>fetch(“ISSUE-1889”)</code></p></li><li><p>Elasticsearch extrait le document complet</p></li><li><p>Retourne un document complet avec tous les champs au niveau racine</p></li><li><p>ChatGPT synthétise les informations et répond avec les citations appropriées.</p></li></ol><h2>Conclusion</h2><p>Dans cet article, nous avons créé un serveur MCP personnalisé qui connecte ChatGPT à Elasticsearch à l'aide d'outils MCP de <strong>recherche</strong> et de <strong>récupération</strong> dédiés, permettant de lancer des requêtes en langage naturel sur des données privées.</p><p>Ce modèle MCP fonctionne pour n'importe quel index Elasticsearch, documentation, produit, log ou toute autre donnée que vous souhaitez interroger en langage naturel.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/chatgpt-connector-mcp-server-github-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/chatgpt-connector-mcp-server-github-elasticsearch</guid>
    <category><![CDATA[IA agentique]]></category>
    <category><![CDATA[Recherche hybride]]></category>
    <dc:creator><![CDATA[Tomás Murúa]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1602c48878dc7a9/6a17f09a6df731cca90a0fff/77a6fc1eb263a0eb16aac64f2ecaca5f4ac12ec2-966x568.gif" length="0" type="image/gif"/>
    <pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[La recherche hybride sans prise de tête : simplifier la recherche hybride avec des extracteurs]]></title>
    <description><![CDATA[Découvrez comment simplifier la recherche hybride dans Elasticsearch avec un format de requête à champs multiples pour les extracteurs linéaires et RRF, et créez des requêtes sans aucune connaissance préalable de votre index Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>La <a href="https://www.elastic.co/what-is/hybrid-search">recherche hybride</a> est largement reconnue comme une approche de recherche puissante, combinant la précision et la vitesse de la <a href="https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch#lexical-search---sparse-retrieval">recherche lexicale</a> avec les capacités de langage naturel de la <a href="https://www.elastic.co/what-is/semantic-search">recherche sémantique</a>. Cependant, son application pratique peut s'avérer délicate, nécessitant souvent une connaissance approfondie de votre index et la construction de requêtes verbeuses avec des configurations non triviales. Dans ce blog, nous allons voir comment le <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers#multi-field-query-format">format de requête multi-champs pour les extracteurs linéaires et RRF</a> rend la recherche hybride plus simple et plus accessible, en éliminant les maux de tête courants et en vous permettant de tirer parti de toute sa puissance avec plus de facilité. Nous verrons également comment le format d'interrogation à champs multiples vous permet d'effectuer des recherches hybrides sans aucune connaissance préalable de votre index.</p><h2>Le problème de l'étendue des scores</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3521a6558cd477ca/6a17f174445de94d3c4d0225/c8b49153c47d2cdc233c0d2e440db04711d48ca5-1600x1600.jpg" alt="" /><p>Pour préparer le terrain, examinons l'une des principales raisons pour lesquelles la recherche hybride peut s'avérer difficile : la variation des fourchettes de scores. Notre vieil ami <a href="https://www.elastic.co/elasticon/conf/2016/sf/improved-text-scoring-with-bm25">BM25</a> produit des scores non bornés. En d'autres termes, BM25 peut générer des scores allant de près de 0 à (théoriquement) l'infini. En revanche, les requêtes portant sur les champs <code>dense_vector</code> produiront des scores limités entre 0 et 1. Pour aggraver ce problème, <code>semantic_text</code> obscurcit le type de champ utilisé pour indexer les embeddings, de sorte qu'à moins d'avoir une connaissance détaillée de la configuration de votre index et de votre point de terminaison d'inférence, il peut être difficile de savoir quelle sera la plage de scores de votre requête. Cela pose un problème lorsqu'on essaie d'intercaler des résultats de recherche lexicaux et sémantiques, car les résultats lexicaux peuvent prendre le pas sur les résultats sémantiques, même si ces derniers sont plus pertinents. La solution généralement acceptée pour ce problème est de normaliser les scores avant d'entrelacer les résultats. Elasticsearch dispose de deux outils pour cela, les extracteurs <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/linear-retriever">linéaires</a> et <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/rrf-retriever">RRF</a>.
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt09ed6bf3d25066bd/6a17f1750b0bedbd70dd3686/264481268c8b6ac259e3c257b85431b513f16672-1077x586.png" alt="Comparaison des résultats de recherche avec linéaire/rrf et sans linéaire/rrf" /><p>Le récupérateur <strong>RRF</strong> applique l'<a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">algorithme RRF</a>, en utilisant le rang du document comme mesure de la pertinence et en écartant le score. Étant donné que le score n'est pas pris en compte, les écarts de score ne posent pas de problème.</p><p>L'extracteur <strong>linéaire</strong> utilise une combinaison linéaire pour déterminer le score final d'un document. Il s'agit de prendre le score de chaque composante de la requête pour le document, de le normaliser et de l'additionner pour obtenir le score total. Mathématiquement, l'opération peut être exprimée comme suit :</p>Total Score = 𝚺(N(Sx))<p>Où <code>N</code> est la fonction de normalisation et SX est le score de la requête X. La fonction de normalisation est essentielle ici, car elle transforme le score de chaque requête pour utiliser le même intervalle. Pour en savoir plus sur le retriever linéaire <a href="https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search">, cliquez ici.</a></p><h2>La décomposition</h2><p>Les utilisateurs peuvent mettre en œuvre une recherche hybride efficace à l'aide de ces outils, mais cela nécessite une certaine connaissance de votre index. Prenons un exemple avec l'extracteur linéaire, où nous allons interroger un index avec deux champs :</p>PUT linear_retriever_example
{
  "mappings": {
    "properties": {
      "semantic_text_field": { &lt;1&gt;
        "type": "semantic_text",
        "inference_id": ".multilingual-e5-small-elasticsearch"
      },
      "text_field": { &lt;2&gt;
        "type": "text"
      }
    }
  }
}<p>1. <code>semantic_text_field</code> est un champ <code>semantic_text</code> qui utilise <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5">E5</a>, un modèle d'intégration de texte.</p><p>2. <code>text_field</code> est un champ standard <code>text</code></p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "standard": {
              "query": {
                "match": { &lt;1&gt;
                  "semantic_text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}<p>1. Nous utilisons une requête <code>match</code> sur notre champ <code>semantic_text</code>, dont <a href="https://www.elastic.co/search-labs/blog/semantic-search-match-knn-sparse-vector#we-made-match-happen-in-semantic-search!">la prise en charge a été ajoutée dans Elasticsearch 8.18/9.0.</a></p><p>
Lors de la construction de la requête, nous devons garder à l'esprit que <code>semantic_text_field</code> utilise un modèle d'intégration de texte, de sorte que toute requête sur ce site générera un score entre 0 et 1. Nous devons également savoir que <code>text_field</code> est un champ standard de <code>text</code> et que les requêtes sur ce champ génèreront donc un score non borné. Pour créer un ensemble de résultats pertinents, nous devons utiliser un extracteur qui normalisera les résultats des requêtes avant de les combiner. Dans cet exemple, nous utilisons l'extracteur linéaire avec la normalisation <code>minmax</code>, qui normalise le score de chaque requête à une valeur comprise entre 0 et 1.</p><p>La construction de la requête dans cet exemple est assez simple car seuls deux champs sont concernés. Toutefois, la situation peut se compliquer très rapidement à mesure que l'on ajoute d'autres champs, de types différents. Cela démontre que la rédaction d'une requête de recherche hybride efficace nécessite souvent une connaissance plus approfondie de l'index interrogé, afin que les scores des composantes de la requête soient correctement normalisés avant d'être combinés. Cela constitue un obstacle à l'adoption plus large de la recherche hybride.</p><h3>Regroupement de requêtes</h3><p>Étendons l'exemple : Et si nous voulions interroger un champ <code>text</code> et deux champs <code>semantic_text</code>? Nous pourrions construire une requête comme celle-ci :</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "semantic_text_field_1",
                  "query": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "semantic": {
                  "field": "semantic_text_field_2",
                  "query": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}<p>Cela semble être une bonne chose à première vue, mais il y a un problème potentiel. Désormais, les matchs sur le terrain <code>semantic_text</code> représentent ⅔ du score total :</p>Total Score = N(semantic_text_field_1 score) + N(semantic_text_field_2 score) + N(text_field score)<p>Ce n'est probablement pas ce que vous souhaitez, car cela crée un score déséquilibré. Les effets ne sont peut-être pas très visibles dans un exemple comme celui-ci, qui ne comporte que trois champs, mais ils deviennent problématiques lorsqu'un plus grand nombre de champs sont interrogés. Par exemple, la plupart des index contiennent beaucoup plus de champs lexicaux que de champs sémantiques (c.-à-d. <code>dense_vector</code>, <code>sparse_vector</code>, ou <code>semantic_text</code>). Que se passerait-il si nous interrogions un index comportant 9 champs lexicaux et 1 champ sémantique en utilisant le modèle ci-dessus ? Les correspondances lexicales représenteraient 90% du score, ce qui réduirait l'efficacité de la recherche sémantique.</p><p>Une solution courante consiste à regrouper les requêtes en catégories lexicales et sémantiques et à pondérer les deux de manière égale. Cela permet d'éviter que l'une ou l'autre catégorie ne domine le score total.</p><p>Mettons cela en pratique. À quoi ressemblerait cette approche de requêtes groupées pour cet exemple en utilisant l'outil de recherche linéaire ?</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "retrievers": [
        {
          "retriever": {
            "linear": {
              "retrievers": [
                {
                  "retriever": {
                    "standard": {
                      "query": {
                        "semantic": {
                          "field": "semantic_text_field_1",
                          "query": "foo"
                        }
                      }
                    }
                  },
                  "normalizer": "minmax"
                },
                {
                  "retriever": {
                    "standard": {
                      "query": {
                        "semantic": {
                          "field": "semantic_text_field_2",
                          "query": "foo"
                        }
                      }
                    }
                  },
                  "normalizer": "minmax"
                }
              ]
            }
          },
          "normalizer": "minmax"
        },
        {
          "retriever": {
            "standard": {
              "query": {
                "match": {
                  "text_field": "foo"
                }
              }
            }
          },
          "normalizer": "minmax"
        }
      ]
    }
  }
}<p>Wow, ça devient verbeux ! Vous avez peut-être même dû faire défiler l'écran de haut en bas plusieurs fois pour examiner l'ensemble de la requête ! Ici, nous utilisons deux niveaux de normalisation pour créer les groupes de requêtes. Mathématiquement, elle peut être exprimée comme suit :</p>Total Score = N(N(semantic_text_field_1 score) + N(semantic_text_field_2 score)) + N(text_field score)<p>Ce deuxième niveau de normalisation garantit que les requêtes portant sur les champs <code>semantic_text</code> et <code>text</code> sont pondérées de manière égale. Notez que nous omettons la normalisation de second niveau pour <code>text_field</code> dans cet exemple puisqu'il n'y a qu'un seul champ lexical, ce qui vous évite <em>encore plus</em> de verbosité.</p><p>Cette structure d'interrogation est déjà lourde, et nous n'interrogeons que trois champs. Il devient de plus en plus difficile à gérer, même pour les praticiens chevronnés de la recherche, au fur et à mesure que l'on interroge davantage de champs.</p><h2>Le format d'interrogation à champs multiples</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5d9007d279f0da29/6a17f17714d90c39cf79b6f5/dd04e1686076a574b717c1460acfe4eb79299208-1600x1600.jpg" alt="" /><p>Nous avons ajouté le <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers#multi-field-query-format">format de requête multi-champs</a> pour les extracteurs linéaires et RRF dans Elasticsearch 8.19, 9.1 et <a href="https://www.elastic.co/cloud/serverless">serverless</a> pour simplifier tout cela. Vous pouvez maintenant effectuer la même requête que ci-dessus avec just :</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>Ce qui réduit la requête de 55 lignes à seulement 9 ! Elasticsearch utilise automatiquement les mappages d'index pour :</p><ul><li><p>Déterminer le type de chaque champ interrogé</p></li><li><p>Regrouper chaque champ dans une catégorie lexicale ou sémantique</p></li><li><p>Pondérer chaque catégorie de manière égale dans la note finale</p></li></ul><p>Cela permet à n'importe qui d'exécuter une requête de recherche hybride efficace sans avoir besoin de connaître les détails de l'index ou les points de terminaison d'inférence utilisés.</p><p>Lorsque vous utilisez la méthode RRF, vous pouvez omettre le site <code>normalizer</code>, car le rang est utilisé comme indicateur de la pertinence :</p>GET rrf_retriever_example/_search
{
  "retriever": {
    "rrf": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field" ],
      "query": "foo"
    }
  }
}<h2>Renforcement par champ</h2><p>Lors de l'utilisation de l'extracteur linéaire, vous pouvez appliquer un boost par champ pour ajuster l'importance des correspondances dans certains champs. Par exemple, disons que vous interrogez quatre champs : deux champs <code>semantic_text</code> et deux champs <code>text</code>:</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1", "semantic_text_field_2", "text_field_1", "text_field_2" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>Par défaut, chaque champ est pondéré de manière égale dans son groupe (lexical ou sémantique). La répartition des points est la suivante :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdc6e197e5ff7b68d/6a17f179505ac32834ad8c46/ba31c76189e3a1e5b1638437ccf0528aafec2598-1600x549.png" alt="Comparaison des groupes d'interrogation et des scores sur le terrain" /><p>En d'autres termes, chaque champ représente 25% du score total.</p><p>Nous pouvons utiliser la syntaxe <code>field^boost</code> pour ajouter un boost par champ à n'importe quel champ. Appliquons un boost de 2 à <code>semantic_text_field_1</code> et <code>text_field_1</code>:</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_1^2", "semantic_text_field_2", "text_field_1^2", "text_field_2" ]
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>La répartition des points est maintenant la suivante :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltccc9ed7d6e865a5e/6a17f17abe6086a31d004882/de20e555d52f914bf483a048d056f54f4fece757-1600x549.png" alt="Modification du poids des champs avec la recherche par réf. et la recherche hybride" /><p>Chaque groupe de requêtes est toujours pondéré de manière égale, mais la pondération des champs à l'intérieur des groupes a changé :</p><ul><li><p><code>semantic_text_field_1</code> est 66% du score du groupe de requêtes sémantiques, 33% du score total</p></li><li><p><code>text_field_1</code> est 66% du score du groupe de requêtes lexicales, 33% du score total</p></li></ul><p>ℹ️ Notez que la fourchette de score total ne changera pas lorsqu'une majoration par champ est appliquée. Il s'agit d'un effet secondaire voulu de la normalisation des scores, qui garantit que les scores des requêtes lexicales et sémantiques restent directement comparables entre eux.</p><p>ℹ️ Le boosting par champ peut également être utilisé avec le récupérateur RRF dans Elasticsearch 9.2+.</p><h3>Résolution sur les caractères génériques</h3><p>Vous pouvez utiliser le caractère générique <code>*</code> dans le paramètre <code>fields</code> pour faire correspondre plusieurs champs. Si l'on reprend l'exemple ci-dessus, cette requête est fonctionnellement équivalente à l'interrogation explicite des sites<code>emantic_text_field_1</code>, <code>semantic_text_field_2</code> et <code>text_field_1</code>:</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "fields": [ "semantic_text_field_*", "*_field_1" ],
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>Il est intéressant de noter que le modèle <code>*_field_1</code> correspond à la fois à <code>text_field_1</code> et à <code>semantic_text_field_1</code>. La requête sera exécutée comme si chacun des champs avait été explicitement interrogé. Le fait que le site <code>semantic_text_field_1</code> corresponde aux deux modèles ne pose pas de problème ; tous les noms de champ correspondant sont dédupliqués avant l'exécution de la requête.</p><p>Vous pouvez utiliser les caractères génériques de différentes manières :</p><ul><li><p>Correspondance des préfixes (ex : <code>*_text_field</code>)</p></li><li><p>Correspondance en ligne (ex : <code>semantic_*_field</code>)</p></li><li><p>Correspondance des suffixes (ex : <code>semantic_text_field_*</code>)</p></li></ul><p>Vous pouvez également utiliser plusieurs caractères génériques pour appliquer une combinaison des éléments ci-dessus, par exemple <code>*_text_field_*</code>.</p><h3>Champs de requête par défaut</h3><p>Le format d'interrogation à champs multiples vous permet également d'interroger un index dont vous ignorez tout. Si vous omettez le paramètre <code>fields</code>, il interrogera tous les champs spécifiés par le <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules">paramètre d'indexation index.query.default_field</a>:</p>GET linear_retriever_example/_search
{
  "retriever": {
    "linear": {
      "query": "foo",
      "normalizer": "minmax"
    }
  }
}<p>Par défaut, <code>index.query.default_field</code> est défini comme <code>*</code>. Ce caractère générique permet de résoudre tous les types de champs de l'index qui prennent en charge les requêtes de termes, ce qui est le cas de la plupart d'entre eux. Les exceptions sont les suivantes :</p><ul><li><p><code>dense_vector</code> champs</p></li><li><p><code>rank_vector</code> champs</p></li><li><p>Champs de géométrie : <code>geo_point</code>, <code>shape</code></p></li></ul><p>Cette fonctionnalité est particulièrement utile lorsque vous souhaitez effectuer une recherche hybride sur un index fourni par un tiers. Le format d'interrogation à champs multiples vous permet d'exécuter une requête appropriée de manière simple. Il suffit d'exclure le paramètre <code>fields</code> pour que tous les champs applicables soient interrogés.</p><h2>Conclusion</h2><p>Le problème de la plage de scores peut faire de la recherche hybride efficace un casse-tête à mettre en œuvre, en particulier lorsque l'on ne dispose que de peu d'informations sur l'index interrogé ou sur les points de terminaison d'inférence utilisés. Le format d'interrogation à champs multiples pour les extracteurs linéaires et RRF atténue cette difficulté en intégrant une approche de recherche hybride automatisée, basée sur le regroupement de requêtes, dans une API simple et facile d'accès. Des fonctionnalités supplémentaires, telles que le renforcement par champ, la résolution des caractères génériques et les champs de requête par défaut, permettent d'étendre les fonctionnalités à de nombreux cas d'utilisation.</p><h2>Essayez le format d'interrogation à champs multiples dès aujourd'hui</h2><p>Vous pouvez tester les extracteurs linéaires et RRF avec le format de requête multi-champs dans des projets Elasticsearch <a href="https://www.elastic.co/cloud/serverless">Serverless</a> entièrement gérés avec un <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/create-serverless-project">essai gratuit</a>. Il est également disponible en version stack à partir de 8.19 &amp; 9.1.</p><p>Démarrez en quelques minutes sur votre environnement local à l'aide d'une simple commande :</p>curl -fsSL https://elastic.co/start-local | sh<p></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hybrid-search-multi-field-query-retrievers-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hybrid-search-multi-field-query-retrievers-elasticsearch</guid>
    <category><![CDATA[Recherche hybride]]></category>
    <category><![CDATA[Pertinence]]></category>
    <dc:creator><![CDATA[Mike Pellegrini]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89e066372c549595/6a17f17c3e9e457c38ba1583/4494f98ae3958bbdbc6171df9677fc4d65ec5640-1536x1024.png" length="0" type="image/png"/>
    <pubDate>Thu, 27 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Vous savez, pour le contexte - Partie III : La puissance de la recherche hybride dans l'ingénierie contextuelle]]></title>
    <description><![CDATA[Découvrez comment utiliser l'ingénierie contextuelle et la recherche hybride pour améliorer la précision des résultats de l'IA avec des agrégations, RBAC et des signaux sans contenu.]]></description>
    <content:encoded><![CDATA[<p>Nous avons abordé la recherche hybride<a href="https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai">(partie I</a>) et l'ingénierie contextuelle<a href="https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai">(partie II)</a>; nous allons maintenant voir comment ces deux techniques fonctionnent ensemble pour fournir un contexte ciblé aux opérations de RAG et d'IA agentique.</p><h2>La recherche n'est pas morte, elle s'est simplement déplacée</h2><p>Nous sommes donc passés d'une recherche de contexte à l'aide d'une zone de texte et de l'utilisation des informations (le contexte) renvoyées pour construire les réponses nous-mêmes, à l'utilisation du langage naturel pour dire à un agent ce que nous voulons et lui permettre de rechercher et de compiler automatiquement la réponse pour nous. Nombreux sont ceux qui, dans le monde de la technologie, soulignent ce changement et proclament que "la recherche est morte" (certes, le monde du référencement et des mots publicitaires est en train de <a href="https://www.pewresearch.org/short-reads/2025/07/22/google-users-are-less-likely-to-click-on-links-when-an-ai-summary-appears-in-the-results/">changer</a>: les <a href="https://www.wired.com/story/goodbye-seo-hello-geo-brandlight-openai/">GEO</a>, par exemple), mais la recherche reste absolument essentielle pour les opérations agentiques - elle est simplement réalisée en grande partie à l'abri des regards par le biais d'outils.</p><p>Auparavant, les humains étaient les principaux arbitres de la pertinence subjective : chaque utilisateur a ses propres raisons d'effectuer une recherche, et son expérience personnelle influence la précision relative des résultats. Si nous voulons que les agents parviennent à la même conclusion (ou à une meilleure conclusion) que nous, nous devons nous assurer que les informations contextuelles auxquelles ils ont accès sont aussi proches que possible de notre intention subjective. Nous devons concevoir le contexte dans lequel nous fournissons les LLM en fonction de cet objectif !</p><h2>Générer du contexte avec la recherche hybride</h2><p>Je vous rappelle que la recherche hybride d'Elastic combine les points forts de la recherche traditionnelle par mot-clé (flexibilité syntaxique, précision des mots-clés et évaluation de la pertinence) avec la compréhension sémantique de la recherche par similarité vectorielle, et offre plusieurs techniques de reclassement. Cette synergie (il n'y a jamais eu d'usage plus vrai de ce mot !) permet d'obtenir des résultats très pertinents, avec des requêtes qui peuvent être beaucoup plus nuancées dans la manière dont elles ciblent le contenu. Il ne s'agit pas seulement d'appliquer la pertinence subjective à l'<em>une des</em> étapes de la recherche ; il s'agit en fait d'inclure la notation de la pertinence dans la première étape de la recherche, ainsi que tous les autres modes à la fois.</p><h3>Précision supérieure &amp; efficacité</h3><p>L'utilisation d'une plateforme de données capable de fournir des services de recherche, d'extraction et de reclassement distribués en tant que principal moteur de recherche contextuelle est très judicieuse. Vous pouvez utiliser une syntaxe d'interrogation avancée pour ajouter la composante manquante de l'intention subjective et filtrer le contenu qui pourrait distraire ou brouiller la valeur des informations contextuelles renvoyées. Vous pouvez sélectionner l'une des options syntaxiques individuelles disponibles ou combiner les modalités dans une recherche unique qui cible chaque type de données de la manière qu'elle comprend le mieux, puis les combiner ou les réordonner avec le reranking. Vous pouvez filtrer la réponse pour qu'elle ne contienne que les champs/valeurs que vous souhaitez, en évitant les données superflues. Au service des agents, cette souplesse de ciblage vous permet de créer des outils extrêmement précis dans la manière dont ils récupèrent le contexte.</p><h3>Raffinement du contexte (agrégations et signaux non liés au contenu)</h3><p>Les agrégations peuvent être particulièrement utiles pour façonner le contenu d'un outil dans la fenêtre contextuelle. Les agrégations fournissent naturellement des faits numériques sur la forme des données contextuelles renvoyées, ce qui permet aux LLM de raisonner plus facilement et avec plus de précision. Les agrégations pouvant être imbriquées hiérarchiquement, il est facile d'ajouter des détails à plusieurs niveaux pour que le mécanisme d'apprentissage tout au long de la vie génère une compréhension plus nuancée. Les agrégations peuvent également faciliter la gestion de la taille de la fenêtre contextuelle - vous pouvez facilement réduire le résultat d'une requête de 100 000 documents à quelques centaines de tokens d'informations agrégées.</p><p>Les signaux non liés au contenu sont les indicateurs inhérents à vos données qui vous donnent une vue d'ensemble de ce que vous regardez ; il s'agit des caractéristiques supplémentaires des résultats, comme la popularité, la fraîcheur, la géolocalisation, les catégories, la diversité des hôtes ou les fourchettes de prix. Ces éléments d'information peuvent être utiles à l'agent pour évaluer l'importance du contexte qu'il a reçu. Quelques exemples simples permettent d'illustrer au mieux ce propos :</p><ul><li><p><strong>Renforcer le contenu récemment publié et populaire</strong> - Imaginez que vous disposiez d'une base de connaissances contenant des articles. Vous souhaitez trouver des articles pertinents par rapport à la requête d'un utilisateur, mais vous voulez également favoriser les articles qui sont récents et qui ont été jugés utiles par d'autres utilisateurs (par exemple, qui ont un nombre élevé de "likes" ). Dans ce scénario, nous pouvons utiliser une recherche hybride pour trouver les articles pertinents, puis les classer en fonction de leur date de publication et de leur popularité.</p></li><li><p><strong>Recherche dans le domaine du commerce électronique avec ajustement des ventes et des stocks</strong> - Dans le cadre du commerce électronique, vous souhaitez montrer aux clients les produits qui correspondent à leur recherche, mais vous voulez également promouvoir les produits qui se vendent bien et qui sont en stock. Vous pouvez également déclasser les produits dont le stock est faible afin d'éviter la frustration des clients.</p></li><li><p><strong>Priorité aux problèmes de haute gravité dans un système de suivi des bogues</strong> - Pour une équipe de développement de logiciels, lorsqu'elle recherche des problèmes, il est essentiel de faire apparaître en premier les problèmes de haute gravité, de haute priorité et ceux qui ont été récemment mis à jour. Vous pouvez utiliser des signaux secondaires tels que "criticité" et "le plus discuté" pour pondérer les différents facteurs de manière indépendante, en veillant à ce que les questions les plus critiques et les plus activement discutées soient placées en tête.</p></li></ul><p>Ces exemples de requêtes et d'autres sont disponibles dans la <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/you-know-for-context/">page de contenu</a> Elasticsearch Labs qui les accompagne.</p><h3>Renforcement de la sécurité</h3><p>Un avantage essentiel de l'exploitation d'une couche de vitesse alimentée par la recherche telle qu'Elastic pour l'ingénierie contextuelle est son cadre de sécurité intégré. La plateforme d'Elastic garantit que le contexte fourni aux opérations d'IA agentique et générative respecte et protège les informations privées sensibles grâce à un contrôle d'accès granulaire basé sur les rôles (RBAC) et un contrôle d'accès basé sur les attributs (ABAC). Cela signifie que non seulement les requêtes sont traitées avec efficacité, mais aussi que les résultats sont filtrés en fonction des autorisations spécifiques de l'agent ou de l'utilisateur à l'origine de la demande.</p><p>Les agents s'exécutent en tant qu'utilisateur authentifié, de sorte que la sécurité est implicitement appliquée par le biais des fonctions de sécurité intégrées à la plateforme :</p><ul><li><p><strong>Permissions précises :</strong> Définissez l'accès au niveau du document, du champ ou même du terme, en veillant à ce que les agents d'intelligence artificielle ne reçoivent que les données qu'ils sont autorisés à consulter.</p></li><li><p><strong>Contrôle d'accès basé sur les rôles (RBAC) :</strong> Attribuer des rôles aux agents ou aux utilisateurs, en leur donnant accès à des ensembles de données ou à des fonctionnalités spécifiques en fonction des responsabilités qu'ils ont définies.</p></li><li><p><strong>Contrôle d'accès basé sur les attributs (ABAC) :</strong> Mettre en œuvre des politiques d'accès dynamiques basées sur les attributs des données, de l'utilisateur ou de l'environnement, permettant une sécurité hautement adaptable et consciente du contexte.</p></li><li><p><strong>Sécurité au niveau du document (DLS) et sécurité au niveau du champ (FLS) :</strong> Ces capacités garantissent que, même au sein d'un document récupéré, seules les parties autorisées sont visibles, empêchant ainsi l'exposition d'informations sensibles.</p></li><li><p><strong>Intégration avec la sécurité de l'entreprise :</strong> Intégration transparente avec les systèmes de gestion des identités existants (tels que LDAP, SAML, OIDC) afin d'appliquer des politiques de sécurité cohérentes dans l'ensemble de l'entreprise.</p></li></ul><p>En intégrant ces mesures de sécurité directement dans le mécanisme de récupération du contexte, Elastic agit comme un gardien sécurisé, garantissant que les agents d'intelligence artificielle opèrent dans des limites de données définies, empêchant l'exposition de données non autorisées et maintenant la conformité avec les réglementations en matière de confidentialité des données. Cela est primordial pour instaurer la confiance dans les systèmes d'IA agentique qui traitent des informations confidentielles ou exclusives.</p><p>En outre, l'utilisation d'une couche de vitesse unifiée sur les sources de données de l'entreprise permet d'alléger les charges de requêtes ad hoc inattendues sur ces référentiels que les outils agentiques créeraient. Vous disposez d'un lieu unique pour tout rechercher en temps quasi réel, et d'un lieu unique pour appliquer les contrôles de sécurité et de gouvernance.</p><h2>Outils hybrides basés sur la recherche</h2><p>La plateforme Elastic comporte certaines fonctionnalités de base (et d'<a href="https://www.elastic.co/blog/whats-new-elastic-9-2-0">autres sont en cours d'élaboration</a>) qui donnent un coup de fouet à l'ingénierie contextuelle. L'essentiel est que la plateforme offre une multitude de moyens de réaliser des choses, avec la flexibilité de s'adapter, de changer et d'étendre les méthodes au fur et à mesure que l'écosystème de l'IA progresse.</p><h3>Présentation de l'Agent Builder</h3><p>Elastic <a href="https://www.elastic.co/elasticsearch/agent-builder">Agent Builder</a> est notre première incursion dans le domaine des outils d'intelligence artificielle conçus pour dialoguer avec les données que vous stockez déjà dans Elastic. Agent Builder offre une interface de chat qui permet aux utilisateurs de créer et de gérer leurs propres agents et outils dans Kibana. Il est livré avec des serveurs MCP et A2A intégrés, des API programmatiques et un ensemble d'outils système prédéfinis pour l'interrogation et l'exploration des index Elasticsearch, ainsi que pour la génération de requêtes ES|QL à partir du langage naturel. Agent Builder vous permet de créer des outils personnalisés qui ciblent et sculptent les données contextuelles renvoyées à l'agent par le biais d'une syntaxe de requête <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> expressive.</p><p>Comment ES|QL effectue-t-il la recherche hybride ? La capacité de base est obtenue par la combinaison du type de champ <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text">semantic_text</a> <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fork">et des</a>commandes<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fuse">FORK/FUSE (FUSE utilise</a> <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">par défaut RRF</a> pour fusionner les résultats de chaque fourchette). Voici un exemple simple de recherche de produit fictif :</p>FROM products
| FORK
  (MATCH description "high performance gaming laptop" | EVAL search_type = "bm25"),
  (MATCH description_semantic "high performance gaming laptop" | EVAL search_type = "semantic")
| FUSE 
| LIMIT 20
| KEEP product_name, description, _score, search_type<p>La clause <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval">EVAL</a> incluse dans chacune des branches FORK de l'exemple ci-dessus n'est pas strictement nécessaire ; elle n'est incluse que pour démontrer comment vous pouvez suivre la modalité de recherche à partir de laquelle un résultat donné a été retourné.</p><h3>Recherche de modèles</h3><p>Supposons que vous souhaitiez faire pointer vos propres outils agentiques externes vers votre déploiement Elastic. Au lieu d'ES|QL, vous souhaitez utiliser des extracteurs à plusieurs niveaux ou réutiliser la syntaxe DSL existante que vous avez développée, et vous voulez également pouvoir contrôler les entrées acceptées par la requête, la syntaxe utilisée pour exécuter la recherche et les champs renvoyés dans le résultat. Les <a href="https://www.elastic.co/docs/solutions/search/search-templates">modèles de recherche</a> permettent aux utilisateurs de définir des structures prédéfinies pour les modèles de recherche courants, ce qui améliore l'efficacité et la cohérence de la recherche de données. Ceci est particulièrement bénéfique pour les outils agentiques qui interagissent avec les API de recherche, car ils aident à normaliser le code standard et permettent une itération plus rapide de la logique de recherche. Et si vous devez modifier l'un de ces facteurs, il vous suffit de mettre à jour le modèle de recherche et voilà, les changements sont appliqués. Si vous cherchez un exemple de modèles de recherche en action avec des outils agentiques, jetez un coup d'œil au blog d'Elasticsearch Labs "<a href="https://www.elastic.co/search-labs/blog/mcp-intelligent-search">MCP for intelligent search</a>", qui utilise un modèle de recherche derrière un appel d'outil à partir d'un serveur MCP externe.</p><h3>Flux de travail intégrés (FTW !)</h3><p>L'une des choses les plus difficiles à gérer dans notre nouveau monde d'IA agentique est la nature non déterministe des agents "raisonnants" semi-autonomes et autodirigés. L'ingénierie contextuelle est une discipline essentielle de l'IA agentique : il s'agit des techniques qui permettent de limiter les conclusions possibles de notre agent à ce que nous connaissons de la vérité de terrain. Même avec une fenêtre contextuelle très précise et pertinente (lorsque nous sortons du domaine des faits numériques), il nous manque toujours cette petite assurance que la réponse de l'agent est entièrement reproductible et fiable.</p><p>Lorsque vous soumettez plusieurs fois la même demande à un agent, les réponses peuvent être <em>essentiellement</em> les mêmes, avec <em>juste</em> une petite différence dans la réponse. C'est généralement bien pour les requêtes simples, peut-être à peine perceptible, et nous pouvons essayer de façonner le résultat à l'aide de techniques d'ingénierie contextuelle. Mais plus les tâches que nous demandons à nos agents sont complexes, plus il y a de chances qu'une ou plusieurs sous-tâches introduisent une variance qui modifie légèrement le résultat final. La situation s'aggravera probablement à mesure que nous commencerons à nous appuyer davantage sur les communications entre agents, et ces écarts deviendront cumulatifs. Cela confirme l'idée que les outils avec lesquels nos agents interagissent doivent être très souples et adaptables pour cibler précisément les données contextuelles, et qu'ils doivent répondre dans un format de sortie attendu. Il indique également que pour de nombreux cas d'utilisation, nous avons besoin de diriger les interactions entre l'agent et l'outil - c'est là que les flux de travail entrent en jeu !</p><p>Elastic disposera bientôt de flux de travail entièrement personnalisables, intégrés au cœur de la plateforme. Ces flux de travail pourront fonctionner avec des agents et des outils de manière bidirectionnelle, de sorte que les flux de travail pourront appeler des agents et des outils, et que les agents et les outils pourront appeler des flux de travail. L'intégration complète de ces capacités dans la même plateforme d'IA de recherche, où toutes vos données sont stockées, sera un facteur de transformation. Bientôt, très bientôt !</p><h3>Elastique comme la banque de mémoire unifiée</h3><p>En tant que plateforme de données distribuées conçue pour la recherche en temps quasi réel, Elastic remplit naturellement les fonctions de mémoire à long terme pour les systèmes d'IA agentique. Avec l'expérience de chat intégrée d'Agent Builder, nous disposons également d'un suivi et d'une gestion de la mémoire à court terme et de l'historique des chats. Et comme toute la plateforme est fondée sur l'API, il est extrêmement facile d'utiliser Elastic comme plateforme pour conserver les résultats contextuels d'un outil (et pouvoir s'y référer ultérieurement) qui pourraient dépasser la fenêtre contextuelle de l'agent ; cette technique est parfois appelée "<a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents#:~:text=Agents%20can%20assemble%20understanding%20layer%20by%20layer%2C%20maintaining%20only%20what%27s%20necessary%20in%20working%20memory%20and%20leveraging%20note%2Dtaking%20strategies%20for%20additional%20persistence">prise de notes</a>" dans les cercles de l'ingénierie contextuelle.</p><p>Le fait de disposer d'une mémoire à court terme et d'une mémoire à long terme sur la même plateforme de recherche présente de nombreux avantages intrinsèques : imaginez que vous puissiez utiliser les historiques de chat et les réponses contextuelles persistantes pour influencer sémantiquement les futures interactions de chat, ou pour effectuer une analyse des menaces, ou pour créer des produits de données persistants générés automatiquement à partir d'appels d'outils fréquemment répétés... Les possibilités sont infinies !</p><h2>Conclusion</h2><p>L'émergence de grands modèles de langage a modifié la façon dont nous pouvons faire correspondre le contenu et les méthodes que nous utilisons pour interroger nos données. Nous nous éloignons rapidement de notre monde actuel, où les humains effectuent les recherches, les considérations contextuelles et le raisonnement logique pour répondre à leurs propres questions, pour passer à un monde où ces étapes sont largement automatisées grâce à l'IA agentique. Pour que nous puissions faire confiance aux réponses générées que nous recevons, nous devons avoir l'assurance que l'agent a pris en compte <em>toutes les</em> informations <em>les plus pertinentes</em> (y compris le facteur de la pertinence subjective) pour générer sa réponse. Notre principale méthode pour rendre l'IA agentique digne de confiance consiste à ancrer les outils qui récupèrent un contexte supplémentaire grâce aux techniques de RAG et d'ingénierie contextuelle, mais la manière dont ces outils effectuent la <em>récupération initiale</em> peut être déterminante pour la précision de la réponse.</p><p>La plateforme d'IA Elastic Search offre la flexibilité et les avantages de la recherche hybride, ainsi que plusieurs fonctionnalités intégrées qui aident l'IA agentique en termes de précision, de performance et d'évolutivité ; en d'autres termes, Elastic est une plateforme fantastique pour plusieurs aspects de l'ingénierie contextuelle ! En normalisant la recherche de contexte via une plateforme de recherche, nous simplifions les opérations de l'outil agentique sur plusieurs fronts - et comme l'oxymore "ralentir pour aller plus vite", la simplicité au niveau de la couche de génération de contexte signifie une IA agentique plus rapide et plus digne de confiance.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-agentic-ai-accuracy</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-agentic-ai-accuracy</guid>
    <category><![CDATA[Recherche hybride]]></category>
    <category><![CDATA[IA agentique]]></category>
    <dc:creator><![CDATA[Woody Walton]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt42a203a316f0e22e/6a170932b339d58ebc769f5f/b82ff25242e4229cc20b218d9cc91c60cfd680bc-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 20 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Vous savez, pour le contexte - Partie I : L'évolution de la recherche hybride et de l'ingénierie contextuelle]]></title>
    <description><![CDATA[Découvrez comment la recherche hybride et l'ingénierie contextuelle ont évolué à partir de bases lexicales pour permettre la prochaine génération de flux de travail d'IA agentique.]]></description>
    <content:encoded><![CDATA[<h2>Notre tout nouveau monde d'IA agentique</h2><p>Comme beaucoup d'entre nous, je suis à la fois heureux et étonné du rythme auquel les capacités de l'IA évoluent. Les grands modèles de langage (LLM) et la recherche vectorielle nous ont d'abord lancés dans la révolution sémantique, où nous ne cherchions plus à trouver des choses à l'aide de mots-clés. Ensuite, les LLM nous ont montré de nouvelles façons d'interagir avec nos données, en utilisant des interfaces de chat pour transformer les demandes en langage naturel en réponses qui distillent de vastes bases de connaissances en résumés facilement consommables. Nous avons maintenant (déjà !) ont les prémices d'une logique automatisée pilotée par le LLM sous la forme de flux de travail "d'IA agentique" qui peuvent comprendre sémantiquement une demande entrante, raisonner sur les étapes à suivre, puis choisir parmi les outils disponibles pour exécuter itérativement des actions afin d'atteindre ces objectifs.</p><p>La promesse de l'IA agentique nous oblige à évoluer et à ne plus utiliser principalement l'"ingénierie de l'invite" pour façonner nos interactions génératives avec l'IA, mais à nous concentrer sur la manière dont nous pouvons aider les outils agentiques à obtenir les informations supplémentaires les plus pertinentes et les plus efficaces que le LLM doit prendre en compte lorsqu'il génère ses réponses - l'"ingénierie du contexte" est la prochaine frontière. La recherche hybride est de loin le moyen le plus puissant et le plus souple de faire apparaître un contexte pertinent, et la plateforme Search AI d'Elastic ouvre une toute nouvelle voie pour exploiter les données au service de l'ingénierie contextuelle. Dans cet article, nous allons examiner comment les LLM ont changé le monde de la recherche d'informations sous deux angles, puis comment ils peuvent travailler ensemble pour obtenir de meilleurs résultats. Il y a beaucoup de chemin à parcourir...</p><h2>Partie I : Comment les LLM ont changé la recherche</h2><p>Commençons par la façon dont les LLM ont changé la façon dont nous accédons à l'information et dont nous la recherchons.</p><h3>Notre héritage lexical</h3><p>Nous vivons tous depuis longtemps dans le monde quelque peu limité de la recherche lexicale (plutôt bien, du mieux que nous pouvons). La recherche est le premier outil que nous utilisons lorsque nous faisons des recherches ou que nous commençons un nouveau projet. Jusqu'à récemment, il nous incombait de formuler nos requêtes de manière à ce qu'elles soient comprises par un moteur de recherche lexical. La recherche lexicale repose sur la mise en correspondance d'une certaine forme de termes d'interrogation avec des mots-clés trouvés dans un corpus de documents, que le contenu soit structuré ou non. Pour qu'une recherche lexicale aboutisse à un document, celui-ci doit correspondre à ce mot-clé (ou disposer d'un vocabulaire contrôlé tel qu'une liste de synonymes ou un dictionnaire pour établir le lien conceptuel).</p>POST my-index/_search
{
  "size": 10,
  "query": {
    "semantic": {
      "query": "machine learning applications",
      "field": "semantic-content-field"
    }
  }
}<p><em>Exemple de </em><em> requête</em><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-multi-match-query"><em>lexicale multi-correspondance</em></a></p><p>Au moins, les moteurs de recherche ont la possibilité de renvoyer les résultats avec un score de pertinence. Les moteurs de recherche offrent une multitude d'options syntaxiques pour cibler efficacement les données indexées et des algorithmes de pertinence intégrés qui évaluent les résultats en fonction de l'intention de la syntaxe de la requête de l'utilisateur. Les moteurs de recherche bénéficient de décennies de progrès dans les algorithmes de classement par pertinence, ce qui en fait une plate-forme efficace de recherche de données capable de fournir des résultats notés et triés en fonction de leur pertinence par rapport à la requête. Les bases de données et autres systèmes qui utilisent SQL comme principale méthode de recherche de données sont ici désavantagés : il n'y a pas de concept de pertinence dans une requête de base de données ; le mieux qu'ils puissent faire est de trier les résultats par ordre alphabétique ou numérique. La bonne nouvelle, c'est que vous obtiendrez tous les résultats (rappel) avec ces mots-clés, mais qu'ils ne seront pas nécessairement dans un ordre utile par rapport à la <em>raison pour laquelle</em> vous les avez demandés (précision). C'est un point important, comme nous le verrons bientôt...</p><h3>Entrez dans le dragon (sémantique)</h3><p>Le potentiel des représentations vectorielles de l'information en tant qu'alternative à la recherche par mot-clé fait l'objet de recherches depuis <a href="https://www.elastic.co/search-labs/blog/introduction-to-vector-search">longtemps</a>. Les vecteurs sont très prometteurs parce qu'ils nous permettent de sortir du mode de correspondance par mot-clé uniquement - parce qu'ils sont des représentations numériques des termes et des poids, les vecteurs permettent de rapprocher mathématiquement les concepts sur la base de la compréhension par un modèle linguistique de la manière dont les termes sont liés les uns aux autres dans le domaine d'apprentissage. Le retard pris par la recherche vectorielle générale s'explique par le fait que les modèles étaient essentiellement limités à des domaines spécifiques et qu'ils n'étaient tout simplement pas assez vastes pour comprendre suffisamment les nombreux concepts différents qu'un terme peut représenter dans des contextes différents.</p><p>Ce n'est que lorsque les grands modèles de langage (LLM) sont apparus il y a quelques années, avec leur capacité à s'entraîner sur des quantités de données beaucoup plus importantes (en utilisant des <a href="https://en.wikipedia.org/wiki/Transformer_(deep_learning_architecture)">transformateurs</a> et de l'<a href="https://en.wikipedia.org/wiki/Attention_(machine_learning)">attention</a>), que la recherche vectorielle est devenue pratique - la taille et la profondeur des LLM ont finalement permis aux vecteurs de stocker suffisamment de nuances pour qu'ils puissent réellement capturer le sens sémantique. Cette augmentation soudaine de la profondeur de compréhension a permis aux LLM de remplir un grand nombre de fonctions de traitement du langage naturel (NLP) qui étaient auparavant verrouillées, la plus importante étant peut-être la capacité à déduire le terme suivant le plus probable dans une séquence, compte tenu du contexte de ce qui se trouve dans la séquence jusqu'à présent. L'inférence est le processus qui donne à l'IA générative sa capacité quasi humaine à produire du texte. Le texte généré par l'IA s'appuie sur la compréhension qu'a le LLM de la manière dont les termes sont liés dans ses données d'apprentissage et utilise également la formulation de la demande pour désambiguïser les différents contextes dans lesquels les termes peuvent apparaître.</p><p>Aussi magique que soit l'IA générative, les LLM présentent <em>des</em> limites qui entraînent des erreurs de qualité et de précision, communément appelées hallucinations. Les hallucinations se produisent lorsque le LLM n'a pas accès aux informations (ou n'est pas guidé vers le bon contexte) pour fonder sa réponse sur la vérité et que, pour être utile, il génère une réponse confiante et plausible qui a été inventée. Cela s'explique en partie par le fait que les LLM apprennent l'usage de la langue dans de vastes domaines d'informations diverses, mais qu'ils doivent cesser leur formation à un moment donné, de sorte que leur compréhension est soumise à un facteur temporel, ce qui signifie que le modèle ne peut savoir que ce qui était exact jusqu'au moment où il a cessé de se former. Un autre facteur d'hallucinations est que le modèle ne connaît généralement pas les données privées (données non disponibles sur l'internet public), ce qui est particulièrement important lorsque ces données contiennent des termes et une nomenclature spécifiques.</p><h3>Bases de données vectorielles</h3><p>Les LLM vectorisent le contenu dans l'espace de leur modèle à l'aide d'une technique appelée "text embedding", qui consiste à <a href="https://www.elastic.co/search-labs/blog/hybrid-search-multiple-embeddings">intégrer</a> ou à cartographier la signification sémantique du contenu dans la vision du monde du modèle sur la base de la formation qu'il a reçue. Quelques étapes sont nécessaires pour préparer et traiter le contenu à intégrer, notamment le <a href="https://www.elastic.co/search-labs/blog/chunking-strategies-elasticsearch">découpage en morceaux</a> et la tokenisation (et la <a href="https://www.kaggle.com/code/danishmahdi/subword-tokenization-bpe-wordpiece-and-unigram">tokenisation des sous-mots</a>). Le résultat est généralement un ensemble de vecteurs denses représentant la compréhension par le modèle de la signification de ce morceau de contenu dans son espace vectoriel. Le découpage est un processus inexact qui vise à adapter le contenu aux limites des contraintes de traitement d'un modèle pour générer des encastrements, tout en essayant de regrouper le texte apparenté dans un morceau à l'aide de constructions sémantiques telles que les indicateurs de phrase et de paragraphe.</p><p>La nécessité d'un découpage en morceaux peut entraîner une certaine perte sémantique dans un document incorporé, car les morceaux individuels ne sont pas entièrement associés à d'autres morceaux du même document. L'opacité inhérente aux réseaux neuronaux peut aggraver cette perte - un LLM est véritablement une "boîte noire" dans laquelle les connexions entre les termes et les concepts établies au cours de la formation sont non déterministes et ne peuvent être interprétées par les humains. Cela pose des problèmes d'explicabilité, de reproductibilité, de partialité inconsciente et, potentiellement, de perte de confiance et d'exactitude. Néanmoins, la possibilité de relier sémantiquement des idées, de ne pas être lié à des mots-clés spécifiques lors de la recherche, est extrêmement puissante :</p>POST my-index/_search 
{
  "size": 10, 
  "query": {
    "semantic": {
      "query": "machine learning applications",
      "field": "semantic-content-field"
    }
  }
} <p><em>Un exemple </em><a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-semantic-query"><em>de</em></a><em> requête sémantique</em></p><p>Les bases de données vectorielles ne sont pas des moteurs de recherche, mais des bases de données ! Lorsqu'une <a href="https://www.elastic.co/search-labs/blog/introduction-to-vector-search">recherche de similarité vectorielle</a> est effectuée, les termes de la requête sont encodés pour trouver un ensemble de coordonnées (d'intégration) dans l'espace vectoriel du modèle. Ces coordonnées sont ensuite utilisées comme œil-de-bœuf pour trouver les documents qui sont les "plus proches voisins" de l'œil-de-bœuf - ce qui signifie que le rang d'un document (ou sa place dans les résultats) est déterminé par la <em>distance de</em> similarité calculée entre les coordonnées de ce document et les coordonnées de la requête. Dans quel sens le classement doit-il primer, lequel des contextes possibles est le plus proche de l'intention de l'utilisateur ? L'image à laquelle je me réfère est une scène du film <a href="https://www.youtube.com/watch?v=x3h7xz558EY&amp;start=3&amp;end=86">Stargate</a>, où nous avons les six points de coordonnées qui se croisent pour nous indiquer la destination (l'œil-de-bœuf), mais nous ne pouvons pas nous y rendre sans connaître le "7e symbole" - les coordonnées du point de départ représentant l'intention subjective de l'utilisateur. Ainsi, au lieu que le classement relatif des vecteurs soit basé sur une sphère de similarité toujours plus étendue et indifférenciée, en tenant compte de l'intention subjective de la requête par le biais d'une syntaxe expressive et d'une notation de la pertinence, nous pouvons obtenir quelque chose qui ressemble à un <em>cylindre</em> de pertinence subjective graduée.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb49ae330d3de1fc1/6a17053ee8fbce089639fb46/1ddfaae0c1496d08d7d30419e6d2aeaeacfc0ea2-1600x544.png" alt="Un cylindre à pertinence subjective graduée." /><p>Les capacités d'inférence d'un LLM peuvent aider à identifier le contexte le plus probable <em>pour</em> la requête, mais le problème est que <em>sans aide, les</em> coordonnées de la requête entrante <em>ne</em> peuvent être déterminées que par la façon dont le modèle a été formé à l'origine.</p><p>D'une certaine manière, on pourrait dire que la similarité vectorielle va à l'extrême opposé d'une correspondance stricte par mot-clé - sa force réside dans sa capacité à surmonter les problèmes d'inadéquation des termes, mais <a href="https://medium.com/data-science/vector-embeddings-are-lossy-heres-what-to-do-about-it-4f9a8ee58bb7">presque jusqu'à la faute</a>: Les LLM tendent à unifier des concepts apparentés plutôt qu'à les différencier. La similarité vectorielle améliore notre capacité à faire correspondre le contenu sur le plan sémantique, mais ne garantit pas la précision car elle peut négliger des mots-clés exacts et des détails spécifiques qui ne sont pas suffisamment désambiguïsés par le modèle. La recherche de similarités vectorielles est puissante en soi, mais nous avons besoin de moyens pour corréler les résultats que nous extrayons d'une base de données vectorielle avec les résultats d'autres méthodes d'extraction.</p><h3>Techniques de repositionnement</h3><p>C'est le moment de mentionner une technique générale appelée "reranking", qui consiste à réévaluer ou à normaliser les ensembles de résultats en fonction d'un ordre de classement unifié. Le besoin de reclassement peut être dû au fait que les résultats provenant de sources multiples ou de méthodes de recherche ont des mécanismes de classement/évaluation différents (ou aucun, SQL !), ou le reclassement peut être utilisé pour aligner sémantiquement les résultats provenant de sources non sémantiques sur la requête de l'utilisateur. Le reclassement est une opération de deuxième étape, c'est-à-dire un ensemble de résultats qui ont été collectés par une méthode de <em>recherche initiale</em> (c'est-à-dire le SQL, recherche lexicale, recherche vectorielle) sont ensuite réordonnées avec une méthode de notation différente.</p><p>Plusieurs approches sont disponibles, notamment <a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr">Learning-To-Rank (LTR)</a> et <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">Reciprocal Rank Fusion (RRF)</a> - LTR est utile pour capturer les caractéristiques des résultats de recherche (likes, évaluations, clics, etc.) et les utiliser pour noter et améliorer ou biaiser les résultats. RRF est parfait pour fusionner les résultats obtenus à partir de différentes modalités d'interrogation (par ex. les recherches dans les bases de données lexicales et vectorielles) en une seule liste de résultats. Elastic offre également la possibilité d'ajuster les scores à l'aide de méthodes de <a href="https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search">reclassement linéaire</a>.</p><p>L'une des techniques de reclassement les plus efficaces est cependant le <a href="https://www.elastic.co/docs/solutions/search/ranking/semantic-reranking">reclassement sémantique</a>, qui utilise la compréhension sémantique d'un LLM pour analyser les vecteurs d'intégration de la requête et des résultats, puis appliquer la notation de la pertinence/le reclassement pour déterminer l'ordre final. Le reranking sémantique nécessite une connexion à un modèle de reranking, bien sûr, et Elasticsearch fournit une <a href="https://www.elastic.co/docs/api/doc/elasticsearch/group/endpoint-inference">API d'inférence</a> qui vous permet de créer des points d'extrémité de <strong>rerank</strong> qui exploitent des modèles intégrés<a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-rerank">(Elastic Rerank</a>), des modèles tiers <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/eland/machine-learning">importés</a> ou des services hébergés en externe tels que <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-cohere">Cohere</a> ou <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-inference-put-googlevertexai">Google Vertex AI.</a> Vous pouvez ensuite effectuer un reclassement grâce à la syntaxe d'abstraction de la requête du <a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">récupérateur</a>:</p>POST my-index/_search 
{
  "size": 10,
  "retriever": {
    "text_similarity_reranker": {
      "retriever": {
        "rrf": {
          "retrievers": [
            {
              "standard": {
                "query": {
                  "multi_match": {
                    "query": "machine learning applications",
                    "fields": ["title", "content"]
                  }
                }
              }
            },
            {
              "knn": {
                "field": "semantic-content-field",
                "k": 10,
                "num_candidates": 100,
                "query_vector_builder": {
                  "text_embedding": {
                    "model_id": "my-text-embedding-model",
                    "model_text": "machine learning applications"
                  }
                }
              }
            }
          ],
          "rank_window_size": 50,
          "rank_constant": 20
        }
      }
    },
    "field": "content",
    "inference_id": "my-reranker",
    "inference_text": "machine learning applications",
    "rank_window_size": 20
  }
}<p><em>Exemple d'opération de remise en ordre d'un récupérateur en plusieurs étapes</em></p><p>Ça a l'air bien, non ? Nous pouvons effectuer un reclassement sur des résultats provenant de sources disparates et nous rapprocher d'une compréhension sémantique de tous les types de contenu... Le reclassement sémantique peut être coûteux tant sur le plan du calcul que du temps de traitement nécessaire, et pour cette raison, le reclassement sémantique ne peut être effectué que sur un nombre limité de résultats, ce qui signifie que la <em>manière dont</em> ces résultats initiaux sont récupérés est importante.</p><h3>La méthode de recherche contextuelle est importante</h3><p>L'intention subjective est un facteur important dans la détermination de l'exactitude d'un résultat, dans l'évaluation de sa pertinence. Sans la possibilité de prendre en compte l'intention de l'utilisateur lors de l'exécution de la requête (telle qu'elle est exprimée par une syntaxe flexible ou par un reclassement de deuxième niveau), nous ne pouvons que sélectionner les contextes existants déjà encodés dans l'espace de modélisation. La façon dont nous abordons généralement ce manque de contexte est par le biais de techniques telles que <a href="https://en.wikipedia.org/wiki/Retrieval-augmented_generation">Retrieval Augment Generation (RAG).</a> La méthode RAG consiste à déplacer les coordonnées de la requête en incluant des termes connexes supplémentaires issus d'une pré-requête de données contextuelles pertinentes. Le moteur qui fournit ce contexte supplémentaire et <em>sa</em> méthode initiale de recherche sont donc d'autant plus importants pour la précision du contexte !</p><p>Passons en revue les différentes méthodes de recherche contextuelle et la manière dont elles peuvent aider ou nuire à une opération RAG :</p><ul><li><p><strong>La recherche hybride sans moteur de recherche manque encore de pertinence subjective.</strong> Si la plateforme qui fournit le RAG est principalement basée sur SQL (ce qui inclut la plupart des plateformes de "lac de données"), elle ne dispose pas d'un système de notation de la pertinence au stade de la recherche initiale. De nombreuses plateformes de lac de données proposent leur propre version de la recherche hybride (et non de la recherche), combinant généralement des techniques de reranking telles que le reranking sémantique et le RRF sur leur recherche basée sur SQL et les résultats de la base de données vectorielles. Un simple tri est manifestement insuffisant pour un classement subjectif, mais même lorsqu'il est utilisé comme base pour une opération de reclassement sémantique à la deuxième étape, SQL comme la recherche à la première étape devient un problème lorsque le reclassement sémantique n'est effectué que sur les "k premiers" résultats - sans un moyen de noter les résultats à la recherche, quelle garantie avons-nous que les <em>meilleurs</em> résultats se trouvent effectivement dans les premiers résultats ?</p></li><li><p><strong>La similarité vectorielle n'est pas suffisante pour le RAG</strong>. Il s'agit en fait d'un ensemble de problèmes combinés - la perte de l'intégration, les méthodes naïves de regroupement, le mode de calcul de la similarité et la composante manquante cruciale de l'intention subjective. L'un des principaux objectifs de RAG est de fonder les interactions génératives de l'IA sur la vérité objective, à la fois pour éviter les hallucinations et pour informer le LLM des informations privées dont il n'a pas eu connaissance au cours de la formation. Nous pouvons utiliser le contexte supplémentaire fourni par le RAG pour contraindre et orienter les MFR à prendre en compte les liens et les détails que nous savons être les plus importants pour répondre à la question posée. Pour ce faire, nous devons utiliser des approches sémantiques et <em>lexicales</em>.</p></li><li><p><strong>RAG (grep/regex) basé sur des fichiers.</strong> Certains <a href="https://www.nicolasbustamante.com/p/the-rag-obituary-killed-by-agents">secteurs</a> de l'univers de l'IA agentique préconisent l'utilisation de fenêtres contextuelles considérablement agrandies qui accèdent aux fichiers locaux via grep et regex pour RAG plutôt que des plates-formes de recherche externes. L'idée est qu'en disposant d'une fenêtre contextuelle beaucoup plus large, les LLM seront en mesure d'établir des connexions conceptuelles au sein de leur propre espace de réflexion plutôt que de s'appuyer sur des éléments fragmentés et de multiples méthodes/plateformes de recherche pour collecter des informations pertinentes. S'il est vrai en théorie que le fait de disposer d'un document entier donne une image plus complète que des segments de document, cela ne peut fonctionner que dans des domaines de données restreints (ou, par exemple, lors de la fourniture de fichiers pour le <a href="https://en.wikipedia.org/wiki/Vibe_coding">vibecodage</a>), et même dans ce cas, la méthode de recherche initiale est un balayage de tous les documents avec une correspondance par mot-clé uniquement.</p></li></ul><p><strong>La recherche, c'est plus que l'extraction</strong></p><p>Les moteurs de recherche sont conçus pour rendre les requêtes aussi rapides et flexibles que possible. En interne, ils utilisent des structures de données spécialisées pour stocker et récupérer différents types de données de manière adaptée à ces types de données. Elasticsearch permet d'optimiser le stockage et l'interrogation de pratiquement tous les types de données, y compris la recherche lexicale non structurée/texte intégral (correspondance, phrase, proximité, correspondance multiple), la correspondance et le filtrage rapides par mot-clé (correspondance exacte), les plages numériques, les dates, les adresses IP, et est très flexible dans la manière dont il stocke les structures de documents (par ex. les documents imbriqués ou aplatis). Elasticsearch est également une base de données vectorielle native capable de stocker et d'interroger des types de vecteurs épars et denses, et nous continuons à explorer des moyens innovants (par exemple, <a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Better Binary Quantization (BBQ)</a> &amp; <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">DiskBBQ</a>) pour maintenir la fidélité de la recherche tout en améliorant la vitesse, l'évolutivité et les coûts associés au contenu vectorisé. La plateforme Elasticsearch offre également une résilience des données et une haute disponibilité intégrées, ainsi que des fonctionnalités de gestion du cycle de vie des données, telles que les <a href="https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/searchable-snapshots">instantanés consult</a> ables, qui vous permettent de conserver les données rarement consultées ou les données conservées à long terme sur un stockage objet rentable, tout en conservant une capacité de recherche totale.</p><h3>La recherche hybride, c'est le meilleur des mondes</h3><p><a href="https://www.elastic.co/what-is/hybrid-search">Recherche hybride</a> (et pas seulement recherche hybride !) combine les forces de la recherche lexicale traditionnelle avec la compréhension sémantique des LLM et la recherche par similarité vectorielle. Cette synergie permet de cibler des résultats très pertinents au stade de la <em>recherche</em> grâce à l'une des options syntaxiques souples proposées par un moteur de recherche : options syntaxiques axées sur l'intention et évaluation de la pertinence, recherche de données multimodales, filtrage, agrégations et biais. Avec une syntaxe de recherche telle que <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> et des <a href="https://www.elastic.co/docs/solutions/search/retrievers-overview">extracteurs</a> à plusieurs niveaux, nous pouvons combiner de manière flexible la recherche traditionnelle avec la recherche sémantique, les filtres et plusieurs techniques de reclassement en une seule requête.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6ee9512910856ee3/6a17053fcdacbf2ea97d291e/f25180cb430414b99ae553d3b8eb161dbccea4d4-1920x1080.png" alt="Comment fonctionne la recherche hybride ?" /><p>L'un des principaux avantages de la recherche hybride est que vos requêtes peuvent utiliser une syntaxe spécialisée pour plusieurs types de données simultanément. Ces différentes syntaxes d'interrogation peuvent être utilisées non seulement pour <em>trouver des</em> résultats, mais aussi comme filtres ou agrégations <em>sur les</em> résultats. Par exemple, l'<a href="https://www.elastic.co/docs/explore-analyze/geospatial-analysis">analyse géospatiale</a> est l'un des types d'interrogation les plus courants qui est fréquemment combiné à d'autres syntaxes. Vous pouvez par exemple demander des résultats dont les coordonnées géographiques se situent à une distance donnée d'un point, ou demander des agrégations de vos résultats par région, ou encore des agrégations pour suivre et alerter sur les mouvements à l'intérieur ou à l'extérieur d'une zone. Avec la recherche hybride, vous avez la possibilité de combiner des syntaxes pour cibler les résultats de la manière la plus précise possible, afin de retrouver le contenu le plus proche de votre contexte.</p><h2>Intermède</h2><p>Cette première partie raconte comment la recherche vectorielle a changé la façon dont nous pouvons récupérer des données et prépare le terrain pour les changements que les LLM ont apportés aux mécanismes d'interrogation que nous utilisons pour interagir avec les données. Nous allons faire comme si nous avions dû diviser ce texte en plusieurs parties pour que les LLM puissent le comprendre sans perdre le contexte... ;-) Nous en apprendrons plus sur les <em>raisons de cette importance</em> dans la <a href="https://www.elastic.co/search-labs/blog/context-engineering-llm-evolution-agentic-ai">Partie II : L'IA agentique et le besoin d'ingénierie contextuelle</a>, et dans la Partie III, nous reviendrons à notre discussion sur la recherche hybride.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/context-engineering-hybrid-search-evolution-agentic-ai</guid>
    <category><![CDATA[Recherche hybride]]></category>
    <category><![CDATA[Pertinence]]></category>
    <dc:creator><![CDATA[Woody Walton]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc49e39872984c8eb/6a1705410e2e49e42c419fd5/7e59a0671aa9ea32d68188a693936a66ebf48625-1000x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 12 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Recherche multimodale de sommets avec Elasticsearch et SigLIP-2 ]]></title>
    <description><![CDATA[Apprenez à mettre en œuvre la recherche multimodale texte-image et image-image en utilisant les encastrements SigLIP-2 et la recherche vectorielle Elasticsearch kNN. Objectif du projet : trouver des photos du sommet du mont Ama Dablam prises lors d'un trekking dans l'Everest.]]></description>
    <content:encoded><![CDATA[<p>Avez-vous déjà voulu rechercher votre album photo par signification ? Essayez des requêtes telles que "montrez-moi mes photos où je porte une veste bleue et suis assis sur un banc", "montrez-moi des photos du mont Everest" ou "saké et sushi". Prenez une tasse de café (ou votre boisson préférée) et poursuivez votre lecture. Dans ce blog, nous vous montrons comment créer une application de recherche hybride multimodale. Multimodale signifie que l'application peut comprendre et rechercher différents types d'entrées - texte, images et audio - et pas seulement des mots. Hybride signifie qu'il combine des techniques telles que la correspondance de mots-clés, la recherche vectorielle kNN et la géolocalisation pour fournir des résultats plus précis.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa1ec1ccd450e94/6a17da751d1b8308ee93e344/0ec6bbb45013846b59ee00d2bf73ee2182ee7392-1920x1080.gif" alt="Bibliothèque de différentes photos de sommets de montagne de la randonnée du Mont Everest." /><p>Pour ce faire, nous utilisons le logiciel SigLIP-2 de Google pour générer des encastrements vectoriels pour les images et le texte, et les stocker dans la base de données vectorielles Elasticsearch. Au moment de la requête, nous convertissons l'entrée de la recherche, texte ou image, en encastrements et exécutons des recherches vectorielles kNN rapides pour extraire les résultats. Cette configuration permet une recherche efficace de texte à image et d'image à image. Une interface utilisateur Streamlit donne vie à ce projet en nous fournissant un frontend qui nous permet non seulement d'effectuer une recherche textuelle pour trouver et afficher les photos correspondantes de l'album, mais aussi d'identifier le sommet de la montagne à partir de l'image téléchargée et d'afficher d'autres photos de cette montagne dans l'album photo.
Nous présentons également les mesures que nous avons prises pour améliorer la précision des recherches, ainsi que des conseils et astuces pratiques. Pour une exploration plus approfondie, nous fournissons un <a href="https://github.com/navneet83/multimodal-mountain-peak-search">dépôt GitHub</a> et un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">carnet de notes Colab</a>.</p><h2>Comment cela a commencé</h2><p>Ce billet a été inspiré par un enfant de 10 ans qui m'a demandé de lui montrer toutes les photos du mont Ama Dablam prises lors de mon trek au camp de base de l'Everest. En parcourant l'album photo, on m'a également demandé d'identifier plusieurs autres pics montagneux, dont certains que je n'arrivais pas à nommer.</p><p>Cela m'a donné l'idée d'un projet amusant de vision par ordinateur. Ce que nous voulions réaliser :</p><ul><li><p>trouver des images d'un sommet de montagne par son nom</p></li><li><p>deviner le nom du sommet d'une montagne à partir d'une image et trouver des sommets similaires dans l'album photo</p></li><li><p>faire fonctionner les requêtes conceptuelles<em>(personne</em>, <em>rivière</em>, <em>drapeaux de prière</em>, <em>etc.)</em></p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf82df9d7005fc3fe/6a17da78abe0f2e77bdfe8b9/e9d0d720a9b565d5b749bdc915068852d4f157ad-1200x1600.png" alt="Mont Ama Dablam " /><h2>Assembler l'équipe de rêve : SigLIP-2, Elasticsearch &amp; Streamlit</h2><p>Il est rapidement apparu que pour que cela fonctionne, nous devions transformer à la fois le texte ("Ama Dablam") et les images (photos de mon album) en vecteurs pouvant être comparés de manière significative, c'est-à-dire dans le même espace vectoriel. Une fois cette étape franchie, la recherche se résume à "trouver les voisins les plus proches".</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f80b69a9d5bd28a/6a17da7a4b055ddd1243209e/20e6f8b7d4fa48414f407ec200adbe00ee28d517-1536x1024.png" alt="SigLIP-2, Elasticsearch &amp; Streamlit- dream team." /><p>Pour générer des enregistrements d'images, nous utilisons un<a href="https://huggingface.co/blog/vlms-2025"> encodeur multilingue</a> vision-langage, de sorte qu'une photo d'une montagne et une phrase comme "Ama Dablam" se retrouvent dans le même espace vectoriel.</p><p><a href="https://huggingface.co/blog/siglip2"><strong>SigLIP-2</strong></a>, récemment publié par Google, s'inscrit parfaitement dans ce cadre. Il peut générer des enchâssements sans formation spécifique à une tâche (un réglage <strong>zéro)</strong> et fonctionne bien pour notre cas d'utilisation : des photos non étiquetées et des pics avec des noms et des langues différents. Parce qu'il est formé à la correspondance texte ↔ image, une photo de montagne prise lors d'une randonnée et un court texte d'incitation se retrouvent proches en tant qu'ancrages, même lorsque la langue ou l'orthographe de la requête varient.</p><p>SigLIP-2 offre un excellent rapport qualité-vitesse, prend en charge plusieurs résolutions d'entrée et fonctionne à la fois avec le CPU et le GPU. SigLIP-2 est conçu pour être plus résistant aux photos prises en extérieur que les modèles précédents tels que le CLIP original. Lors de nos tests, SigLIP-2 a toujours produit des résultats fiables. Il est également très bien supporté, ce qui en fait un choix évident pour ce projet.</p><p>Ensuite, nous avons besoin d'une base de données vectorielle pour stocker les encastrements et effectuer des recherches puissantes. Il devrait permettre non seulement la recherche par cosinus kNN sur des images intégrées, mais aussi l'application de filtres de géographie et de texte en une seule requête. Elasticsearch convient bien ici : il gère très bien les vecteurs (HNSW kNN sur les champs dense_vector), prend en charge la recherche hybride qui combine le texte, les vecteurs et les requêtes géographiques, et propose d'emblée le filtrage et le tri. Il est également évolutif horizontalement, ce qui permet de passer facilement d'une poignée de photos à des milliers. Le client officiel <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/python">Elasticsearch Python</a> simplifie la plomberie et s'intègre parfaitement au projet. Enfin, nous avons besoin d'un frontal léger où nous pouvons saisir des requêtes de recherche et afficher les résultats. Pour une démonstration rapide, basée sur Python, Streamlit est une solution idéale. Il fournit les primitives dont nous avons besoin - le téléchargement de fichiers, une grille d'images réactive et des menus déroulants pour le tri et la géolocalisation. Il est facile de le cloner et de l'exécuter localement, et il fonctionne également dans un cahier Colab.</p><h2>Implémentation</h2><h3>Conception et stratégie d'indexation Elasticsearch</h3><p>Nous utiliserons deux indices pour ce projet : <code>peaks_catalog</code> et <code>photos</code>.</p><h4>Index du catalogue des pics</h4><p>Cet index constitue un catalogue compact des principaux sommets visibles pendant le trek du camp de base de l'Everest. Chaque document de cet index correspond à un sommet de montagne, comme le mont Everest. Pour chaque document relatif à un pic montagneux, nous stockons les noms/alias, les coordonnées facultatives de latitude et de longitude, ainsi qu'un vecteur prototype unique construit en mélangeant les messages-guides SigLIP-2 (+ images de référence facultatives).</p><p><strong>Mappage de l'index :</strong></p><p>Champ d'application</p><p>Type</p><p>Exemple</p><p>Objectif/Notes</p><p>Vecteur/Indexation</p><p>id</p><p>mot-clé</p><p>ama-dablam</p><p>Slug/id stable</p><p>-</p><p>noms</p><p>texte + sous-champ mot-clé</p><p>["Ama Dablam","Amadablam"]</p><p>Alias / noms multilingues ; names.raw pour les filtres exacts</p><p>-</p><p>latlon</p><p>geo_point</p><p>{"lat":27.8617,"lon":86.8614}</p><p>Coordonnées GPS du pic sous la forme d'une combinaison latitude/longitude (facultatif)</p><p>-</p><p>elev_m</p><p>entier</p><p>6812</p><p>Élévation (facultatif)</p><p>-</p><p>texte_embed</p><p>dense_vector</p><p>768</p><p>Prototype mixte (invites et éventuellement 1 à 3 images de référence) pour ce pic</p><p>index:true, similarité :"cosine", index_options :{type:"hnsw", m:16, ef_construction:128}</p><p>Cet index est principalement utilisé pour des recherches d'image à image, telles que l'identification de sommets de montagne à partir d'images. Nous utilisons également cet index pour améliorer les résultats de recherche texte-image.</p><p>En résumé, le site <code>peaks_catalog</code> transforme la question "Quelle est cette montagne ?" en un problème ciblé de plus proche voisin, séparant efficacement la compréhension conceptuelle des complexités des données d'image.</p><p><strong>Stratégie d'indexation pour l'index peaks_catalog : </strong>Nous commençons par créer une liste des sommets les plus importants visibles lors de la randonnée EBC. Pour chaque pic, nous stockons sa position géographique, son nom, ses synonymes et son altitude dans un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/data/peaks.yaml">fichier yaml</a>. L'étape suivante consiste à <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L351">générer l'intégration</a> pour chaque pic et à la stocker dans le champ <code>text_embed</code>. Afin de générer des encastrements robustes, nous utilisons la technique suivante :</p><ul><li><p>Créer un prototype de texte en utilisant :</p><ul><li><p>noms des sommets</p></li><li><p>l'<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L301">ensemble des invites</a> (utilisation de plusieurs invites différentes pour tenter de répondre à la même question), par exemple :</p><ul><li><p>"photo naturelle du sommet de la montagne {name} dans l'Himalaya, Népal".</p></li><li><p>"{name} sommet emblématique de la région du Khumbu, paysage alpin"</p></li><li><p>"{name} sommet montagneux, neige, ligne de crête rocheuse"</p></li></ul></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L333">anti-concept</a> optionnel (indiquant à SigLIP-2 ce qu'il ne faut pas faire) : soustraire un petit vecteur pour "peinture, illustration, affiche, carte, logo" afin de privilégier les photos réelles.</p></li></ul></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L388C13-L388C29">Créer éventuellement un prototype d'image</a> si des images de référence du pic sont fournies.</p></li></ul><p>Nous <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L392">fusionnons ensuite les prototypes de texte et d'image</a> pour générer l'intégration finale. Enfin, le document est <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L396">indexé</a> avec tous les champs obligatoires :</p>def l2norm(v: np.ndarray) -&gt; np.ndarray:
    return v / (np.linalg.norm(v) + 1e-12)
def compute_blended_peak_vec(
        emb: Siglip2,
        names: List[str],
        peak_id: str,
        peaks_images_root: str,
        alpha_text: float = 0.5,
        max_images: int = 3,
) -&gt; Tuple[np.ndarray, int, int, List[str]]:
    """
    Build blended vector for a single peak.

    Returns:
      vec           : np.ndarray (L2-normalized)
      found_count   : number of reference images discovered
      used_count    : number of references used (&lt;= max_images)
      used_filenames: list of filenames used (for logging)
    """
    # 1) TEXT vector
    tv = embed_text_blend(emb, names)

    # 2) IMAGE refs: prefer folder by id; fallback to slug of the primary name
    root = Path(peaks_images_root)
    candidates = [root / peak_id]
    if names:
        candidates.append(root / slugify(names[0]))

    all_refs: List[Path] = []
    for c in candidates:
        if c.exists() and c.is_dir():
            all_refs = list_ref_images(c)
            if all_refs:
                break

    found = len(all_refs)
    used_list = all_refs[:max_images] if (max_images and found &gt; max_images) else all_refs
    used = len(used_list)

    img_v = embed_image_mean(emb, used_list) if used_list else None

    # 3) Blend TEXT and IMAGE vectors, clamp alpha to [0,1]
    a = max(0.0, min(1.0, float(alpha_text)))
    vec = l2norm(tv if img_v is None else (a * tv + (1.0 - a) * img_v)).astype("float32")
    return vec, found, used, [p.name for p in used_list]<p>Exemple de document provenant de l'index <code>peaks_catalog</code>:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1219f5d0e39b512c/6a17da7c57726263161bcace/bc05fbd0c4f8d721d5170c28a3884a9eda80bb7d-1210x1132.png" alt="Un exemple de document provenant de l'index peaks_catalog dans Elasticsearch." /><h4>Index des photos</h4><p>Cet index primaire contient des informations détaillées sur toutes les photos de l'album. Chaque document représente une seule photo, contenant les informations suivantes :</p><ul><li><p>Chemin d'accès relatif à la photo dans l'album photo. Cette option permet de visualiser l'image correspondante ou de charger l'image dans l'interface de recherche.</p></li><li><p>Informations sur le GPS et l'heure de la photo.</p></li><li><p>Vecteur dense pour le codage d'images généré par SigLIP-2.</p></li><li><p><code>predicted_peaks</code> qui nous permet de filtrer par nom de pic.

<strong>Cartographie de l'index</strong></p></li></ul><p>Champ d'application</p><p>Type</p><p>Exemple</p><p>Objectif/Notes</p><p>Vecteur / Indexation</p><p>chemin</p><p>mot-clé</p><p>data/images/IMG_1234.HEIC</p><p>Comment l'interface utilisateur ouvre la vignette/l'image complète</p><p>-</p><p>clip_image</p><p>dense_vector</p><p>768</p><p>Intégration d'images SigLIP-2</p><p>index:true, similarité :"cosine", index_options :{type:"hnsw", m:16, ef_construction:128}</p><p>pics_prédits</p><p>mot-clé</p><p>["ama-dablam","pumori"]</p><p>Les suppositions Top-K au moment de l'indexation (filtre / facette UX bon marché)</p><p>-</p><p>GPS</p><p>geo_point</p><p>{"lat":27.96,"lon":86.83}</p><p>permet d'utiliser des filtres géographiques</p><p>-</p><p>heure de la prise de vue</p><p>date</p><p>2023-10-18T09:41:00Z</p><p>temps de capture : tri/filtre</p><p>-</p><p><strong>Stratégie d'indexation pour l'index des photos : </strong>Pour chaque photo de l'album, nous procédons comme suit :
Extraire les informations sur les images <code>shot_time</code> et <code>gps</code> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L526">à partir des métadonnées de l'image</a>.</p><ul><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L511">Intégration d'images SigLIP-2</a>: passage de l'image dans le modèle et normalisation L2 du vecteur. Stocker l'intégration dans le champ <code>clip_image</code>.</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L519">Prédire les pics</a> et les stocker dans le champ <code>predicted_peaks</code>. Pour ce faire, nous prenons d'abord le vecteur image de la photo généré à l'étape précédente, puis nous effectuons une recherche rapide par kNN sur le champ text_embed de l'index <code>peaks_catalog</code>. Nous conservons les 3-4 premiers sommets et ignorons les autres.</p></li><li><p>Nous calculons le champ <code>_id</code> en effectuant un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L509">hachage</a> du nom de l'image et du chemin d'accès. Cela permet de s'assurer qu'il n'y a pas de doublons après plusieurs exécutions.</p></li></ul><p>Une fois que nous avons déterminé tous les champs de la photo, les documents photo sont indexés par lots à l'aide de l'indexation <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L530">en bloc :</a></p>def bulk_index_photos(
        es: Elasticsearch,
        images_root: str,
        photos_index: str = "photos",
        peaks_index: str = "peaks_catalog",
        topk_predicted: int = 5,
        batch_size: int = 200,
        refresh: str = "false",
) -&gt; None:
    """Walk a folder of images, embed + enrich, and bulk index to Elasticsearch."""
    root = Path(images_root)
    if not root.exists():
        raise SystemExit(f"Images root not found: {images_root}")

    emb = Siglip2()
    batch: List[Dict[str, Any]] = []
    n_indexed = 0

    for p in iter_images(root):
        rel = relpath_within(root, p)
        _id = id_for_path(rel)

        # 1) Image embedding (and reuse it for predicted_peaks)
        try:
            with Image.open(p) as im:
                ivec = emb.image_vec(im.convert("RGB")).astype("float32")
        except (UnidentifiedImageError, OSError) as e:
            print(f"[skip] {rel} — cannot embed: {e}")
            continue

        # 2) Predict top-k peak names
        try:
            top_names = predict_peaks(es, ivec.tolist(), peaks_index=peaks_index, k=topk_predicted)
        except Exception as e:
            print(f"[warn] predict_peaks failed for {rel}: {e}")
            top_names = []

        # 3) EXIF enrichment (safe)
        gps = get_gps_decimal(str(p))
        shot = get_shot_time(str(p))

        # 4) Build doc and stage for bulk
        doc = {"path": rel, "clip_image": ivec.tolist(), "predicted_peaks": top_names}
        if gps:
            doc["gps"] = gps
        if shot:
            doc["shot_time"] = shot

        batch.append(
            {"_op_type": "index", "_index": photos_index, "_id": _id, "_source": doc}
        )

        # 5) Periodic flush
        if len(batch) &gt;= batch_size:
            helpers.bulk(es, batch, refresh=refresh)
            n_indexed += len(batch)
            print(f"[photos] indexed {n_indexed} (last: {rel})")
            batch.clear()

    # Final flush
    if batch:
        helpers.bulk(es, batch, refresh=refresh)
        n_indexed += len(batch)
        print(f"[photos] indexed {n_indexed} total.")

    print("[done] photos indexing")<p>Exemple de document de l'index des photos :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt744b7e6326937cfc/6a17da7e6df731d3040a0da8/1dc1406ac2a97440b6804838795b3c2205c4c6b2-1080x1234.png" alt="Un exemple de document provenant de l'index des photos dans Elasticsearch." /><p>En résumé, l'index des photos est le magasin rapide, filtrable et prêt pour le kNN de toutes les photos de l'album. Sa cartographie est volontairement minimale - juste assez de structure pour permettre une recherche rapide, un affichage propre et une répartition des résultats dans l'espace et dans le temps. Cet index sert aux deux types de recherche. Le script Python permettant de créer les deux indices est disponible <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/create_indices.py">ici.</a></p><p>La visualisation des cartes Kibana ci-dessous affiche les documents de l'album photo sous forme de points verts et les pics montagneux de l'index <code>peaks_catalog</code> sous forme de triangles rouges, les points verts correspondant bien au sentier de randonnée du camp de base de l'Everest.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5bf016e8d9c3e84/6a17da80be608681f10045e6/1c75d0ed0ce53d28a94bf2f47a354e25581d2baf-1600x1402.png" alt="Visualisation de cartes Kibana affichant les documents de l'album photo sous forme de points verts et les sommets de l'index peaks_catalog sous forme de triangles rouges, les points verts correspondant bien au sentier de trekking du camp de base de l'Everest." /><h2>Cas d'utilisation de la recherche</h2><p><strong>Recherche par nom (texte-image) :</strong> Cette fonction permet aux utilisateurs de localiser des photos de sommets de montagne (et même des concepts abstraits comme les "drapeaux de prière") à l'aide de requêtes textuelles. Pour ce faire, l'entrée texte est <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L87C5-L87C20">convertie en un vecteur texte</a> à l'aide de SigLIP-2. Pour la génération de vecteurs de texte robustes, nous utilisons la même stratégie que pour la création d'enchâssements de texte dans l'index <code>peaks_catalog</code>: <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104">combinaison de l'</a> entrée texte avec un petit <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L100">ensemble d'invites</a>, soustraction d'un<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L103"> vecteur anti-concept</a> mineur et application de la <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104">normalisation L2</a> pour produire le vecteur d'interrogation final. Une <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L140">requête</a> kNN est ensuite exécutée sur le champ <code>photos.clip_image</code> pour récupérer les pics les plus proches, sur la base de la similarité cosinusoïdale pour trouver les images les plus proches. Il est possible de rendre les résultats de la recherche plus pertinents en appliquant des <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L152">filtres</a> géographiques et de date, et/ou un filtre de terme <code>photos.predicted_peaks</code> dans le cadre de la requête (voir les exemples de requêtes ci-dessous). Cela permet d'exclure les sommets qui ressemblent à d'autres et qui ne sont pas visibles lors de la randonnée.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9bb9abf5ce64fcbb/6a17da81e8fbce20db3a17da/b5fac28ffdbedb820505365ca07df125cd01b939-946x370.png" alt="Comment fonctionne la recherche multimodale par nom (texte-image) dans Elasticsearch." /><p><strong>Requête Elasticsearch avec filtre géographique :</strong></p>POST photos/_search
{
  "knn": {
    "field": "clip_image",
    "query_vector": [ ... ],
    "k": 60,
    "num_candidates": 2000
  },
  "query": {
    "bool": {
      "filter": [
        { "geo_bounding_box": { "gps": { "top_left": "...", "bottom_right": "..." } } }
      ]
    }
  },
  "_source": ["path","predicted_peaks","gps","shot_time"]
}

Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<p><strong>Recherche par image (image à image) :</strong> Cette fonction permet d'identifier une montagne sur une photo et de trouver d'autres images de cette même montagne dans l'album photo. Lorsqu'une image est téléchargée, elle est traitée par l'encodeur d'images SigLIP-2 pour générer un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L228">vecteur d'image</a>. Une <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L234">recherche kNN</a> est ensuite effectuée sur le champ <code>peaks_catalog.text_embed</code> pour identifier les noms de pics qui correspondent le mieux. Ensuite, un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L257">vecteur de texte est généré</a> à partir des noms de pics correspondants, et une autre <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L263">recherche kNN</a> est effectuée sur l'index des photos pour localiser les images correspondantes.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab9d16333e2a9e69/6a17da827f6f155448c099cc/3a3d5635bee7a222b95529dd7f9fbee016381610-1226x550.png" alt="Comment fonctionne la recherche multimodale par image (image à image) dans Elasticsearch." /><p><strong>Requête Elasticsearch :</strong></p><p>Étape 1 : Trouver les noms de pics correspondants</p>GET peaks_catalog/_search
{
 "knn": {
   "field": "text_embed",
   "query_vector": [...image-vector... ],
   "k": 3,
   "num_candidates": 500
 },
 "_source": [
   "id",
   "names",
   "latlon",
   "text_embed"
 ]
}


Response (first two documents):
{
 "took": 2,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 3,
     "relation": "eq"
   },
   "max_score": 0.58039916,
   "hits": [
     {
       "_index": "peaks_catalog",
       "_id": "pumori",
       "_score": 0.58039916,
       "_source": {
         "id": "pumori",
         "names": [
           "Pumori",
           "Pumo Ri"
         ],
         "latlon": {
           "lat": 28.01472,
           "lon": 86.82806
         },
         "text_embed": [
                  ... embeddings...
         ]
       }
     },
     {
       "_index": "peaks_catalog",
       "_id": "kyajo-ri",
       "_score": 0.57942784,
       "_source": {
         "id": "kyajo-ri",
         "names": [
           "Kyajo Ri",
           "Kyazo Ri"
         ],
         "latlon": {
           "lat": 27.909167,
           "lon": 86.673611
         },
         "text_embed": [
           ... embeddings...
         ]
       }
     }
   ]
 }
}<p>Étape 2 : Effectuer une recherche dans l'index <code>photos</code> pour trouver les images correspondantes (même requête que dans le cas d'utilisation de la recherche texte-image) :</p>POST photos/_search
{
 "knn": {
   "field": "clip_image",
   "query_vector": [ ...image-vector... ],
   "k": 30,
   "num_candidates": 2000
 },
 "_source": [
   "path",
   "gps",
   "shot_time",
   "predicted_peaks",
   "clip_image"
 ],
 "query": {
   "bool": {
     "filter": [
       {
         "term": {
           "predicted_peaks": "Pumori"
         }
       }
     ]
   }
 }
}


Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<h2>Streamlit UI</h2><p>Pour réunir le tout, nous avons créé une interface utilisateur Streamlit simple qui nous permet de réaliser les deux cas d'utilisation de la recherche. La barre de gauche affiche une liste déroulante de pics (agrégés à partir de <code>photos.predicted_peaks</code>) avec des cases à cocher et un filtre mini-carte/géo. En haut, il y a un champ de <strong>recherche par nom</strong> et un bouton d'<strong>identification à partir du</strong> téléchargement d'une photo. Le volet central présente une grille de vignettes réactive indiquant les scores kNN, les badges de pic prédit et les heures de capture. Chaque image comporte un bouton " <strong>Voir l'image"</strong> qui permet d'obtenir un aperçu en pleine résolution.</p><p><strong>Recherchez en téléchargeant une image :</strong> Nous prédisons le pic et trouvons les pics correspondants dans l'album photo.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1fb2b0304a310d2/6a17da8425daab7cda08a0fa/dca540cbf5279e6d6102c5a0c0351ddd4ac91cda-1600x1112.png" alt="Une interface utilisateur simple et claire qui permet d'effectuer une recherche multimodale texte-image et image-image pour les pics du mont Ama Dablam." /><p><strong>Recherche par texte</strong>: Trouver les sommets correspondants dans l'album à partir d'un texte.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt496c1ae8f7886320/6a17da86abe0f2da48dfe8bd/b1e8618db746cd49ea4962d3dc73031387b975dd-1600x1166.png" alt="Comment rechercher un sommet du mont Everest avec la recherche par texte dans la bibliothèque des sommets de montagne." /><h2>Conclusion</h2><p>Tout a commencé par <em>la possibilité de voir les </em><em> photos de</em><em><strong>l'Ama Dablam.</strong></em> s'est transformé en un petit système de <strong>recherche multimodale</strong> fonctionnel. Nous avons pris des photos brutes de trek, les avons transformées en <strong>encastrements SigLIP-2</strong> et avons utilisé <strong>Elasticsearch</strong> pour effectuer un <strong>kNN</strong> rapide sur les vecteurs, ainsi que des filtres géo/temporels simples pour faire remonter à la surface les bonnes images en fonction de leur <em>signification</em>. En cours de route, nous avons séparé les préoccupations en deux indices : un minuscule <code>peaks_catalog</code> de prototypes mélangés (pour l'identification) et un index évolutif <code>photos</code> de vecteurs d'images et d'EXIF (pour la recherche). Il est pratique, reproductible et facile à étendre.</p><p>Si vous souhaitez l'accorder, vous pouvez jouer avec quelques paramètres :</p><ul><li><p><strong>Paramètres de temps de recherche :</strong> <code>k</code> (nombre de voisins à récupérer) et <code>num_candidates</code> (étendue de la recherche avant la notation finale). Ces paramètres sont abordés dans le blog <a href="https://www.elastic.co/search-labs/blog/elasticsearch-knn-and-num-candidates-strategies">ici.</a></p></li><li><p><strong>Paramètres de temps d'indexation :</strong> <code>m</code> (connectivité du graphique) et <code>ef_construction</code> (précision du temps de construction par rapport à la mémoire). Pour les requêtes, expérimentez avec <code>ef_search</code> également - une valeur plus élevée signifie généralement un meilleur rappel avec un certain compromis en termes de latence. Consultez <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">ce blog</a> pour plus de détails sur ces paramètres.</p></li></ul><p>A l'avenir, des modèles natifs/rerankers pour la recherche <strong>multimodale</strong> et <strong>multilingue</strong> seront bientôt intégrés à l'écosystème Elastic, ce qui devrait rendre la recherche d'images/de textes et le classement hybride encore plus performants.<a href="https://ir.elastic.co/news/news-details/2025/Elastic-Completes-Acquisition-of-Jina-AI-a-Leader-in-Frontier-Models-for-Multimodal-and-Multilingual-Search/default.aspx?utm_source=chatgpt.com"> ir.elastic.co+1</a></p><p>Si vous souhaitez essayer vous-même :</p><ul><li><p><strong>GitHub repo</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search"><em>: https://github.com/navneet83/multimodal-mountain-peak-search</em></a></p></li><li><p><strong>Colab quickstart</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">: https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb</a></p></li></ul><p>Notre voyage s'achève donc et il est temps de prendre l'avion du retour. J'espère que cela vous a été utile et si vous le cassez (ou l'améliorez), j'aimerais savoir ce que vous avez changé.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdce2fff1569d2a8b/6a17da894b055dd1f24320a2/d324d1e1472f1bfbd8f25747f57bdeeb9c7f16b2-1600x1200.png" alt="" />]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[Recherche hybride]]></category>
    <category><![CDATA[IA]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Navneet Kumar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltccb66279debb05f9/6a17da8b63baffe228741b15/ffcf93358a7c5dadcea82faf3de460bf060d003c-1600x1200.png" length="0" type="image/png"/>
    <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Expériences d'amélioration des outils d'IA agentique pour Elasticsearch]]></title>
    <description><![CDATA[Découvrez comment nous avons amélioré les flux de travail des agents d'IA pour Elasticsearch par le biais d'expériences itératives en combinant les extracteurs linéaires, la recherche hybride et semantic_text pour une optimisation RAG évolutive.]]></description>
    <content:encoded><![CDATA[<p>Comme tout le monde ces jours-ci, ici à Elastic, nous nous lançons à fond dans le Chat, les agents et le RAG. Dans le département Search, nous avons récemment travaillé sur un Agent Builder et un Tool Registry, dans le but de rendre trivial le "chat" avec vos données dans Elasticsearch.</p><p>Lisez le <a href="https://www.elastic.co/search-labs/blog/ai-agentic-workflows-elastic-ai-agent-builder">blog Building AI Agentic Workflows with Elasticsearch</a> pour en savoir plus sur la "vue d'ensemble" de cet effort, ou <a href="https://www.elastic.co/search-labs/blog/ai-agent-builder-elasticsearch">Your First Elastic Agent : From a Single Query to an AI-Powered Chat</a> pour une introduction plus pratique.</p><p>Dans ce blog, nous allons nous intéresser à l'une des premières choses qui se produisent lorsque vous commencez à discuter et vous présenter quelques-unes des améliorations récentes que nous avons apportées.</p><h2>Que se passe-t-il ici ?</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1331b1043612efe3/6a17f115505ac3dc41ad8c3c/25a24055a166d7d6ba81d80aa35cb97163662e23-1600x443.png" alt="" /><p>Lorsque vous discutez avec vos données Elasticsearch, notre agent IA par défaut suit ce flux standard :</p><ol><li><p>Inspecter l'invite.</p></li><li><p>Identifiez l'index susceptible de contenir les réponses à cette question.</p></li><li><p>Générer une requête pour cet index, sur la base de l'invite.</p></li><li><p>Effectuez une recherche dans cet index avec cette requête.</p></li><li><p>Synthétiser les résultats.</p></li><li><p>Les résultats peuvent-ils répondre à l'invitation ? Si oui, répondez. Si ce n'est pas le cas, répétez l'opération, mais essayez quelque chose de différent.</p></li></ol><p>Cela ne devrait pas sembler trop nouveau - il s'agit simplement de Retrieval Augmented Generation (RAG). Et comme on peut s'y attendre, la qualité de vos réponses dépend fortement de la pertinence de vos premiers résultats de recherche. En travaillant à l'amélioration de la qualité de nos réponses, nous avons donc accordé une attention toute particulière aux requêtes générées à l'étape 3 et exécutées à l'étape 4. Et nous avons remarqué une tendance intéressante.</p><p>Souvent, lorsque nos premières réponses étaient "mauvaises", ce n'était pas parce que nous avions lancé une mauvaise requête. C'est parce que <em>nous avions choisi le mauvais index</em> à interroger. Les étapes 3 et 4 ne nous posaient généralement pas de problème - c'était l'étape 2.</p><h2>Que faisions-nous ?</h2><p>Notre mise en œuvre initiale était simple. Nous avions construit un outil (appelé index_explorer) qui faisait effectivement un <code>_cat/indices</code> pour lister tous les indices disponibles, puis demandait au LLM d'identifier lequel de ces indices correspondait le mieux au message/à la question/à l'invitation de l'utilisateur. Vous pouvez voir cette <a href="https://github.com/elastic/kibana/blob/0cc78184957fcd12110dabae50353392ea937508/x-pack/platform/packages/shared/onechat/onechat-genai-utils/tools/index_explorer.ts#L98-L113">mise en œuvre originale ici.</a></p>You are an AI assistant for the Elasticsearch company.
based on a natural language query from the user, your task is to select up to ${limit} most relevant indices from a list of indices.

*The natural language query is:* ${nlQuery}

*List of indices:*
${indices.map((index) =&gt; `- ${index.index}`).join('\n')}

Based on those information, please return most relevant indices with your reasoning.
Remember, you should select at maximum ${limit} indices.<p>Dans quelle mesure cela a-t-il fonctionné ? Nous n'étions pas sûrs ! Nous avions des exemples clairs de <em>dysfonctionnements</em>, mais notre premier défi était de quantifier notre situation actuelle.</p><h2>Établir une base de référence</h2><h3>Cela commence par des données</h3><p>Nous avions besoin d'un ensemble de données en or pour mesurer l'efficacité d'un outil à sélectionner le bon indice à partir d'une demande de l'utilisateur et d'un ensemble préexistant d'indices. Comme nous ne disposions pas d'un tel ensemble de données, nous en avons créé un.</p><p>Reconnaissance : Il ne s'agit pas d'une "meilleure pratique", nous le savons. Mais parfois, il est préférable d'aller de l'avant plutôt que de faire du surplace. <a href="https://www.elastic.co/about/our-source-code#progress-perfection">Le progrès, la perfection SIMPLE</a>.</p><p>Nous avons généré des indices de semences pour plusieurs domaines différents à l'aide de <a href="https://gist.github.com/seanstory/a08db2e149897da656db3a1ca72e17ac">cette invite</a>. Ensuite, pour chaque domaine généré, nous avons généré quelques indices supplémentaires en utilisant<a href="https://gist.github.com/seanstory/a280a85d067e61bfeb5911bf2654e6e2"> cette invite</a> (l'objectif étant ici de semer la confusion pour le LLM avec des négatifs durs et des exemples difficiles à classer). Ensuite, nous avons édité manuellement chaque index généré et ses descriptions. Enfin, nous avons généré des requêtes de test à l'aide de <a href="https://gist.github.com/seanstory/44291b666c05a383136f6e36bb9106fa">cette invite</a>, ce qui nous a permis d'obtenir des échantillons de données tels que les suivants :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1bd9cd78154195e3/6a17f117dbb4fff7b5fb57d2/9d96d87e286eddbc012402b1ecccd57419a99253-1600x782.png" alt="" /><p>et des cas de test tels que :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltadf30a0aeafd56ef/6a17f1192f4a5c160ffa89eb/4c2e9ad941d98d7e66033bbc08c9b8060ec19097-1600x797.png" alt="" /><h3>Élaboration d'un harnais de test</h3><p>À partir de là, la procédure a été très simple. Script up a tool that could :</p><ol><li><p>Faire table rase du passé avec un cluster Elasticsearch cible.</p></li><li><p>Créer tous les indices définis dans le jeu de données cible.</p></li><li><p>Pour chaque scénario de test, exécutez l'outil i<code>ndex_explorer</code> (nous disposons d'une <a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-post-agent-builder-tools-execute">API pour l'exécution de l'outil</a>).</p></li><li><p>Comparez l'indice du résultat à l'indice attendu et saisissez le résultat.</p></li><li><p>Une fois tous les scénarios de test terminés, les résultats sont présentés sous forme de tableau.</p></li></ol><h3>L'enquête dit...</h3><p>Les premiers résultats ont été, sans surprise, médiocres.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt73367741359e258d/6a17f11a505ac39749ad8c40/9c10679bcd6291edfa2a9ba42e7dd922aa483f0b-1216x806.png" alt="" /><p>Dans l'ensemble, 77,14% ont identifié avec précision le bon indice. Et ce, dans le meilleur des cas, c'est-à-dire lorsque tous les indices ont des noms appropriés et sémantiquement significatifs. Quiconque a déjà fait un `PUT test2/_doc/foo {...}` sait que vos index n'ont pas toujours des noms significatifs.</p><p>Nous disposons donc d'une base de référence, qui montre qu'il y a beaucoup de place pour l'amélioration. Il était temps de faire de la science ! 🧪</p><h2>Expérimentation</h2><h3>Hypothèse 1 : Les cartographies aideront à</h3><p>L'objectif est ici d'identifier un index qui contiendra des données pertinentes pour le message original. La partie d'un index qui décrit le mieux les données qu'il contient est le <em>mappage de</em> l'index. Même sans saisir d'échantillons du contenu de l'index, le fait de savoir que l'index possède un champ prix de type double implique que les données représentent quelque chose à vendre. Un champ auteur de type texte implique des données linguistiques non structurées. L'association des deux pourrait impliquer que les données sont des livres, des histoires ou des poèmes. De nombreux indices sémantiques peuvent être déduits de la seule connaissance des propriétés d'un index. Dans une branche locale, j'ai donc ajusté notre `.index_explorer` pour envoyer les mappings complets d'un index (ainsi que son nom) au LLM afin qu'il prenne sa décision. </p><p>Le résultat (à partir des journaux Kibana) :</p>[2025-09-05T11:01:21.552-05:00][ERROR][plugins.onechat] Error: Error calling connector: event: error
data: {"error":{"code":"request_entity_too_large","message":"Received a content too large status code for request from inference entity id [.rainbow-sprinkles-elastic] status [413]","type":"error"}}


    at createInferenceProviderError (errors.ts:90:10)
    at convertUpstreamError (convert_upstream_error.ts:39:38)
    at handle_connector_response.ts:26:33
    at Observable.init [as _subscribe] (/Users/seanstory/Desktop/Dev/kibana/node_modules/rxjs/src/internal/observable/throwError.ts:123:68)...<p>Les premiers auteurs de l'outil l'avaient prévu. Si le mappage d'un index est une mine d'or d'informations, c'est aussi un bloc de JSON assez verbeux. Et dans un scénario réaliste où vous comparez de nombreux indices (notre ensemble de données d'évaluation en définit 20), ces blobs JSON s'accumulent. Nous voulons donc donner au LLM plus de contexte pour sa décision que de simples noms d'index pour toutes les options, mais pas autant que les mappings complets de chacune d'entre elles.</p><h3>Hypothèse 2 : des correspondances "aplaties" (listes de champs) en guise de compromis</h3><p>Nous sommes partis de l'hypothèse que les créateurs d'index utiliseront des noms d'index sémantiquement significatifs. Et si nous étendions cette hypothèse aux noms des champs ? Notre expérience précédente a échoué parce que le mappage de JSON comprend BEAUCOUP de métadonnées et d'éléments parasites.</p>     "description_text": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword"
            }
          },
          "copy_to": [
            "description_semantic"
          ]
        },<p>Le bloc ci-dessus, par exemple, compte 236 caractères et définit un seul champ dans une correspondance Elasticsearch. Alors que la chaîne "description_text" ne comporte que 16 caractères. Le nombre de caractères a été multiplié par près de 15, sans amélioration sémantique significative de la description de ce que ce champ implique à propos des données disponibles. Que se passerait-il si nous récupérions les correspondances pour tous les indices, mais qu'avant de les envoyer au LLM, nous les "aplatissions" en une simple liste de noms de champs ?</p><p>Nous avons essayé.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta5eda7a79493ee81/6a17f11c9da390327fe46590/112c2f447c11f154b5082725cd49b51d0a3c8a65-1214x804.png" alt="" /><p>C'est formidable ! Des améliorations dans tous les domaines. Mais pourrions-nous faire mieux ?</p><h3>Hypothèse 3 : Descriptions dans le mapping _meta</h3><p>Si le simple fait de nommer les champs sans contexte supplémentaire a provoqué un tel saut, on peut supposer que l'ajout d'un contexte substantiel serait encore plus efficace ! Il n'est pas nécessairement conventionnel que chaque index soit accompagné d'une description, mais il est possible d'ajouter des métadonnées de tout type au niveau de l'index à l'objet _meta de la cartographie. Nous avons repris les index générés et ajouté des descriptions pour chaque index de notre ensemble de données. Tant que les descriptions ne sont pas trop longues, elles devraient utiliser moins de tokens que la cartographie complète et fournir de bien meilleures indications sur les données incluses dans l'index. Notre expérience a validé cette hypothèse.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt61b85cf40e0e6357/6a17f11dfbc5f82809491bbe/32d2692ad4479d0e52d8ee723dcc5710a6ec90f3-1208x806.png" alt="" /><p>Une amélioration modeste, et nous sommes maintenant &gt;90% précis dans tous les domaines.</p><h3>Hypothèse 4 : La somme est plus grande que les parties</h3><p>Les noms de champs ont permis d'améliorer nos résultats. Les descriptions ont permis d'accroître nos résultats. L'utilisation des descriptions ET <em>des </em>noms de champs devrait donc permettre d'obtenir de meilleurs résultats, n'est-ce pas ?</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte6297c6aaf7db802/6a17f11e14d90c1bd779b6e6/114cbb408ff16b136251d2265416bd5270380fe5-1208x794.png" alt="" /><p>Les données ont répondu "non" (pas de changement par rapport à l'expérience précédente). La théorie principale était que, puisque les descriptions ont été générées à partir des champs/mappings de l'index, il n'y a pas assez d'informations différentes entre ces deux éléments de contexte pour ajouter quelque chose de "nouveau" lorsqu'on les combine. En outre, la charge utile que nous envoyons pour nos 20 indices de test devient assez importante. Le raisonnement que nous avons suivi jusqu'à présent n'est pas extensible. En fait, il y a de bonnes raisons de croire qu'aucune des expériences que nous avons menées jusqu'à présent ne fonctionnerait sur des clusters Elasticsearch où il y a des centaines ou des milliers d'indices à choisir. Toute approche qui augmente linéairement la taille du message envoyé au LLM à mesure que le nombre total d'indices augmente n'est probablement pas une stratégie généralisable.</p><p>Ce dont nous avons vraiment besoin, c'est d'une approche qui nous aide à réduire un grand nombre de candidats aux options les plus pertinentes...</p><p>Il s'agit d'un problème de recherche.</p><h3>Hypothèse 5 : Sélection par recherche sémantique</h3><p>Si le nom d'un index a une signification sémantique, il peut être stocké sous forme de vecteur et faire l'objet d'une recherche sémantique.</p><p>Si les noms des champs d'un index ont une signification sémantique, ils peuvent être stockés sous forme de vecteurs et faire l'objet d'une recherche sémantique.</p><p>Si un index possède une description ayant une signification sémantique, il peut lui aussi être stocké sous forme de vecteur et faire l'objet d'une recherche sémantique.</p><p>Aujourd'hui, les index Elasticsearch ne rendent aucune de ces informations consultables (peut-être devrions-nous le faire !), mais il était assez simple de<a href="https://github.com/elastic/connectors/pull/3638"> bricoler quelque chose</a> qui pouvait combler cette lacune. En utilisant le cadre de connecteur d'Elastic, j'ai construit un connecteur qui produirait un document pour chaque index dans un cluster. Les documents de sortie ressembleraient à quelque chose comme :</p> doc = {
                "_id": index_name,
                "index_name": index_name,
			"meta_description”: description,
"field_descriptions" = field_descriptions,
                "mapping": json.dumps(mapping),  
                "source_cluster": self.es_client.configured_host,
            }<p>J'ai envoyé ces documents vers un nouvel index où j'ai défini manuellement le mappage :</p>{
   "mappings": {
       "properties": {
           "semantic_content": {
               "type": "semantic_text"
           },
           "index_name": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "mapping": {
               "type": "keyword",
               "copy_to": "semantic_content"
           },
           "source_cluster": {
               "type": "keyword"
           },
           "meta_description": {
               "type": "text",
               "copy_to": "semantic_content"
           },
           "field_descriptions": {
               "type": "text",
               "copy_to": "semantic_content"
           }
       }
   }
}<p>Cela crée un champ unique semantic_content, dans lequel tous les autres champs ayant une signification sémantique sont regroupés et indexés. La recherche dans cet index devient triviale, avec simplement :</p>GET indexed-indices/_search
{
 "query": {
   "semantic": {
     "field": "semantic_content",
     "query": "$query"
   }
 }
}<p>L'outil <code>index_explorer</code> modifié est maintenant <em>beaucoup</em> plus rapide, car il n'a pas besoin de faire une demande à un LLM, mais peut demander un seul encastrement pour la requête donnée et effectuer une opération de recherche vectorielle efficace. En prenant le premier hit comme index sélectionné, nous avons obtenu les résultats suivants :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc27c302e6bef0b23/6a17f120577262d2f21bccdc/06ef5d78040d064d3444793f636d527d9e19a869-1214x800.png" alt="" /><p>Cette approche est évolutive. Cette approche est efficace. Mais cette approche est à peine meilleure que notre ligne de base. Ce n'est pas surprenant, car l'approche de la recherche est incroyablement naïve. Il n'y a aucune nuance. Aucune reconnaissance du fait que le nom et la description d'un index devraient avoir plus de poids qu'un nom de champ arbitraire que l'index contient. Pas de possibilité de pondérer les correspondances lexicales exactes par rapport aux correspondances synonymes. Cependant, la construction d'une requête très nuancée nécessiterait de supposer BEAUCOUP de choses sur les données disponibles. Jusqu'à présent, nous avons déjà fait des hypothèses importantes sur la signification sémantique des noms d'index et de champs, mais nous devrions aller plus loin et commencer à supposer la signification <em>qu</em> 'ils ont et la manière dont ils sont liés les uns aux autres. Sans cela, nous ne pouvons probablement pas identifier de manière fiable la meilleure correspondance comme premier résultat, mais nous pouvons plus probablement dire que la meilleure correspondance se trouve quelque part dans les N premiers résultats. Nous avons besoin de quelque chose qui puisse consommer des informations sémantiques dans le contexte dans lequel elles existent, en les comparant à celles d'une autre entité qui peut se représenter d'une manière sémantiquement distincte, et juger entre elles. Comme un LLM.</p><h3>Hypothèse 6 : Réduction du nombre de candidats</h3><p>Il y a eu bien d'autres expériences que je vais passer sous silence, mais la principale avancée a été d'abandonner le désir de choisir la meilleure correspondance uniquement à partir d'une recherche sémantique, et d'utiliser plutôt la recherche sémantique comme un filtre pour éliminer les indices non pertinents de la considération du LLM. Nous avons combiné la recherche linéaire, la recherche hybride avec RRF et <code>semantic_text</code> pour <a href="https://gist.github.com/seanstory/d704443120e20f6c844db10e30066860">notre recherche</a>, en limitant les résultats aux 5 premiers indices correspondants.</p><p>Ensuite, pour chaque correspondance, nous avons ajouté le nom de l'index, la description et les noms des champs à un message pour le LLM. Les résultats ont été fantastiques :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ac4cb8f7153fdf9/6a17f121af47b66d1dcde082/8fcabd78f591f90d6bc7c0e087d31317e4eef791-1206x804.png" alt="" /><p>La plus grande précision de toutes les expériences réalisées à ce jour ! Et comme cette approche n'augmente pas la taille du message proportionnellement au nombre total d'indices, elle est beaucoup plus évolutive.</p><h2>Résultats</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8d66130d9fae6bea/6a17f123ddf97d7527910c19/04d630797213dbb8bf567da41d1cdd5c7b4586c9-1600x521.png" alt="" /><p>Le premier résultat clair est que notre base de référence <em>peut être</em> améliorée. Cela semble évident rétrospectivement, mais avant le début de l'expérimentation, des discussions sérieuses ont eu lieu sur la question de savoir si nous devions abandonner complètement notre outil <code>index_explorer</code> et nous fier à la configuration explicite de l'utilisateur pour limiter l'espace de recherche. Bien que cela reste une option viable et valable, cette recherche montre qu'il existe des voies prometteuses vers l'automatisation de la sélection de l'indice lorsque les données de l'utilisateur ne sont pas disponibles.</p><p>Le résultat suivant a été que le simple fait d'ajouter des caractères de description au problème a un rendement décroissant. Avant cette recherche, nous nous demandions si nous devions investir dans l'extension de la capacité d'Elasticsearch à stocker des <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-field-meta">métadonnées au niveau des champs</a>. Aujourd'hui, ces valeurs <code>meta</code> sont plafonnées à 50 caractères, et l'on a supposé qu'il faudrait augmenter cette valeur pour pouvoir obtenir une compréhension sémantique de nos champs. Ce n'est manifestement pas le cas, et le LLM semble s'en sortir assez bien avec des noms de domaines. Nous pourrons approfondir cette question ultérieurement, mais elle ne nous semble plus urgente.</p><p>Inversement, cela a clairement démontré l'importance d'avoir des métadonnées d'index "consultables". Pour ces expériences, nous avons piraté un index des indices. Mais c'est quelque chose que nous pourrions étudier en l'intégrant directement dans Elasticsearch, en créant des API pour le gérer, ou au moins en établissant une convention à ce sujet. Nous allons évaluer nos options et en discuter en interne, alors restez à l'écoute.</p><p>Enfin, cet effort a confirmé l'intérêt de prendre le temps d'expérimenter et de prendre des décisions fondées sur des données. En fait, cela nous a aidés à réaffirmer que notre produit Agent Builder aura besoin de capacités d'évaluation robustes et intégrées au produit. Si nous devons créer un ensemble de tests uniquement pour un outil qui prélève des indices, nos clients auront absolument besoin de moyens pour évaluer qualitativement leurs outils personnalisés au fur et à mesure qu'ils procèdent à des ajustements itératifs.</p><p>Je suis impatient de voir ce que nous allons construire, et j'espère que vous l'êtes aussi !</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-agent-builder-experiments-performance</guid>
    <category><![CDATA[IA agentique]]></category>
    <category><![CDATA[À l'intérieur d'Elastic]]></category>
    <category><![CDATA[Recherche hybride]]></category>
    <dc:creator><![CDATA[Sean Story]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68d11a4c7fd11d4c/6a17f1257b54f9b6598b39d4/42903c869e034674b30bb36013345aaa97f6608b-1184x864.png" length="0" type="image/png"/>
    <pubDate>Mon, 06 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[La recherche hybride revisitée : introduction de la recherche linéaire dans Elasticsearch !]]></title>
    <description><![CDATA[Découvrez comment le récupérateur linéaire améliore la recherche hybride en exploitant les scores pondérés et la normalisation MinMax pour des classements plus précis et plus cohérents, et apprenez à l'utiliser.]]></description>
    <content:encoded><![CDATA[<p>Dans notre <a href="https://www.elastic.co/fr/search-labs/blog/elasticsearch-retrievers-ga-8.16.0">précédent article</a> de blog, nous avons présenté le cadre des récupérateurs remanié de A à Z, qui permet de créer des pipelines de classement complexes. Nous avons également étudié la manière dont le récupérateur Reciprocal Rank Fusion (RRF) permet une recherche hybride en fusionnant les résultats de différentes requêtes. Bien que la méthode RRF soit facile à mettre en œuvre, elle présente une limitation notable : elle se concentre uniquement sur les rangs relatifs, sans tenir compte des scores réels. Cela rend le réglage fin et l'optimisation difficiles.</p><h2>Rencontrez le retriever linéaire !</h2><p>Dans ce billet, nous vous présentons le <a href="https://www.elastic.co/fr/docs/solutions/search/retrievers-overview#retrievers-overview-types"><code>linear</code></a> <a href="https://www.elastic.co/fr/docs/solutions/search/retrievers-overview#retrievers-overview-types">retriever</a>, notre dernier ajout pour soutenir la recherche hybride ! Contrairement à <code>rrf</code>, le récupérateur <code>linear</code> calcule une somme pondérée de toutes les requêtes correspondant à un document. Cette approche préserve l'importance relative de chaque document dans un ensemble de résultats tout en permettant un contrôle précis de l'influence de chaque requête sur le score final. Il s'agit donc d'un moyen plus intuitif et plus souple d'affiner la recherche hybride.</p><p>Définition d'un récupérateur linéaire où le score final sera calculé comme suit :</p><p>C'est aussi simple que cela :</p>GET linear_retriever_blog/_search
{
   "retriever": {
       "linear": {
           "retrievers": [
               {
                   "retriever": {
                       "knn": {
                          ...
                        }
                    },
                   "weight": 5
               },
                  {
                   "retriever": {
                       "standard": {
                          ...
                        }
                    },
                   "weight": 1.5
               },


           ]
        }
     }
}<p>Vous avez remarqué à quel point c'est simple et intuitif ? (et très similaire à <code>rrf</code>!) Cette configuration vous permet de contrôler précisément la contribution de chaque type de requête au classement final, contrairement à <code>rrf</code>, qui s'appuie uniquement sur les classements relatifs.</p><p>Une mise en garde subsiste : les scores <code>knn</code> peuvent être strictement limités, en fonction de la métrique de similarité utilisée. Par exemple, avec la similarité en cosinus ou le produit de points de vecteurs normalisés en unités, les scores se situeront toujours dans l'intervalle <code>[0, 1]</code>. En revanche, les scores sur <code>bm25</code> sont moins prévisibles et n'ont pas de limites clairement définies.</p><h2>Mise à l'échelle des scores : kNN vs BM25</h2><p>L'une des difficultés de la recherche hybride réside dans le fait que les différents outils de recherche produisent des résultats sur des échelles différentes. Prenons par exemple le scénario suivant :</p><p>La requête A obtient un score :</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>100</p><p>1.5</p><p>1</p><p>0.5</p><p>Les résultats de la requête B :</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>0.63</p><p>0.01</p><p>0.3</p><p>0.4</p><p>Vous pouvez voir la disparité ci-dessus : les scores de <code>kNN</code> se situent entre 0 et 1, tandis que les scores de <code>bm25</code> peuvent varier considérablement. Cette différence rend difficile l'établissement de poids statiques optimaux pour la combinaison des résultats.</p><h2>La normalisation à la rescousse : le normalisateur MinMax</h2><p>Pour y remédier, nous avons introduit un normalisateur <code>minmax</code> facultatif qui adapte les scores, indépendamment pour chaque requête, à la plage <code>[0, 1]</code> en utilisant la formule suivante :</p><p>Cela permet de préserver l'importance relative de chaque document dans l'ensemble des résultats d'une requête, ce qui facilite la combinaison des scores obtenus par différents extracteurs. Avec la normalisation, les scores deviennent :</p><p>La requête A obtient un score :</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>1.00</p><p>0.01</p><p>0.005</p><p>0.000</p><p>Les résultats de la requête B :</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>1.00</p><p>0.000</p><p>0.465</p><p>0.645</p><p>Tous les scores se situent désormais dans la fourchette <code>[0, 1]</code> et l'optimisation de la somme pondérée est beaucoup plus simple car nous capturons désormais l'importance (relative à la requête) d'un résultat au lieu de son score absolu et nous maintenons la cohérence entre les requêtes.</p><h2>Exemple de récupérateur linéaire </h2><p>Prenons maintenant un exemple pour illustrer ce qui précède et la façon dont le récupérateur <code>linear</code> comble certaines lacunes de <code>rrf</code>. RRF se base uniquement sur les rangs relatifs et ne prend pas en compte les différences de score réelles. Par exemple, compte tenu de ces scores :</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>100</p><p>1.5</p><p>1</p><p>0.5</p><p>score du rrf</p><p>0.03226</p><p>0.03252</p><p>0.03200</p><p>0.03125</p><p>rrf classerait les documents comme suit :</p><p>Cependant, doc1 a un score <code>bm25</code> nettement plus élevé que les autres, ce que <code>rrf</code> ne parvient pas à capturer car il ne tient compte que des rangs relatifs. L'outil <code>linear</code> retriever, combiné à la normalisation, prend correctement en compte les scores et leurs différences, ce qui permet d'obtenir un classement plus significatif :</p><p></p><p>doc1</p><p>doc2</p><p>doc3</p><p>doc4</p><p>knn</p><p>0.347</p><p>0.35</p><p>0.348</p><p>0.346</p><p>bm25</p><p>1</p><p>0.01</p><p>0.005</p><p>0</p><p>Comme nous pouvons le voir ci-dessus, l'excellent classement de doc1 et <code>score</code> pour <code>bm25</code> est correctement pris en compte et reflété dans les notes finales. En outre, tous les scores se situent désormais dans la fourchette <code>[0, 1]</code>, ce qui nous permet de les comparer et de les combiner de manière beaucoup plus intuitive (et même de mettre en place des processus d'optimisation hors ligne).</p><h2>La mise en place de l'ensemble</h2><p>Pour tirer pleinement parti de l'outil de recherche <code>linear</code> et de la normalisation, la demande de recherche devrait ressembler à ce qui suit :</p>GET linear_retriever_blog/_search
{
   "retriever": {
       "linear": {
           "retrievers": [
               {
                   "retriever": {
                       "knn": {
                          ...
                        }
                    },
                   "weight": 5
               },
                  {
                   "retriever": {
                       "standard": {
                          ...
                        }
                    },
                   "weight": 1.5,
                   "normalizer": "minmax"
               },


           ]
       }
   }
}<p>Cette approche combine le meilleur des deux mondes : elle conserve la flexibilité et la notation intuitive du <code>linear</code> retriever, tout en garantissant une mise à l'échelle cohérente des scores grâce à la normalisation MinMax.</p><p>Comme tous nos retrievers, le retriever <code>linear</code> peut être intégré à n'importe quel niveau d'un arbre hiérarchique de retrievers, avec une prise en charge de l'explicabilité, de la mise en évidence des correspondances, du regroupement des champs, etc.</p><h2>Quand choisir le retriever linéaire et pourquoi cela fait une différence</h2><p>Le <code>linear</code> retriever :</p><ul><li><p>Préserve l'importance relative en s'appuyant sur les scores réels, et pas seulement sur les rangs.</p></li><li><p>Permet un réglage fin avec des contributions pondérées de différentes requêtes.</p></li><li><p>Améliore la cohérence grâce à la normalisation, ce qui rend la recherche hybride plus robuste et plus prévisible.</p></li></ul><h2>Conclusion</h2><p>Le récupérateur <code>linear</code> est déjà disponible sur Elasticsearch Serverless, et les versions 8.18 et 9.0 ! D'autres exemples et paramètres de configuration sont également disponibles dans notre documentation. Essayez-le et voyez comment il peut améliorer votre expérience de la recherche hybride - nous attendons vos commentaires. Bonne recherche !</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/linear-retriever-hybrid-search</guid>
    <category><![CDATA[Pertinence]]></category>
    <category><![CDATA[Recherche hybride]]></category>
    <dc:creator><![CDATA[Panagiotis Bailis]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte58a751cbcc1153d/6a17e3c76317305c4f585a10/7a07e27e3095463ff93b4cb7f8a0cf3b8e44eab0-1777x1000.png" length="0" type="image/png"/>
    <pubDate>Wed, 28 May 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>