<?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[Base vectorielle - 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[Base vectorielle - 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/vector-database</link>
    </image>
    <link>https://www.elastic.co/fr/search-labs/blog/category/vector-database</link>
    <atom:link href="https://www.elastic.co/fr/search-labs/rss/category/vector-database.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[fr]]></language>
    <lastBuildDate>Tue, 22 Sep 2026 04:43:22 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[Comment nous avons construit Elasticsearch simdvec pour faire de la recherche vectorielle l'une des plus rapides au monde]]></title>
    <description><![CDATA[Comment nous avons conçu Elasticsearch simdvec, la bibliothèque de noyaux SIMD optimisée manuellement qui alimente chaque requête de recherche vectorielle dans Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch simdvec est le moteur de calcul de distance vectorielle dans Elasticsearch. Il fournit des noyaux AVX-512 et NEON ajustés manuellement pour chaque type de vecteur pris en charge par Elasticsearch. Son architecture de calcul par lots masque la latence mémoire grâce à un prefetching explicite sur x86 et un chargement entrelacé sur ARM, surpassant jusqu'à 4 fois les performances de bibliothèques comme FAISS et jvector lorsque le volume de données dépasse la capacité du cache du processeur. Dans cet article, nous expliquons les raisons de sa création, son fonctionnement interne et comment il contribue à faire de la recherche vectorielle d'Elasticsearch l'une des plus rapides au monde.</p><h2>Comment nous avons conçu Elasticsearch simdvec</h2><p>Chaque requête de recherche vectorielle dans Elasticsearch, qu'il s'agisse d'un balayage transversal <a href="https://arxiv.org/abs/1603.09320">Hierarchical Navigable Small World (HNSW)</a>, d'un balayage de fichier inversé (IVF) ou d'une passe de reclassement, se résume au même problème : calculer les distances entre les vecteurs, des millions de fois par requête. Elasticsearch prend en charge un large éventail de types de données et de stratégies de quantification, de float32 à int8, bfloat16, binaire et quantification binaire améliorée (BBQ). Chacune présente des compromis différents entre mémoire, débit et rappel. Derrière tout cela se cache un moteur unique : simdvec.</p><p>Nous avons conçu simdvec pour rendre chaque calcul de distance aussi rapide que le permet le matériel. Dans cet article, nous expliquons pourquoi nous l'avons conçu, ce qu'il contient et où il a le plus d'impact.</p><h3>Construit comme une voiture de course</h3><p>En tant que passionnés de Formule 1 (l'un d'entre nous a travaillé pour l'écurie Ferrari), nous constatons un parallèle évident. Une Formule 1 est conçue dans un seul but : réaliser le meilleur temps au tour. La puissance du moteur, l'aérodynamisme et la conception du châssis n'ont d'importance que dans la mesure où ils contribuent à cet objectif. Il en va de même pour une base vectorielle, où le débit d'indexation, la latence des requêtes et le rappel sont les facteurs déterminants de sa performance.</p><p>Bien que le résultat final soit ce qui compte, atteindre les plus hauts niveaux de performance nécessite que chaque composant contribue de façon optimale. Il ne doit pas être simplement <em>suffisant</em>, mais <em>le meilleur </em>de sa catégorie. Simdvec est conçu dans cet esprit, en se concentrant sur une partie critique du système : le moteur. C'est une bibliothèque de noyaux dédiée, optimisée pour le <a href="https://en.wikipedia.org/wiki/Single_instruction,_multiple_data">Single Instruction Multiple Data</a> (SIMD), qui fournit des fonctions de distance C++ natives optimisées et appelées depuis Java via l'interface de fonction étrangère (FFI) <a href="https://openjdk.org/projects/panama/">Panama</a>. Il prend en charge le scoring en lots, le prefetching des lignes de cache et tous les types et mises en page de vecteurs utilisés dans Elasticsearch.</p><p>C'est le moteur derrière chaque requête.</p><h3>Pourquoi nous avons créé le nôtre</h3><p>Nous avons commencé en 2023 avec l'API Panama Vector dans Apache Lucene. Elle fonctionnait bien pour les produits scalaires de nombres flottants 32 bits, mais les besoins d'Elasticsearch ont rapidement dépassé ses capacités. Elasticsearch prend en charge une large gamme de types de vecteurs quantifiés : int8, int4, bfloat16, mono-bit et BBQ asymétrique. Chacun possède des stratégies SIMD, des compositions de packs et des exigences d'accumulateur différents. Au-delà de la couverture des types, les méthodes de scoring d'Elasticsearch exigent un débit supérieur à celui d'une simple paire : HNSW doit évaluer plusieurs voisins du graphe en une seule passe, IVF nécessite un scoring en lots de milliers de candidats avec prefetching, et le scoring sur disque doit fonctionner directement sur la mémoire mappée en mémoire sans copie. Nous avons examiné les solutions disponibles, mais aucune ne couvrait l'ensemble des besoins.</p><p>Nous avons donc développé simdvec : des noyaux C++ natifs optimisés manuellement, appelés depuis Java via FFI, avec scoring par lots, prefetching et prise en charge de tous les types vectoriels utilisés par Elasticsearch. En étant propriétaires de la bibliothèque, nous maîtrisons l'intégralité de la pile. Lorsque nous ajoutons un nouveau type de quantification comme BBQ, il bénéficie d'un noyau SIMD optimisé intégré à l'ensemble du système. Nous n'attendons pas qu'une bibliothèque tierce le prenne en charge et nous ne faisons aucun compromis sur les performances, quel que soit le type. Chaque requête vectorielle dans Elasticsearch – HNSW, IVF, de reclassement ou hybride – s'exécute sur ce moteur, conçu autour des opérations et des types que nous utilisons réellement.</p><p>Simdvec possède des bibliothèques natives distinctes pour x86 et ARM, chacune comportant plusieurs niveaux d'architecture du jeu d'instructions (ISA) sélectionnés au démarrage. La surcharge des appels depuis Java via FFI est très faible, de l'ordre de quelques <a href="https://github.com/ldematte/simsimd-benchmarks/blob/main/COMPARISON.md#ffm-downcall-overhead-measurements">nanosecondes</a>.</p><h3>Le panorama</h3><p>Nous ne sommes pas les seuls à développer des noyaux de distance vectorielle optimisés pour SIMD. L'écosystème est riche et nous souhaitions comprendre les performances de simdvec. Non pas pour classer les projets, mais pour contextualiser et situer le moteur d'Elasticsearch. Nous avons sélectionné trois projets comme points de référence, chacun représentant une approche différente :</p><ul><li><p><strong>jvector</strong> : une bibliothèque Java de recherche de plus proches voisins approximatifs (ANN) qui utilise l'API Panama Vector pour le calcul de distance vectorisé, avec une accélération C native optionnelle sur x86.</p></li><li><p><strong>FAISS</strong> : un framework de recherche vectorielle open source largement déployé, avec des noyaux AVX2/AVX-512 ajustés manuellement.</p></li><li><p><strong>NumKong</strong> (anciennement SimSIMD) : une suite complète de plus de 2 000 noyaux SIMD ajustés manuellement, couvrant les fonctions de distance, les opérations matricielles et le calcul géospatial.</p></li></ul><p>Chaque projet répond à un objectif différent et fait l'objet de compromis différents. Nous incluons des numéros de référence provenant d'eux pour donner du contexte à la performance de simdvec sur les opérations spécifiques dont Elasticsearch a besoin.</p><h3>Comment nous mesurons</h3><p>Les <a href="https://github.com/ChrisHegarty/jvector-kernel-benchmarks">benchmarks simdvec et jvector</a> sont écrits en Java avec JMH, le harnais de microbenchmark JVM standard, avec la surcharge FFI incluse. Pour les <a href="https://github.com/ldematte/simsimd-benchmarks">benchmarks NumKong</a> et <a href="https://github.com/ChrisHegarty/faiss-kernel-benchmarks">FAISS</a>, nous avons écrit de petits programmes C/C++ utilisant Google Benchmark, qui est le framework standard de microbenchmark C++. Ces deux frameworks mesurent les temps d'exécution en nanosecondes, après une phase de warmup et un étalonnage des itérations. Nous avons vérifié, grâce à des compteurs de performance matériels, que toutes les bibliothèques utilisent SIMD sur les deux plateformes. L'ensemble du code des benchmarks est disponible publiquement dans les référentiels GitHub associés (et, pour simdvec, dans le référentiel <a href="https://github.com/elastic/elasticsearch">elasticsearch</a>).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt90a7d66553be3c9d/6a1701d166c4f942caf8bea5/aee116772df161cf86b7668f575ac34c733a23c5-1580x238.png" alt="Tableau listant deux plateformes : x86 avec AMD EPYC Turin (Zen 5), AVX2 et AVX‑512, AWS c8a.4xlarge, et ARM avec Graviton 4 (Neoverse V2), NEON et SVE2, AWS c8g.4xlarge." /><p><strong>Logiciel :</strong> JDK 25.0.2, JMH 1.37, GCC 14, Google Benchmark (dernière version).</p><h2>Un vecteur à la fois</h2><p>L'opération fondamentale de la recherche vectorielle consiste à calculer la distance entre deux vecteurs. Chaque évaluation de voisinage HNSW, chaque score de candidat IVF, chaque comparaison de reclassement ramène à cette boucle interne.</p><p>Nous avons mesuré le débit d'une seule paire à 1 024 dimensions sur les deux plateformes, en commençant par le type float32, le type de référence et celui où l'écosystème est le plus compétitif. Nous avons comparé simdvec à FAISS et jvector ; nous avons exclu NumKong car il utilise des accumulateurs float64 pour float32, ce qui le rend 3,2 à 5,3 fois plus lent (selon la plateforme), privilégiant la précision numérique au débit. Pour une comparaison équitable, nous avons testé NumKong sur int8, où il utilise la même stratégie d'accumulateurs que simdvec.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte3bc2ecaf3ad2b61/6a1701d2ab7f08039ddb9d0f/352cfa0fc18f123140f746d843404f80127fb1b7-1500x675.png" alt="Graphique à barres horizontales intitulé &quot;float32 Dot Product – AMD Turin&quot; qui compare cinq implémentations : FAISS AVX‑512 à 23,2 ns/op, ES simdvec AVX‑512 à 28,3 ns/op, FAISS AVX2 à 36,4 ns/op, ES simdvec AVX2 à 38,9 ns/op et jvector à 43,9 ns/op." /><p>Sur x86, FAISS AVX-512 est le noyau à paire unique le plus rapide à 23 ns. Simdvec AVX-512 suit à 28 ns, un écart qui reflète la surcharge d'appel FFI. Les deux utilisent le FMA 512 bits avec déroulement par accumulateurs multiples. Au niveau AVX2, les deux sont beaucoup plus proches, 36 ns et 39 ns respectivement, tous deux limités par le registre de 256 bits et les largeurs de chargement en mémoire. jvector arrive à 44 ns grâce à l'API Java Panama Vector. Panama génère un bon code SIMD, mais les intrinsèques C++ optimisées manuellement conservent un avantage.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcbbbe442631a575d/6a1701d42b835f81bcf4b083/95e44d9767f21ec4bccaed835e3c99aa180431ee-1500x495.png" alt="Graphique à barres horizontales intitulé &quot;float32 Dot Product – Graviton 4 (ARM)&quot; montrant ES simdvec à 70,2 ns/op, jvector à 110,0 ns/op et FAISS à 155,6 ns/op." /><p>Sur ARM, simdvec affiche le meilleur temps d'exécution (70 ns), devançant largement jvector (110 ns) et FAISS (156 ns). Simdvec utilise des noyaux NEON optimisés manuellement pour aarch64. Jvector, quant à lui, ne possède aucun code ARM natif et repose sur Panama. FAISS s'appuie sur la vectorisation automatique du compilateur plutôt que sur des fonctions intrinsèques NEON explicites, ce qui explique l'écart plus important. Ceci illustre l'avantage pratique de posséder la bibliothèque de noyaux : lors du passage d'Elasticsearch à Graviton, nous avons intégré des noyaux NEON dédiés. Ni jvector ni FAISS n'ont accordé la même priorité au code natif ARM.</p><p>Mais Elasticsearch ne se limite pas aux nombres à virgule flottante 32 bits. La quantification d'<strong>Int8</strong> réduit la mémoire d'un facteur 4, la quantification bfloat16 d'un facteur 2 et la quantification BBQ d'un facteur 32. Chaque type nécessite sa propre stratégie SIMD, et simdvec fournit des noyaux natifs optimisés manuellement pour chacun d'entre eux.</p><p>Parmi les bibliothèques que nous avons comparées, seule NumKong possède des noyaux comparables pour int8. Nous avons mesuré le produit scalaire int8, la distance euclidienne au carré et le cosinus pour le format int8 sur 1 024 dimensions.</p><p><strong>Score Int8 pour une seule paire (1 024 dimensions, ns/vec op – plus bas est mieux)</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ac255d158e93504/6a1701d5a292997e48d00eb1/a0b852fd5f51d57bd2488886600472ee65ab64da-1594x378.png" alt="Tableau comparant les performances x86 et ARM pour le produit scalaire, la distance euclidienne au carré et les opérations cosinus, en listant les valeurs ES, NumKong et diff pour chaque opération sur les deux architectures." /><p>Sur les deux architectures, NumKong est aussi performant, voire plus rapide, pour les petites et moyennes dimensions, la différence étant principalement due à une surcharge d'appels réduite (appel direct en C contre FFI Java). Pour les grandes dimensions, simdvec rattrape son retard, grâce à une implémentation noyau plus efficace (qui utilise le déroulement en cascade) qui amortit le coût des appels : à mesure que la dimension augmente, <a href="https://github.com/ldematte/simsimd-benchmarks/blob/main/COMPARISON.md#single-pair-i8-nsop-2">cet écart se réduit et finit par s'inverser</a>. Le point de bascule se situe entre 768 et 1 536 dimensions, selon la fonction et l'architecture.</p><p>Malgré la surcharge légèrement supérieure de l'interface FFI Java, simdvec rivalise avec les bibliothèques C/C++ fortement optimisées. Non seulement c'est la seule bibliothèque dotée de noyaux optimisés pour float32 <em>et</em> int8, mais elle est également en tête sur ARM et juste derrière FAISS sur x86 (pour float32), et très proche de NumKong sur les deux architectures (pour int8). Enfin, pour bfloat16, int4, binary et BBQ, bien qu'il existe des alternatives, simdvec se distingue par un SIMD ajusté manuellement et adapté à la structure des données de chaque type.</p><p>Cependant, un moteur de recherche en production n'évalue pas un vecteur à la fois ; il en évalue des milliers par requête. La question suivante est de savoir ce qui se passe à cette échelle.</p><h3>Des milliers à la fois</h3><p>Les performances sur une seule paire ne représentent qu'une partie du problème. En pratique, c'est le comportement des systèmes sous charge qui est important. Une simple requête HNSW peut évaluer des centaines de voisins dans le graphe. Une analyse IVF peut évaluer des milliers d'entrées de listes de publication. Une passe de reclassement peut évaluer des dizaines de milliers de candidats. Le débit sur une seule paire est important, mais ce qui compte davantage, c'est la rapidité avec laquelle il est possible d'évaluer de nombreux vecteurs et la façon dont les performances se dégradent lorsque l'ensemble de travail déborde des caches du processeur.</p><p>Simdvec propose un scoring par lots pour tous les types de données. Il ne s'agit pas simplement de boucles sur des noyaux de distance à une seule paire, mais de boucles internes dotées de plusieurs accumulateurs qui chargent le vecteur de requête une fois par pas de dimension et le partagent entre plusieurs vecteurs de documents, avec un prefetching explicite des lignes de cache pour le lot suivant. Ni jvector ni FAISS n'offrent d'équivalent (à l'heure actuelle). Jvector ne dispose pas d'API Bulk ; les appelants calculent donc le score d'une paire à la fois dans une boucle. FAISS expose <code>fvec_inner_products_ny</code>, qui, à l'heure actuelle, est implémenté comme une boucle sur sa fonction de distance à une seule paire, sans amortissement ni prefetching des requêtes.</p><p><strong>Float32.</strong> Pour mesurer l'impact au niveau du noyau, nous avons évalué une requête unique sur un nombre croissant de vecteurs de documents float32 de 1 024 dimensions, en utilisant des modèles d'accès aléatoire simulant des recherches de voisins dans un graphe dispersé de type HNSW. Les trois tailles d'ensemble de données (32, 625 et 32 500 vecteurs) ont été choisies de manière à ce que l'ensemble de travail dépasse respectivement les caches L1, L2 et L3.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2df64ec77a1921ae/6a1701d714b270393ae3c4de/1d90267be1c63ac82b8ba588617ebedb0be0d1b6-1334x558.png" alt="Deux graphiques à barres comparant les temps de scoring du produit scalaire Float32 en lots pour Elasticsearch simdvec, FAISS et jvector sur AMD Turin (x86, AVX-512) et Graviton 4 (ARM, NEON) sur trois tailles : 32 vecteurs, 625 vecteurs et 32 500 vecteurs." /><p>Lorsque les données tiennent dans le cache, simdvec est le plus rapide sur les deux plateformes, mais l'écart reste modeste, car l'arithmétique du noyau est prépondérante. La différence est flagrante lorsque la taille de l'ensemble de travail dépasse le cache L3. Sur x86, simdvec atteint 95 ns par vecteur, contre 165 ns pour FAISS et 412 ns pour jvector. Sur ARM, le constat est identique : simdvec se maintient à 162 ns, tandis que FAISS grimpe à 347 ns et jvector à 476 ns. Le prefetching et l'amortissement des requêtes dans simdvec masquent la latence mémoire, contrairement à une simple boucle sur des noyaux à paire unique. Cet avantage est encore plus manifeste précisément là où les véritables charges de travail de recherche s'exécutent, au cœur de la mémoire principale.</p><p><strong>Int8.</strong> Le même schéma s'applique aux types quantifiés. Nous avons mesuré le scoring par lots du produit scalaire int8 à 1 024 dimensions avec des tailles d'ensemble de données choisies pour dépasser les mêmes limites de cache L1, L2 et L3, en comparant le scoring par lots de simdvec au scoring de paires individuelles de NumKong dans une boucle.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcda85e10007188d0/6a1701d9cf4f25d722b2cff7/9ee97d98b40d13b19370b76b33e67ea66bfb3250-1580x338.png" alt=" Tableau intitulé &quot;x86 – Bulk scoring, int8 dot product (ns/op, lower is better)&quot; comparant ES simdvec et NumKong sur trois tailles de vecteurs – 128, 2 500 et 130 000 – avec les valeurs ns/op correspondantes et les ratios d'accélération." /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt972c311b7d08ba73/6a1701dadc55dec02ee00c87/601f50a03fa3e6263cbac9700281dfe3e511de60-1580x338.png" alt="Tableau intitulé &quot;ARM – Bulk scoring, int8 dot product (ns/op, lower is better)&quot; comparant ES simdvec et NumKong sur trois tailles de vecteurs – 128, 2 500 et 130 000 – avec les valeurs ns/op correspondantes et les ratios d'accélération" /><p>Sur x86, simdvec est de 1,2 à 1,9 fois plus rapide, grâce à la combinaison du prefetching explicite et du traitement par lots. Sur ARM, simdvec l'emporte également (de 1,7 à 1,9 fois plus rapide) quelle que soit la taille des ensembles de données. Cet avantage provient du traitement par lots de quatre vecteurs simultanément, offrant un parallélisme au niveau mémoire via un modèle d'accès entrelacé. Dans les deux cas, le résultat le plus frappant se situe au niveau des ensembles de données les plus volumineux, là où il est le plus significatif.</p><p>Les résultats concernant la distance au carré et le cosinus montrent un schéma similaire, avec des accélérations de 1,4 à 1,8 fois pour ARM, et de 1,3 à 3,0 fois pour x86 (détails <a href="https://github.com/ldematte/simsimd-benchmarks/blob/main/COMPARISON.md">ici</a>).</p><h3>Quand la mémoire est essentielle</h3><p>Les index vectoriels de production ne tiennent généralement pas dans le cache du processeur. Un index vectoriel de 10 millions d'éléments (int8) à 1 024 dimensions pèse 10 Go. Le scoring des candidats implique le traitement en continu des données depuis la DRAM, et c'est là que l'architecture de scoring par lots fait toute la différence.</p><p>Nous avons utilisé des compteurs de performances matérielles pour mesurer ce qui se passe à l'intérieur du processeur pendant le scoring par lots et avons constaté que masquer la latence de la mémoire nécessite deux stratégies fondamentalement différentes, une par architecture.</p><p><strong>Sur x86, le prefetching explicite élimine les défauts de cache. </strong>Le noyau principal traite les vecteurs séquentiellement, chaque vecteur étant entièrement calculé avant le suivant, tout en émettant des instructions de prefetching pour le lot suivant. Les données futures sont chargées dans le cache L1 avant que le processeur n'en ait besoin.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt368f29b5143d0f5a/6a1701dcc1e8a51780f8817b/a39548f8060c2a4a5154521a4047dd92d8cd77be-1580x309.png" alt="Tableau intitulé « x86 (AMD Turin) — Compteurs matériels par opération int8 » comparant les modes simple et groupé pour les pertes de cache L1, l'IPC et les pertes de dTLB, avec les facteurs d'amélioration correspondants." /><p>Sur ARM, la même approche séquentielle s'est avérée peu performante, même avec prefetching. En revanche, le <strong>noyau de traitement par lots entrelace les charges</strong> de quatre vecteurs à chaque position d'itération, offrant ainsi au moteur hors séquence quatre flux mémoire indépendants. Le processeur ne récupère pas les données plus rapidement, mais le temps d'attente est réduit en ayant toujours une autre opération à traiter pendant que les requêtes mémoire sont en cours de traitement. Une analyse détaillée est disponible dans <a href="https://github.com/elastic/elasticsearch/issues/145412">ce ticket GitHub</a>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8751ac5b20b75508/6a1701deab7f08db83db9d13/832de3bc0d556a493b7bf3f250196018acfc1585-1580x238.png" alt="Tableau intitulé &quot;ARM (Graviton 4) – Hardware counters per int8 operation&quot; comparant les modes unique et groupé pour les défauts de cache L1 et les blocages du backend, avec les notes d'amélioration correspondantes" /><p>Les chiffres racontent deux histoires différentes :</p><ol><li><p>Sur x86, le prefetching réduit les défauts de cache de 139 000 à 19 000 et double le nombre d'instructions par cycle (IPC). L'avantage en termes de traitement par lots s'accroît avec la taille des données, passant de 1,2 fois pour le cache L2 à 2,8 fois pour les caches au-delà du cache L3, car le prefetching masque les allers-retours DRAM de plus en plus coûteux.</p></li><li><p>Sur ARM, le nombre d'échecs de cache reste pratiquement inchangé. Ce qui change, c'est le taux d'utilisation : les blocages du backend diminuent de 40 % car le modèle d'accès entrelacé assure l'alimentation continue du pipeline. Cet avantage reste constant, à 1,8 fois, quelle que soit la taille de l'ensemble de données, car le parallélisme au niveau de la mémoire s'applique que les données proviennent du cache ou de la DRAM.</p></li></ol><p>Deux architectures, deux stratégies, un résultat : à l'échelle de la production, simdvec maintient le pipeline du processeur occupé même lorsque les vecteurs sont dispersés dans la mémoire principale.</p><h2>Ce que cela signifie pour les utilisateurs d'Elasticsearch</h2><p>Ces capacités au niveau du noyau s'additionnent. Une simple requête de recherche vectorielle peut calculer des millions d'opérations de distance : parcours de graphe HNSW, scoring des candidats, reclassement. Sur des milliers de requêtes simultanées, chaque opération, même en nanosecondes, se traduit directement par une latence de requête et un débit de cluster optimaux. Que vous utilisiez float32, int8, bfloat16 ou BBQ, que votre index soit en mémoire ou sur disque, simdvec est le moteur sous-jacent, et chacune de ces opérations s'exécute sur ce même moteur, optimisé à la nanoseconde près.</p><p>L'essentiel à retenir est qu'à l'échelle de la production, les performances de la recherche vectorielle ne sont pas principalement déterminées par le débit SIMD brut. Elles dépendent surtout de la capacité du système à masquer efficacement la latence mémoire tout en maintenant la capacité de calcul sur des millions de petites opérations.</p><p>Les noyaux simdvec s'améliorent à quasiment chaque nouvelle version d'Elasticsearch. Dès l'apparition de nouveaux types de quantification et de plateformes matérielles, des noyaux optimisés sont intégrés. De plus, les types existants continuent de gagner en vitesse grâce à l'amélioration des implémentations déjà disponibles.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-vector-search-simdvec-engine</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-vector-search-simdvec-engine</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[À l'intérieur d'Elastic]]></category>
    <dc:creator><![CDATA[Chris Hegarty,Lorenzo Dematte,Simon Cooper]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta18088409621369b/6a1701dfdc55de7297e00c8b/df9646091bafbbf0a6dfd212ff8a6bd1e8589708-1280x720.png" length="0" type="image/png"/>
    <pubDate>Thu, 23 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Clustering de documents non supervisé avec Elasticsearch + Jina embeddings]]></title>
    <description><![CDATA[Une approche pratique et reproductible pour le clustering non supervisé de documents avec Elasticsearch et les embeddings Jina.]]></description>
    <content:encoded><![CDATA[<p>La recherche vectorielle s’appuie sur une requête, mais qu’en est-il si vous n’en avez aucune ?</p><p>Les organisations amassent d’importantes quantités de documents, tels que des tickets d’assistance, des documents juridiques, des flux de nouvelles et des travaux de recherche ; elles doivent comprendre ce qu’ils contiennent avant d’être en mesure de poser les questions pertinentes. Sans libellés ni données d’entraînement, il est impossible de passer en revue manuellement des milliers de documents. La recherche classique n’est d’aucun secours lorsque vous ne savez pas quoi chercher.</p><p>Cet article explore une approche native d’Elasticsearch pour le partitionnement de documents sans supervision et le suivi d’histoires temporelles permettant de pallier ce problème de découverte. Au terme de cette lecture, vous pourrez suivre l’évolution de récits de ce type au fil des jours :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta8c243e0c5773440/6a17093fa6c2b98c86e7968c/100a60a7fb85da8ab3813fd071a82c93f2c3f318-1300x650.png" alt="Chaînes narratives temporelles s’étalant sur février 2025 — chaque tracé de couleur est une histoire qui perdure dans le temps, tandis que l’épaisseur du lien illustre la force de l’ajustement kNN" /><p><strong>Ce que vous découvrirez :</strong></p><ul><li><p>Pourquoi le recours aux <strong>plongements dédiés au clustering</strong> (plutôt qu’à la recherche d’information) est crucial pour identifier des thématiques en l’absence de requête explicite.</p></li><li><p>Comment la classification centroïde par densité regroupe les documents par sujet en utilisant Elasticsearch k-nearest neighbor (kNN) et par lots <code>msearch</code>.</p></li><li><p>Comment <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significanttext-aggregation"><code>significant_text</code></a> peut étiqueter automatiquement les clusters pour que les thèmes soient lisibles sans avoir à créer de modèle.</p></li><li><p>Comment les chaînes d'histoires temporelles relient le clustering quotidien pour montrer comment les thèmes évoluent de jour en jour.</p></li></ul><p>Le pipeline utilise environ 8 500 articles de février 2025 provenant de BBC News et du Guardian comme corpus de test. Le secteur des informations est idéal car il possède une dynamique temporelle explicite, mais ce schéma s’adapte à tout processus de découverte documentaire : audit juridique, contrôle de conformité, synthèse documentaire, triage du support technique.</p><p><strong>Pile :</strong></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text"><strong>Jina v5</strong></a> <strong>Plongements de regroupement :</strong> des adaptateurs LoRA (Low-Rank Adaptation) spécifiques à la tâche pour le groupement thématique. <a href="https://www.elastic.co/blog/elastic-jina-ai">Jina a rejoint Elastic</a>, et ses modèles sont directement disponibles par le biais du <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service (EIS)</a>.</p></li><li><p><strong>Elasticsearch :</strong> Scalable <a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN</a>, étiquetage <code>significant_text</code> et stockage vectoriel.</p></li><li><p><a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction"><strong>DiskBBQ :</strong></a> format d'index vectoriel sur disque combinant une <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq">meilleure quantification binaire (BBQ)</a> et un partitionnement hiérarchique par k-means pour l'accélération de l'approximation des plus proches voisins (ANN). Ce partitionnement de l'index est interne à la recherche vectorielle et distinct de l'algorithme de clustering par densité utilisé dans ce billet. <code>bbq_disk</code> stocke les vecteurs quantifiés sur disque et ne conserve que les métadonnées de partition en tas, réduisant considérablement les besoins en ressources par rapport à <code>bbq_hnsw</code>, tout en maintenant un rappel élevé.</p></li><li><p><strong>Clustering global + liaison temporelle quotidienne :</strong> découverte et évolution des récits.</p></li></ul><p><strong>Ce dont vous aurez besoin :</strong></p><ul><li><p>Un déploiement Elasticsearch (Elastic Cloud, Elasticsearch Serverless, ou Elastic Self-Managed 8.18+/9.0+) : <code>bbq_disk</code> nécessite une version 8.18 ou plus récente. La partie optionnelle sur le récupérateur de diversité exige la version 9.3+ ou une solution sans serveur.</p></li><li><p>Une <a href="https://jina.ai/embeddings/">clé API Jina</a>: Le niveau gratuit comprend 10 millions de jetons, ce qui couvre le pipeline de clustering de noyau (~4,25 millions de jetons). La comparaison facultative entre la récupération et le clustering utilise un second passage de plongement.</p></li><li><p>Une <a href="https://bonobo.capi.gutools.co.uk/register/developer">clé API Guardian</a> (gratuite).</p></li></ul><h2>Configuration</h2><p>Installez les packs requis :</p>pip install elasticsearch pandas numpy plotly umap-learn python-dotenv pydantic-settings datasets requests<p>Facultatif (uniquement si vous exécutez des assistants de scraping depuis ce dépôt) :</p>pip install beautifulsoup4<p>Ensuite, configurez les clés API dans un fichier <code>.env</code> à la racine du projet :</p>ELASTIC_CLOUD_ID=your-cloud-id        # or ELASTIC_HOST=https://...
ELASTIC_API_KEY=your-api-key
JINA_API_KEY=your-jina-key
GUARDIAN_API_KEY=your-guardian-key<p>Ce bloc-notes appelle <code>load_dotenv(override=True)</code>, donc les valeurs locales <code>.env</code> ont la priorité.</p>Connected to Elasticsearch<h2>Partie 1 : Clustering de découverte — Pourquoi effectuer un clustering de plongements ?</h2><p>La plupart des recherches vectorielles utilisent des <strong>plongements de récupération</strong> entraînés pour associer une <em>requête</em> à des <em>documents</em> pertinents. C'est parfait pour les recherches, mais pas pour les découvertes. Lorsque vous souhaitez trouver les sujets qui existent dans un corpus sans aucune requête, vous avez besoin de plongements qui regroupent des documents similaires.</p><p>Jina v5 résout ce problème grâce à des <strong>adaptateurs LoRA (Low-Rank Adaptation) spécifiques à chaque tâche</strong>. Grâce à LoRA, des ajustements de rang réduit sont injectés dans des couches internes spécifiques tandis que le reste du modèle demeure figé, ce qui réaligne ses capacités sur une tâche donnée sans passer par un cycle de réentraînement complet. Le même modèle de base produit des plongements différents selon le paramètre <code>task</code> :</p><p>Tâche</p><p>Entraîné pour</p><p>Cas d'utilisation</p><p>retrieval.passage</p><p>Correspondance requête-document</p><p>Recherche, génération augmentée par récupération (RAG)</p><p>clustering</p><p>Regroupement de sujets (optimisé pour des clusters serrés)</p><p>Découverte, catégorisation</p><p>L'adaptateur de clustering est formé pour <em>rapprocher</em> les documents sur le même sujet dans l'espace d'intégration et <em>éloigner</em> les documents sur des sujets différents. La comparaison visuelle ci-dessous montre clairement la différence.</p><h3>Recherche et clustering : une comparaison visuelle</h3><p>Pour observer la différence, un échantillon de documents est intégré avec les deux types de tâches. Le clustering est effectué dans l'espace d'intégration original à 1024 dimensions ; l'approximation uniforme et la projection (UMAP) sont utilisées uniquement pour projeter ces intégrations en 2D à des fins de visualisation. L’UMAP conserve la structure de voisinage local, ce qui facilite la comparaison de la segmentation des clusters.</p><p>Ci-dessous, le même échantillon de 480 documents est intégré avec les deux types de tâches et projeté en 2D avec UMAP. Cherchez des groupes de couleurs plus serrés et plus séparés dans le panneau de clustering.</p>    Full dataset: 8,495 articles
    Sources: guardian: 5749, bbc: 2746
    Date range: 2025-02-01 to 2025-02-28


    Sample: 480 docs across 8 sections
    section
    Film              60
    World news        60
    Australia news    60
    Opinion           60
    Football          60
    US news           60
    Sport             60
    Business          60


    Clustering embeddings: 480
    Retrieval embeddings:  480


    UMAP projection complete<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4b3733dccad212b6/6a1709407d8d67aaeb70e6a4/9bcf7a744900560c1c6c63a2dc3af2f9bfd33e11-1100x500.png" alt="Comparaison UMAP des plongements de recherche par rapport aux plongements de clustering" /><p><em>Les plongements de recherche (à gauche) répartissent les sujets de manière large ; les plongements de clustering (à droite) produisent des groupes plus serrés et mieux séparés à partir des mêmes documents.</em></p><p>Les plongements de clustering produisent des groupes plus serrés et plus distincts visuellement. Les plongements de recherche répartissent les sujets de manière plus uniforme, ce qui est idéal pour la recherche (similarité à grain fin) ; mais pour la découverte, ce sont les clusters thématiques serrés qui importent.</p><p>C’est pourquoi <code>task="clustering"</code> est utilisé pour le reste de cette explication.</p><h3>Chargement de l'ensemble de données</h3><p>Le corpus combine deux sources d'actualités pour février 2025 :</p><ul><li><p><strong>BBC News</strong> via l'ensemble de données <a href="https://huggingface.co/datasets/RealTimeData/bbc_news_alltime">RealTimeData/bbc_news_alltime</a> de HuggingFace.</p></li><li><p><strong>The Guardian</strong> via <a href="https://open-platform.theguardian.com/">l’API Open Platform de Guardian</a>.</p></li></ul><p>Le fait d'avoir plusieurs sources permet de vérifier que le clustering permet de trouver des <em>sujets</em> plutôt qu'un style spécifique à la <em>source.</em></p>    Total articles:  8,495
    
    Source breakdown:
    source
    guardian    5749
    bbc         2746
    
    Date range: 2025-02-01 → 2025-02-28
    Days covered: 28
    
    Sample article:
      Source:  guardian
      Title:   Carbon monoxide poisoning ruled out in death of Gene Hackman and wife, police sa
      Section: Film
      Text:    Authorities have ruled out that Gene Hackman and his wife, Betsy Arakawa, died from carbon monoxide poisoning earlier this week in their home in Santa Fe, New Mexico. The Santa Fe county sheriff, Adan...<h3>Intégration avec la tâche de clustering</h3><p>L’API de Jina v5 est sollicitée avec le paramètre <code>task="clustering"</code> pour l’ensemble des documents. Les plongements sont mis en cache sur le disque, de sorte que les exécutions ultérieures ignorent complètement l’API.</p><p>L'appel d'API est simple. Le paramètre <code>task</code> est la différence clé avec l’utilisation typique de l’intégration :</p>payload = {
    "model": "jina-embeddings-v5-text-small",
    "input": texts,
    "task": "clustering",  # ← This selects the clustering LoRA adapter
}<p>Le chronométrage ci-dessous correspond à un accès au cache. La première exécution de l'API prend plus de temps, en fonction de la taille du corpus.</p>    Embeddings ready: 8,495 vectors of dimension 1024
    Time: 0.6s<h3>Indexation dans un seul index Elasticsearch</h3><p>Pour le clustering de découverte, le mois entier est placé dans un seul index (<code>docs-clustering-all</code>). Le partitionnement journalier intervient plus tard, pour la liaison temporelle des articles.</p><p>Le mappage d'index utilise <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq"><code>bbq_disk</code></a> pour le champ vectoriel :</p>{
  "embedding": {
    "type": "dense_vector",
    "dims": 1024,
    "index": true,
    "similarity": "cosine",
    "index_options": {
      "type": "bbq_disk"        // hierarchical k-means partitioning for ANN index lookup; separate from this post's clustering algorithm
    }
  }
}<p>Un vecteur Float32 de 1024 dimensions représente 4 Ko. <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction"><code>bbq_disk</code></a> utilise des k-moyens hiérarchiques pour partitionner les vecteurs en petits clusters, les quantifier en binaire et stocker les vecteurs en pleine précision sur disque pour les re-noter. Seules les métadonnées des partitions sont conservées en mémoire vive, ce qui permet de maintenir des exigences matérielles minimales, même avec des volumes de données importants. Pour les charges de travail qui peuvent se permettre plus de mémoire, <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq"><code>bbq_hnsw</code></a> construit un graphe Hierarchical Navigable Small World (HNSW) pour des recherches plus rapides à un coût de ressources plus élevé.</p><p>Le type de champ <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector"><code>dense_vector</code></a> prend en charge plusieurs stratégies de quantification : <code>bbq_disk</code> et <code>bbq_hnsw</code> sont les meilleurs ajustements pour les plongements de haute dimension comme les vecteurs 1024-dim utilisés ici.</p>    Indexed 8,495 documents into docs-clustering-all
    Time: 57.5s<h3>Clustering : classification de centroïdes par sondage de densité</h3><p>Les algorithmes de partitionnement classiques tels que HDBSCAN partent du principe que l’intégralité de la matrice vectorielle N×d est stockée en mémoire pour exécuter des cycles de mises à jour exhaustifs. Avec 8 495 documents et 1024 dimensions, la charge est supportable (~35 Mo), toutefois la méthode ne peut s’étendre à des millions de documents sans ressources d’infrastructure additionnelles.</p><p>Cet algorithme est conceptuellement proche de l’initialisation KMeans++ avec une affectation de Voronoï et un seuil de bruit, mais il utilise la <a href="https://www.elastic.co/docs/solutions/search/vector/knn">recherche kNN</a> d’Elasticsearch comme primitive de calcul, ce qui permet de réaliser la quasi-totalité du travail côté serveur :</p><ol><li><p><strong>Échantillonner 5 % des documents</strong> comme sondes de densité (échantillon aléatoire, minimum 50).</p></li><li><p><strong>Densité des sondes par lots via</strong> <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-msearch"><strong><code>msearch</code></strong></a> <strong>kNN.</strong> Chaque sonde lance une requête kNN et enregistre la similarité moyenne de ses voisins. Similarité moyenne élevée = zone dense de l'espace d'intégration. <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-msearch"><code>msearch</code></a> envoie plusieurs requêtes de recherche en un seul appel HTTP, ce qui est crucial ici : le sondage de densité génère des centaines de requêtes kNN, et leur regroupement par lots permet d’éviter la surcharge liée à chaque requête individuelle.</p></li><li><p><strong>Sélection de germes à haute densité avec diversification</strong> : les éléments dont la densité est supérieure à la médiane sont classés par ordre décroissant ; ils sont ensuite retenus selon un algorithme glouton, à condition que leur similarité cosinus par rapport aux germes déjà sélectionnés ne dépasse pas un seuil de séparation défini. C’est le seul calcul côté client (~0,01s pour 8k documents).</p></li><li><p><strong>Classez tous les documents par rapport aux centroïdes via</strong> <strong><code>msearch</code></strong> <strong>kNN</strong>: Chaque graine agit comme un centroïd  ; une recherche kNN permet d'extraire les documents proches au-dessus d'un seuil de similarité. Chaque document est affecté au centroïde qui l'a renvoyé avec le score le plus élevé. Les clusters de taille réduite sont dissous pour être classés comme du bruit.</p></li></ol><p>Elasticsearch s'occupe du gros du travail : <code>msearch</code> pour les sondes de densité, <code>msearch</code> pour la classification et <code>significant_text</code> pour l'étiquetage. Pour ce corpus (8 495 documents), l’échantillon de sonde à densité à 5 % lance des requêtes de sonde de 425 kNN, qui <code>msearch</code> se regroupent en neuf appels HTTP (à la taille de lot 50), évitant ainsi une requête par sonde. Combiné à la recherche <code>bbq_disk</code> ANN, cela permet de rendre la phase de clustering rapide et évolutive. Les requêtes kNN utilisent une valeur minimale <a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/approximate-knn-search"><code>num_candidates</code></a> pour la vitesse lors de la passe de clustering ; les requêtes de recherche de production doivent utiliser des valeurs <code>num_candidates</code> plus élevées pour améliorer le rappel au prix de la latence.</p><p>Les tailles naturelles des clusters sont déterminées par la densité d'espace intégrée autour de chaque centroïde, et non par une limite <code>k</code> stricte. Les thématiques denses produisent des clusters plus importants ; les thématiques de niche produisent des clusters plus petits.</p><h4>Pourquoi pas KMeans ou HDBSCAN ?</h4><p>KMeans suppose des amas sphériques et nécessite la matrice complète N× en mémoire. Pour les corpus qui tiennent dans la mémoire, <a href="https://scikit-learn.org/stable/modules/generated/sklearn.cluster.HDBSCAN.html">HDBSCAN</a> est une alternative solide. Il gère des formes de clusters arbitraires et possède une sémantique de densité bien comprise.</p><p>L’approche par centroïdes par sondage de densité cible un créneau différent : les corpus pour lesquels vous souhaitez un système unique de stockage, de récupération et de clustering, ou lorsque l’échelle rend les opérations de matrice côté client peu pratiques. Cette approche emploie le kNN d’Elasticsearch en tant que primitive de calcul, prend en charge n’importe quelle taille de cluster et conserve presque tout le traitement côté serveur.</p>    Clustered global index in 31.6s
      Total clusters: 82
      Total noise:    2420 (28.5%)
      Density probes: 425 kNN queries via 9 _msearch HTTP calls<h4>Comprendre le taux de bruit</h4><p>Le taux de bruit de ~28% est le résultat d'une conception et non d'un mode de défaillance. Les documents qui ne correspondent à aucun cluster dense au <code>similarity_threshold</code> configuré ne sont pas attribués, plutôt que d’être forcés dans une mauvaise correspondance. Cela agit comme un filtre de qualité : les tribunes d’opinion, les articles courts et les articles ponctuels résistent naturellement au clustering, car ils n’ont pas la densité thématique qui définit un groupe cohérent.</p><p>Ce seuil peut être modulé : réduire <code>similarity_threshold</code> génère un clustering plus large (plus de documents intégrés, au prix d’une homogénéité moindre), tandis que son augmentation densifie les clusters et rejette davantage d’éléments comme bruit. Pour ce corpus composé de contenus d’actualité variés, un taux de bruit d’environ 30 % constitue un point de fonctionnement raisonnable. Pour les déploiements en production, il convient de régler le seuil selon les exigences de qualité spécifiques au secteur d’activité.</p><h3>Étiquettes automatiques avec significant_text</h3><p>Désormais, chaque cluster a besoin d'une étiquette facile à lire. L’agrégation <code>significant_text</code> d’Elasticsearch permet de trouver les termes dont la fréquence est anormalement élevée dans un groupe spécifique (le cluster) comparativement à l’ensemble du corpus.</p><p>Techniquement, il emploie une heuristique statistique (le score JLH par défaut) qui pondère les écarts de fréquence absolue et relative, le tout sans « machine learning » ni sollicitation de modèles de langage (LLM). Un groupe sur la politique britannique peut faire apparaître des termes tels que <code>starmer</code>, <code>labour</code>, <code>downing</code> parce que ces termes sont disproportionnellement fréquents dans ce groupe par rapport à l'ensemble du corpus d'actualités.</p><p>Pour ce traitement global, le calcul des étiquettes s’effectue directement face à <code>docs-clustering-all</code> ; ainsi, les données de premier plan et de référence sont extraites de la totalité du mois. Dans la partie 2, l’étiquetage utilise le modèle d’index quotidien (<code>docs-clustering-*</code>), un caractère générique (« wildcard ») qui permet aux requêtes de couvrir simultanément tous les index correspondants, afin d’offrir à <code>significant_text</code> un arrière-plan plus large pour un meilleur contraste.</p><p>Voici à quoi ressemble une requête minimale :</p>{
  "size": 0,
  "query": { "term": { "cluster_id": "72" } },
  "aggs": {
    "label_terms": {
      "significant_text": {
        "field": "text",
        "size": 5,
        "filter_duplicate_text": true
      }
    }
  }
}<p><code>significant_text</code> sert également de barrière de qualité : les clusters qui ne produisent aucun terme important n'ont pas de vocabulaire distinctif. Il s’agit de regroupements dépourvus de sens qui devraient être réintégrés au bruit de fond au lieu d’être identifiés par un label erroné.</p><p>Une étape de nettoyage déterministe légère supprime les termes d’étiquetage parasites (jetons numériques, mots génériques) et se rabat sur un titre représentatif en cas de besoin. Cela permet de conserver les labels Elasticsearch natifs tout en améliorant la lisibilité.</p>    Sample cluster labels:
      cluster   3  (200 docs)  arsenal | mikel | villa
      cluster   1  (198 docs)  volodymyr | ukrainian | kyiv
      cluster   0  (196 docs)  hostages | hamas | israeli
      cluster   4  (187 docs)  scrum | rugby | borthwick
      cluster  52  (185 docs)  fossil | renewable | renewables
      cluster  10  (156 docs)  labour | gwynne | mps
      cluster  40  (151 docs)  novel | novels | literary
      cluster  11  (149 docs)  mewis | sarina | wiegman
      cluster  44  (143 docs)  flooding | rainfall | rain
      cluster  13  (131 docs)  doge | musk | elon
      cluster  12  (128 docs)  murder | insp | knockholt
      cluster   5  (124 docs)  putin | backstop | starmer


    Reassigned 35 docs from incoherent clusters to noise
    Total docs: 8,495
    Clustered:  6,040 (71.1%)
    Noise:      2,455 (28.9%)<h3>Visualisation des clusters</h3><p>Les visualisations ci-dessous montrent ce que la passe de clustering global a découvert : une répartition par date des documents regroupés par rapport au bruit, une projection UMAP du mois complet et un graphique de mixité des sources confirmant que les clusters reflètent les sujets plutôt que les sources.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4ed087d8b6dac2a0/6a17094260084b44543c4501/99099f5adaa945ae4097c50b0d7151c7dd28872e-1000x400.png" alt="Distribution quotidienne de documents en cluster vs documents de bruit" /><p>Distribution quotidienne des documents regroupés par rapport aux documents parasites tout au long de février 2025.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5ca320bc91131ab/6a17094366c4f95828f8bfbf/477c6c7177942955a942f85f5c881da50e517915-1100x700.png" alt="Projection UMAP pour le mois complet avec tous les documents" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f554a6bc2bc367b/6a170945a929cfa7a1ae0947/4f4302556c8974c416842452cf33bca06e90b966-1100x700.png" alt="Projection UMAP montrant uniquement les documents regroupés" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8e40c26f89ce5523/6a17094747d49c147d2d8974/327f96a79e382ef30614cb0570aa7fccd822b8f8-1100x700.png" alt="[Projection UMAP mettant en évidence un seul cluster" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf1dd2c19ae628f1d/6a1709481949f7a630e7a9a3/acfb1524a10e24d6ff2412e7c3ec0f2b3ac75193-900x600.png" alt="Mélange de sources par cluster montrant le regroupement thématique" /><p>Chaque îlot coloré dans l’UMAP représente un cluster : un groupe d’articles sur le même sujet découvert uniquement par similarité d’intégration. Les points gris de bruit correspondent aux articles qui ne s’intègrent pas clairement dans un cluster (souvent des articles courts, des tribunes libres ou des récits isolés).</p><p>Le graphique de répartition des sources confirme que les clusters contiennent des articles de <strong>BBC News et The Guardian</strong>. Le clustering est de trouver des <em>sujets</em> et non <em>des sources</em>, ce qui correspond exactement à ce que la découverte non supervisée devrait produire.</p><h3>Exploration de l’étendue des clusters avec le récupérateur de diversification</h3><p>Le kNN simple renvoie les documents les plus similaires au centroïde d'un cluster (le noyau dense). Mais les vrais clusters couvrent des sous-thèmes. Le <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/diversify-retriever"><strong>récupérateur de diversité</strong></a> utilise la pertinence marginale maximale (MMR) pour faire remonter des documents qui sont à la fois pertinents pour le centroïde et <em>différents les uns des autres</em>.</p><p>Le paramètre clé est <strong>λ (lambda)</strong> :</p><ul><li><p>λ = 1,0 → pertinence pure (identique à la kNN simple).</p></li><li><p>λ = 0,0 → diversité pure (résultats à répartition maximale).</p></li><li><p>λ = 0,5 → équilibré : cela est pertinent pour le sujet, tout en couvrant différents aspects.</p></li></ul><p>La structure minimale d’une requête de récupérateur se présente comme suit :</p>{
  "size": 8,
  "retriever": {
    "diversify": {
      "type": "mmr",
      "field": "embedding",
      "lambda": 0.5,
      "query_vector": "&lt;cluster-centroid-vector&gt;",
      "retriever": {
        "knn": {
          "field": "embedding",
          "query_vector": "&lt;cluster-centroid-vector&gt;",
          "k": 50,
          "num_candidates": 100
        }
      }
    }
  }
}<p>Les paramètres <code>type</code>, <code>field</code>, <code>query_vector</code> sont requis au niveau de diversification : <code>field</code> indique à MMR quel champ dense_vector utiliser pour la similarité entre les résultats, et <code>query_vector</code> fournit le point de référence pour le score de pertinence.</p><p>Cela vous permet de répondre à la question : « Que couvre réellement ce groupe ? » au lieu de simplement « Quel est son centre ? »</p>    Exploring cluster 52 (185 docs)
    Label: fossil | renewable | renewables
    Centroid computed (dim=1024)


    ========================================================================
    Plain kNN (closest to centroid)
    ========================================================================
      1. [0.9738] Green campaigners fear ministers are poised to award billions of pounds in fresh subsidies to Drax power station, despite strong concerns...
      2. [0.9710] Thirteen more oil and gas licences could be cancelled as ministers decide new guidance for fossil fuel extraction after a landmark court...
      3. [0.9699] Experts have accused the fossil fuel industry of seeking special treatment after lobbyists argued greenhouse gas emissions from oilfields...
      4. [0.9681] Burning wood is a terrible way of producing electricity . Chopping down trees destroys habitats for wildlife, and growing new trees cannot...
      5. [0.9649] Keir Starmer will do huge damage to the global fight against climate change if he gives in to political pressure and allows the development...
      6. [0.9641] Labour will next week be confronted with stark policy choices that threaten to expose the fault lines between the Treasury and the...
      7. [0.9638] The Drax power station near Selby in north Yorkshire burns imported wood pellets  The government has agreed a new funding arrangement with...
      8. [0.9581] If you care about the world we are handing on to future generations, the news on Thursday morning was dramatic. This January was the...
    
    ========================================================================
    Diversify retriever (MMR, lambda=0.5)
    ========================================================================
      1. [0.9738] Green campaigners fear ministers are poised to award billions of pounds in fresh subsidies to Drax power station, despite strong concerns...
      2. [0.9434] Oil and gas interests have waged a coordinated campaign to kill pro-electrification policies that ban gas connections in new buildings ,...
      3. [0.9303] It was interesting to read that new licences for oil and gas production in the North Sea are being delayed by legal action ( Thirteen more...
      4. [0.9139] The US energy secretary, Chris Wright, has said he “would love to see Australia get in the game of supplying uranium and maybe going down...
      5. [0.9077] Rachel Reeves was facing criticism on Saturday night as it was confirmed that a report she cited as evidence that a third ­runway at...
      6. [0.8996] When Margaret Thatcher opened the Hadley Centre for Climate Change in 1990 journalists suggested she was attempting to appear to be doing...
      7. [0.8993] The vast majority of governments are likely to miss a looming deadline to file vital plans that will determine whether or not the world has...
      8. [0.8987] European imports of seaborne gas shipments fell by a fifth last year to their lowest level since the pandemic, according to a new report,...
    
    Overlap: 1/8 documents appear in both result sets
    
    Avg pairwise similarity (lower = more diverse):
      Plain kNN:          0.9057
      Diversify retriever: 0.6965<p>Les résultats simples kNN se regroupent autour d'un angle du sujet : les documents les plus similaires au centroïde et les uns aux autres. Le récupérateur de diversité fait ressortir différentes facettes d’un même cluster : des sous-thématiques, des sources distinctes et des perspectives variées.</p><p>La métrique de diversité valide ce point quantitativement : la similarité moyenne entre paires est plus basse avec le récupérateur de diversité, prouvant que les documents obtenus traitent d’un éventail de sujets plus vaste.</p><p>C'est utile pour :</p><ul><li><p><strong>Comprendre ce que recouvre réellement un groupe</strong>, pas seulement son centre mais aussi ses bords.</p></li><li><p><strong>Générer des résumés</strong>. Des documents représentatifs et variés offrent un meilleur matériel pour un LLM.</p></li><li><p><strong>Trouver des exemples représentatifs</strong> pour l'évaluation humaine ou l'étiquetage en aval.</p></li><li><p><strong>Contrôles qualité</strong>. Si les divers résultats semblent incohérents, il se peut que le groupe doive être scindé.</p></li></ul><h2>Partie 2 : chaînes d’histoires temporelles</h2><h3>Suivi des histoires au fil des jours</h3><p>La partie 1 a appliqué le clustering à l’ensemble du mois à l’échelle globale pour la découverte de thématiques. En ce qui concerne le flux temporel, le même processus de classification par centroïde avec sondage de densité est appliqué de manière autonome sur les <strong>index journaliers</strong>, les clusters étant ensuite mis en relation d’un jour à l’autre. Il est important de noter que les clusters journaliers sont distincts des clusters globaux présentés en partie 1 ; chaque jour produit ses propres attributions de groupes et des labels optimisés pour le contenu du jour même.</p><h4><strong>L'approche de liaison : échantillonnage et requête</strong></h4><p>Pour chaque cluster le jour A :</p><ol><li><p>Prenez quelques exemples de documents représentatifs.</p></li><li><p>Exécutez la recherche kNN sur l'index du jour B.</p></li><li><p>Comptez le nombre de visites par groupe B par jour.</p></li><li><p>Si la fraction de réussite dépasse un seuil (fraction kNN ≥ 0,4), enregistrez un lien.</p></li></ol><p>Cela est rapide (seuls quelques documents par cluster sont interrogés, pas tous) et utilise le kNN natif d'Elasticsearch, aucun outil externe n'est nécessaire.</p>Preparing daily indices for temporal linkage...


Indexed 8,495 docs into 28 daily indices


Temporal links found: 808 in 145.4s

Strongest links:
  2025.02.01 'league | arsenal | premier' -&gt; 2025.02.02 'league | season | striker'  (100%)
  2025.02.03 'league | striker | loan' -&gt; 2025.02.04 'league | striker | season'  (100%)
  2025.02.03 'score | operator | gedling' -&gt; 2025.02.04 'league | striker | season'  (100%)
  2025.02.12 'playoff | leg | bayern' -&gt; 2025.02.13 'league | players | injury'  (100%)
  2025.02.14 'league | injury | football' -&gt; 2025.02.15 'league | premier | football'  (100%)
  2025.02.18 'russia | ukraine | talks' -&gt; 2025.02.19 'saudi | russia | arabia'  (100%)
  2025.02.18 'football | league | bayern' -&gt; 2025.02.19 'league | manchester | players'  (100%)
  2025.02.21 'league | premier | manchester' -&gt; 2025.02.22 'game | players | defeat'  (100%)
  2025.02.21 'rugby | calcutta | brilliant' -&gt; 2025.02.22 'game | players | defeat'  (100%)
  2025.02.26 'metals | kyiv | ukrainian' -&gt; 2025.02.27 'ukraine | russia | talks'  (100%)<p>Une fraction kNN de 100 % signifie que chaque document échantillonné du cluster source s'est retrouvé dans le même cluster cible, ce qui constitue le lien inter-jours le plus fort possible. La plupart des liens ci-dessus concernent le football, ce qui est logique : la couverture de la Premier League est quotidienne et présente une grande cohérence thématique.</p><p>Le lien <code>score | operator | gedling</code> → <code>league | striker | season</code> est un exemple de cluster de football local de niche (Gedling étant un club amateur) absorbé par le cluster plus large de la Premier League le jour suivant, un effet naturel du reclustering quotidien à différents niveaux de granularité.</p><h3>Création de chaînes d’histoires</h3><p>Une chaîne d'histoires est une séquence de clusters liés sur des jours consécutifs.</p><p>Les liens individuels par paires vous indiquent que le cluster « politique britannique » du lundi est relié à celui du mardi. Les chaînes révèlent l’arc complet : une histoire qui commence le lundi, évolue tout au long de la semaine et s’estompe le vendredi.</p><p>Les chaînes sont élaborées selon un algorithme glouton à partir de liens présentant une fraction kNN ≥ 0,4 ; ainsi, au moins 40 % des documents du groupe d’origine se retrouvent dans un groupe de destination unique. En partant du cluster le plus ancien, l'algorithme suit toujours le lien sortant le plus fort.
</p>    Strong links (kNN fraction &gt;= 0.4): 244
    Story chains spanning 3+ days: 18
      Chain 1: 'ukrainian | kyiv | eastern' (19 days: Feb 3 → Feb 21)
      Chain 2: 'playing | opposition' (19 days: Feb 10 → Feb 28)
      Chain 3: 'tadhg | maro | cadan' (10 days: Feb 1 → Feb 10)
      Chain 4: 'invade | china | putin' (8 days: Feb 21 → Feb 28)
      Chain 5: 'elected | labour | leader' (7 days: Feb 12 → Feb 18)
      Chain 6: 'film | swift | awards' (6 days: Feb 2 → Feb 7)
      Chain 7: 'amendment | termination | reporting' (6 days: Feb 12 → Feb 17)
      Chain 8: 'officers | scene | police' (5 days: Feb 1 → Feb 5)<p>La chaîne la plus longue suit la couverture Ukraine-Russie pendant 19 jours consécutifs, ce qui n'est pas surprenant étant donné l'intensité géopolitique soutenue en février 2025. La deuxième plus longue suit le football de Premier League pendant 19 jours du mois. Des chaînes plus courtes couvrent la saison des récompenses (cinéma/prix, six jours), le rugby du Tournoi des Six Nations (10 jours) et la couverture du leadership politique au Royaume-Uni (sept jours). Chaque chaîne représente un arc narratif que l'algorithme a découvert uniquement à partir de la similarité des indices quotidiens.</p><h3>Sankey : Visualisation du flux narratif</h3><p>Un diagramme de Sankey est une visualisation de flux où la largeur des liens représente la force de la connexion. Ici, chaque bande verticale correspond à un jour, chaque nœud à un cluster quotidien (dimensionné selon le nombre de documents), et chaque chemin coloré suit une chaîne narrative au fil du temps. La largeur des liens encode la force du chevauchement kNN : des liens plus épais signifient qu'un plus grand nombre de documents échantillonnés se sont retrouvés dans le cluster cible. Les couleurs sont cohérentes pour chaque chaîne, de sorte qu'un chemin d'une même couleur de gauche à droite se lit comme la progression d'une histoire.</p><p>Par exemple, la chaîne Ukraine–Russie (visible comme l’un des parcours les plus longs) s’étend de façon continue du début du mois de février jusqu’à la troisième semaine, avec des liens constamment épais indiquant une forte continuité thématique d’un jour à l’autre.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta8c243e0c5773440/6a17093fa6c2b98c86e7968c/100a60a7fb85da8ab3813fd071a82c93f2c3f318-1300x650.png" alt="Des chaînes narratives temporelles se déroulant en février 2025" /><p><em>Des chaînes narratives temporelles se déroulant en février 2025. Chaque chemin coloré est une histoire qui persiste au fil des jours ; la largeur des liens indique la force de chevauchement en kNN.</em></p><h2>Ce que cette approche apporte</h2><p>Cette présentation couvre un pipeline complet de clustering de documents non supervisé construit sur Elasticsearch :</p><ol><li><p><strong>Clustering de plongements</strong> : les adaptateurs spécifiques aux tâches de Jina v5 produisent des plongements optimisés pour le regroupement thématique, et non seulement pour la correspondance requête-document.</p></li><li><p><strong>Regroupement global par clusters</strong> : en traitant le mois entier au sein d’un index unique, on optimise la détection de thèmes transversaux sur l’ensemble de la période.</p></li><li><p><strong>Classification des centroïdes sondés par densité</strong>: échantillon 5 %, densité de la sonde via <code>msearch</code> kNN, sélection de diverses graines à haute densité, classification de toutes les substances par rapport aux centroïdes. Elasticsearch gère la puissance de calcul lourde ; seule la sélection des graines s’exécute côté client (~0,01 s).</p></li><li><p><a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significanttext-aggregation"><strong><code>significant_text</code></strong></a> <strong>Étiquetage par pertinence</strong> : les tests de signification produisent des étiquettes de clusters pertinentes sans aucun modèle de « machine learning » ni annotation manuelle. Les clusters qui ne produisent aucun terme significatif sont incohérents et sont relégués au rang de bruit : un portail de qualité intégré.</p></li><li><p><strong>Liaison temporelle d'histoires</strong> : Les indices quotidiens et l'index croisé kNN par échantillonnage et interrogation retracent l'évolution des articles dans le temps.</p></li></ol><p><strong>Points clés :</strong></p><ul><li><p>Le type de tâche de plongement est important : les plongements de clustering produisent des groupes thématiques sensiblement plus resserrés.</p></li><li><p>Elasticsearch peut servir à la fois de couche de stockage <em>et</em> de moteur de clustering via <a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN search</a>.</p></li><li><p>La classification par centroïde basée sur la densité conserve presque tous les calculs côté serveur et produit des clusters de tailles naturelles déterminées par la densité de l'espace de plongement.</p></li><li><p><code>significant_text</code> est rapide, interprétable et efficace pour le marquage automatique et le contrôle de la qualité.</p></li></ul><p><strong>Situations dans lesquelles cette approche est utile :</strong></p><ul><li><p>Vous disposez de texte horodaté et souhaitez effectuer une découverte de thématiques sans données d'entraînement étiquetées.</p></li><li><p>Vous voulez une seule pile pour le stockage, la recherche vectorielle, l’étiquetage et la liaison temporelle.</p></li></ul><p><strong>Extensions à explorer :</strong></p><ul><li><p>Clustering multi-période (cumuls hebdomadaires et mensuels).</p></li><li><p>Ingestion en temps réel avec attribution incrémentale de clusters.</p></li><li><p>Résumés de cluster générés par LLM en utilisant les termes significant_text comme germes.</p></li><li><p>À plus grande échelle, des centroïdes KMeans échantillonnés peuvent servir de points de départ pour le clustering basé sur la densité, réduisant ainsi le coût de la phase de sondage.</p></li></ul><h2>Essayez par vous-même</h2><p>Ajoutez votre propre corpus de documents horodatés ; toute collection de texte contenant des dates fonctionne avec ce pipeline. Le cahier complet et le code de support sont disponibles dans le <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/unsupervised-document-clustering-elasticsearch-jina-embeddings">référentiel associé</a>.</p><ul><li><p><a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs"><strong>Démarrez un essai gratuit d’Elastic Cloud</strong></a>: Lancez un cluster géré avec <code>bbq_disk</code> support en quelques minutes.</p></li><li><p><a href="https://www.elastic.co/elasticsearch/serverless"><strong>Essayez Elasticsearch Serverless</strong></a> : aucune gestion de cluster, une mise à l’échelle automatique et une prise en charge de l’intégralité de ce guide.</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/unsupervised-document-clustering-elasticsearch-jina-embeddings</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/unsupervised-document-clustering-elasticsearch-jina-embeddings</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[Recherche ML]]></category>
    <category><![CDATA[Jina AI]]></category>
    <dc:creator><![CDATA[Matthew Adams]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4bd7dd10a7cd6dc8/6a17094a14b270581de3c5b6/662c00694c3e0c2fb2128098bdb6813df9e86a72-1280x720.png" length="0" type="image/png"/>
    <pubDate>Fri, 10 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Quand les TSDS rencontrent l'ILM : Concevoir des flux de données temporelles qui ne rejettent pas les données en retard]]></title>
    <description><![CDATA[Comment les limites temporelles des TSDS interagissent avec les phases de l'ILM ; et comment concevoir des politiques qui tolèrent les métriques arrivant en retard.]]></description>
    <content:encoded><![CDATA[<p>Récemment, j'ai migré le cluster de métriques d'un client d'une architecture "tout en accès direct" vers une architecture en mode "chaud/froid/gelé". J'avais pourtant effectué cette migration des dizaines de fois auparavant. Quelques minutes plus tard, Logstash a complètement cessé de transmettre les données.</p><p>Elasticsearch rejetait les métriques arrivant en retard. Ces rejets ont provoqué un retard dans le pipeline, entraînant davantage de données en retard, ce qui a déclenché encore plus de rejets. Finalement, le pipeline s'est complètement arrêté.</p><p>Nous avons dû restaurer les données à partir d'un snapshot, les réindexer et repenser le pipeline d'ingestion pour pouvoir les récupérer.</p><p>La cause principale n'était pas la gestion du cycle de vie des index (ILM) elle-même. C'étaient les flux de données temporelles (TSDS) et la manière dont ils imposent des index sous-jacents limités dans le temps.</p><p>Les TSDS peuvent réduire les besoins de stockage des métriques de 40 à 70 %, mais les modifications architecturales qui optimisent leur fonctionnement influent également sur l'évolution des index. Ces changements sont importants lors de la conception de politiques ILM ou lorsque vos pipelines d'ingestion sont susceptibles de générer des données arrivant en retard.</p><h2>RÉSUMÉ</h2><p>Lors de l'utilisation de TSDS :</p><ul><li><p>Les index sous-jacents acceptent uniquement les documents compris dans une fenêtre temporelle spécifique.</p></li><li><p>Si des données en retard arrivent après qu'un index passe en mode froid ou gelé, Elasticsearch rejette ces documents ou les achemine vers le stockage des échecs, si configuré.</p></li></ul><p>Règle de conception :</p>warm_min_age &gt; rollover_max_age + maximum_expected_lateness<h2>Qu'est-ce qu'un flux de données temporelles ?</h2><p>Un<em> flux de données temporelles</em> (TSDS) est un flux de données spécialisé optimisé pour les données de métriques. Les données sont acheminées de manière à ce que les documents associés soient situés dans les mêmes partitions, optimisant ainsi la requête et la récupération. Voici comment Elasticsearch procède :</p><p>Chaque document contient :</p><ul><li><p>Un horodatage.</p></li><li><p>Des champs dimensionnels qui identifient les séries temporelles.</p></li><li><p>Des champs de métriques qui représentent les valeurs mesurées.</p></li></ul><p>En voici quelques exemples :</p><ul><li><p>Utilisation du processeur par hôte.</p></li><li><p>Latence des requêtes par service.</p></li><li><p>Relevés de température par capteur.</p></li></ul><p>Les <em>dimensions </em>identifient ce que nous voulons mesurer, tandis que les <em>métriques </em>représentent des valeurs qui évoluent au fil du temps.</p><h3>Dimensions</h3><p>Les dimensions décrivent l'entité mesurée.</p><p>Exemples :</p>host.name
service.name
container.id<p>Nous les définissons dans les mappings avec :</p>time_series_dimension: true<h3>Des métriques</h3><p>Les métriques représentent des valeurs numériques et sont définies comme suit :</p>time_series_metric<p>Types de métriques courants :</p><ul><li><p>Jauge : valeurs qui montent et descendent.</p></li><li><p>Compteur : valeurs qui augmentent jusqu'à la réinitialisation.</p></li></ul><p>Elastic Agent collecte principalement des métriques et des données de logs, donc même si vous n'avez pas activé d'index TSDS manuellement, il est possible que vous en ayez toujours dans votre cluster.</p><h3>Le champ _tsid</h3><p>Elasticsearch génère en interne une valeur <code>_tsid</code> à partir des champs de dimensions. Cela permet d'acheminer les documents de dimensions identiques vers la même partition, ce qui améliore :</p><ul><li><p>Compression.</p></li><li><p>Localité de la requête.</p></li><li><p>Performance d'agrégation.</p></li></ul><h2>La principale différence : les index sous-jacents à durée déterminée</h2><p>Les flux de données traditionnels écrivent toujours dans l'index sous-jacent le plus récent, appelé <em>index d'écriture</em>, mais TSDS se comporte différemment.</p><p>Chaque index TSDS sous-jacent comporte une fenêtre temporelle définie et n'accepte que les documents dont les valeurs <code>@timestamp</code> se situent dans cette fenêtre :</p>GET _data_stream/my-metrics-data-stream


     "index_mode": "time_series",
     "time_series": {
       "temporal_ranges": [
         {
           "start": "2026-01-15T14:35:50.000Z",
           "end": "2026-03-16T11:34:40.000Z"
         }
       ]
     }<p>Lorsqu’un document est indexé, Elasticsearch l’achemine vers l’index sous-jacent responsable de cet horodatage, ce qui signifie que, contrairement aux index traditionnels, un TSDS peut écrire simultanément dans plusieurs index sous-jacents.</p><p>Par exemple :</p><ul><li><p>Données en temps réel → dernier index.</p></li><li><p>Données tardives → index plus ancien couvrant cette plage horaire.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b001853af30d5f8/6a17dc2cfaa9137a7d93c751/31af2bb3b3dc24db8342e791e1db77a44659ba7a-1589x502.png" alt="Chronologie montrant comment un document en retard est acheminé vers un index plus ancien, tandis qu'un document actuel est dirigé vers l'index le plus récent." /><h2>Conception pour les données arrivant en retard</h2><p>Dans la réalité, les pipelines d'ingestion fournissent rarement les métriques dans les délais impartis. Les métriques peuvent être retardées par des pannes de réseau, des accumulations de données en cours de route, l'ingestion par lots et la perte d'appareils en périphérie, qui se reconnectent ensuite et commencent à rattraper leur retard.</p><p>Les index traditionnels absorbent discrètement ces retards, ce qui n'est pas le cas des TSDS.</p><p>Si l'horodatage d'un document se situe en dehors de la plage des index de support inscriptibles, Elasticsearch le rejette, ce qui signifie que votre politique ILM doit tenir compte des données en retard.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6e8eae1b2ddad142/6a17dc2e1d1b8335a793e35c/32a103b95b20e31615c214271e27811a7ee315ae-1999x691.png" alt="Chronologie du cycle de vie des index" /><h2>La contrainte critique</h2><p>Les index sous-jacents doivent rester accessibles en écriture suffisamment longtemps pour accepter les données en retard.</p><p>Concrètement :</p>time_until_readonly &gt; maximum_expected_lateness<p>Étant donné que l'ILM mesure l'âge à partir de la substitution, la règle opérationnelle est la suivante :</p>warm_or_cold_min_age &gt; rollover_max_age + maximum_expected_lateness<p></p><p>Par exemple, si les métriques peuvent arriver jusqu'à six heures en retard, les index doivent rester inscriptibles au moins six heures après la substitution.</p><p></p><p>L'absence de prise en compte de cette contrainte est exactement ce qui a causé l'échec d'ingestion décrit précédemment. Les données arrivées en retard ont été dirigées vers un index antérieur, qui se trouvait déjà dans le niveau froid et était donc bloqué en écriture.</p><p></p><h2>Gestion des documents rejetés</h2><p>Lorsqu'un TSDS rejette un document, Elasticsearch renvoie une erreur, indiquant que l'horodatage ne se situe pas dans la plage des index inscriptibles. La façon dont votre pipeline d'ingestion gère cette erreur détermine si vous perdez des données ou si l'ingestion est bloquée.</p><p>Le principal mécanisme pour la gestion des documents rejetés est le magasin des échecs.</p><h3>Stockage des échecs (recommandé dans Elasticsearch 9.1+)</h3><p>Elasticsearch 9.1 a introduit le magasin des échecs, qui capture automatiquement les documents rejetés. Au lieu de renvoyer des erreurs aux clients, Elasticsearch écrit les échecs de documents dans un index d'échec dédié au sein du flux de données.</p><p>Vous pouvez examiner les échecs à l'aide de :</p>GET metrics-myapp::failures/_search<p>L'utilisation du stockage des échecs empêche les pipelines d'ingestion d'être bloqués par des erreurs de rejet, tout en préservant les données rejetées à des fins d'analyse ou de <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/reindex-tsds">réindexation</a>.</p><h2>Surveillance des problèmes de rejet</h2><p>Les problèmes de retard d'arrivée apparaissent généralement d'abord sous forme d'anomalies d'ingestion. Vous les remarquerez peut-être d'abord comme :</p><ul><li><p>Des chutes soudaines du taux d'indexation.</p></li><li><p>Une forte augmentation du nombre de documents rejetés.</p></li><li><p>Un nombre croissant d'entrées de stockage des échecs.</p></li><li><p>Incohérences entre les nombres d'entrées et de sorties du pipeline.</p></li></ul><p>Les alertes en fonction de ces signaux permet aux opérateurs de détecter les problèmes avant que les pipelines ne s'arrêtent. Les workflows, les tâches de machine learning et d'autres mécanismes peuvent être utilisés pour automatiser la détection et les notifications.</p><h2>Checklist de migration pour les TSDS et l'ILM</h2><p>Si vous migrez un cluster de métriques vers TSDS, introduisez une hiérarchisation ILM ou effectuez une mise à niveau vers une version d'Elasticsearch où les métriques sont TSDS par défaut, consultez d'abord ces éléments.</p><h3><strong>1. Mesurer la latence d'ingestion</strong></h3><p>Avant de modifier les politiques ILM, déterminez :</p><ul><li><p>Le délai d'ingestion normal.</p></li><li><p>Le délai maximal en cas d'incident.</p></li><li><p>Les retards dus aux pipelines de traitement par lots.</p></li></ul><p>Votre conception ILM doit prendre en compte le délai réaliste maximal.</p><h3><strong>2. Vérifier les fenêtres temporelles d'index</strong></h3><p>Vérifiez vos index sous-jacents TSDS :</p>GET _data_stream/&lt;your-stream&gt;<p>Recherchez :</p><ul><li><p><code>time_series.start_time</code></p></li><li><p><code>time_series.end_time</code></p></li></ul><p>Ces limites déterminent quels index peuvent accepter des documents. Comprendre ces délais permet de déterminer la date limite de rejet des données.</p><h3><strong>3. Dimensionner le niveau hot pour les arrivées tardives</strong></h3><p>Assurez-vous que les index sous-jacents restent modifiables suffisamment longtemps pour les données en retard.</p><p>Règle opérationnelle :</p><ul><li><p><code>warm_min_age &gt; rollover_max_age + maximum_expected_lateness</code></p></li></ul><p>N'oubliez pas que les index doivent rester accessibles en écriture pendant au moins six heures si les métriques peuvent arriver avec six heures de retard.</p><h3><strong>4. Décider comment traiter les documents rejetés</strong></h3><p>Choisissez une stratégie avant d'activer TSDS :</p><ul><li><p>Magasin des échecs (recommandé dans Elasticsearch 9.1+).</p></li><li><p>File d'attente des messages Logstash non distribuables.</p></li><li><p>Index de repli pour les arrivées tardives.</p></li><li><p>Accepter une perte de données limitée.</p></li></ul><h3><strong>5. Monitorer l'intégrité de l'ingestion</strong></h3><p>Ajoutez des alertes pour :</p><ul><li><p>Le taux d'indexation chute.</p></li><li><p>Documents refusés.</p></li><li><p>Croissance du magasin des échecs.</p></li><li><p>Incohérences entre les entrées et les sorties du pipeline.</p></li></ul><p>Les problèmes de données tardives apparaissent souvent d'abord sous forme d'anomalies d'ingestion.</p><h2>Résumé</h2><p>Les flux de données temporelles apportent des améliorations majeures en termes de stockage et de performances pour les charges de travail des métriques, mais ils introduisent un changement architectural important : les index sous-jacents sont limités dans le temps, ce qui affecte le comportement de l'ILM.</p><p>Lors de l'utilisation de TSDS :</p><ul><li><p>Les index doivent rester inscriptibles suffisamment longtemps pour accepter les données différées.</p></li><li><p>Les pipelines d'ingestion doivent gérer les documents rejetés de manière sécurisée.</p></li></ul><p>La règle essentielle à retenir est la suivante :</p>warm_min_age &gt; rollover_max_age + maximum_expected_lateness<p>Si vous concevez les politiques ILM en tenant compte de cette contrainte, TSDS fonctionne de manière optimale pour les charges de travail de métriques.</p><p>En revanche, si vous ignorez cette règle, votre pipeline d'ingestion risque de découvrir ces limites temporelles à ses dépens.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/tsds-ilm-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/tsds-ilm-elasticsearch</guid>
    <category><![CDATA[Indexer des données]]></category>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Bret Wortman]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcacf154aeacb29d8/6a17dc30dbb4ffc7ddfb557f/e4c46e4a6f746d9c845857e80de036f5d51cd4e7-1280x720.png" length="0" type="image/png"/>
    <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[LINQ to Elasticsearch ES|QL : écrire en C#, interroger Elasticsearch]]></title>
    <description><![CDATA[Découverte du nouveau fournisseur LINQ to Elasticsearch ES|QL dans le client Elasticsearch .NET, qui vous permet d'écrire du code C# qui est automatiquement converti en requêtes ES|QL.]]></description>
    <content:encoded><![CDATA[<p>À partir des versions <strong>9.3.4</strong> et <strong>8.19.18</strong>, le client .NET Elasticsearch inclut un fournisseur <a href="https://learn.microsoft.com/en-us/dotnet/csharp/linq/">LINQ (Language Integrated Query) </a>qui traduit les expressions LINQ C# en <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">requêtes ES|QL (Elasticsearch Query Language)</a> à l'exécution. Au lieu d'écrire manuellement des chaînes ES|QL, vous composez vos requêtes à l'aide des fonctions <code>Where</code>, <code>Select</code>, <code>OrderBy</code>, <code>GroupBy</code> et d'autres opérateurs standard. Le fournisseur se charge de la traduction, du paramétrage et de la désérialisation des résultats, y compris le flux par ligne qui maintient l'utilisation de la mémoire constante, quelle que soit la taille de l'ensemble des résultats.</p><h2>Votre première requête</h2><p>Commencez par définir un objet CLR (POCO) classique qui correspond à votre index Elasticsearch. Les noms de propriétés sont résolus en noms de colonnes ES|QL via des attributs standard <code>System.Text.Json</code>, comme <code>[JsonPropertyName]</code>, ou via un <code>JsonNamingPolicy</code> configuré. Les mêmes règles de <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization">sérialisation des sources</a> que celles qui s'appliquent au reste du client s'appliquent également ici.</p>using System.Text.Json.Serialization;

public class Product
{
    [JsonPropertyName("product_id")]
    public string Id { get; set; }

    public string Name { get; set; }

    public string Brand { get; set; }

    [JsonPropertyName("price_usd")]
    public double Price { get; set; }

    [JsonPropertyName("in_stock")]
    public bool InStock { get; set; }
}<p>Une fois le type défini, une requête ressemble à ceci :</p>var minPrice = 100.0;
var brand = "TechCorp";

await foreach (var product in client.Esql.QueryAsync&lt;Product&gt;(q =&gt; q
    .From("products")
    .Where(p =&gt; p.InStock &amp;&amp; p.Price &gt;= minPrice &amp;&amp; p.Brand == brand)
    .OrderByDescending(p =&gt; p.Price)
    .Take(10)))
{
    Console.WriteLine($"{product.Name}: ${product.Price}");
}<p>Le fournisseur la traduit en ES|QL comme ceci :</p><p>Quelques détails à noter :</p><ul><li><p><strong>Résolution des noms de propriété</strong> : <code>p.Price</code> devient <code>price_usd</code> en raison de l'attribut <code>[JsonPropertyName]</code>, et <code>p.Brand</code> devient <code>brand</code> conformément à la politique de dénomination camelCase par défaut.</p></li><li><p><strong>Capture des paramètres</strong> : les variables C# <code>minPrice</code> et <code>brand</code> sont capturées comme paramètres nommés (<code>?minPrice</code>, <code>?brand</code>). Elles sont envoyées séparément de la chaîne de requête dans la charge utile JSON, ce qui empêche l'injection et permet la mise en cache du plan de requête côté serveur.</p></li><li><p><strong>Flux en continu</strong> : <code>QueryAsync&lt;T&gt;</code> renvoie <code>IAsyncEnumerable&lt;T&gt;</code>. Les lignes se matérialisent une à une à mesure de leur arrivée depuis Elasticsearch.</p></li></ul><p>Vous pouvez également inspecter la requête générée et ses paramètres sans l'exécuter :</p>var query = client.Esql.CreateQuery&lt;Product&gt;()
    .Where(p =&gt; p.InStock &amp;&amp; p.Price &gt;= minPrice &amp;&amp; p.Brand == brand)
    .OrderByDescending(p =&gt; p.Price)
    .Take(10);

Console.WriteLine(query.ToEsqlString());
// FROM products | WHERE (in_stock == true AND price_usd &gt;= 100) | SORT price_usd DESC | LIMIT 10

Console.WriteLine(query.ToEsqlString(inlineParameters: false));
// FROM products | WHERE (in_stock == true AND price_usd &gt;= ?minPrice AND brand == ?brand) | SORT price_usd DESC | LIMIT 10

var parameters = query.GetParameters();
// { "minPrice": 100.0, "brand": "TechCorp" }<h2>Comment ça marche ? Petit rappel sur LINQ</h2><p>Le mécanisme qui rend possibles les fournisseurs LINQ est la distinction entre <code>IEnumerable&lt;T&gt;</code> et <code>IQueryable&lt;T&gt;</code>.</p><p>Lorsque vous appelez <code>.Where(p =&gt; p.Price &gt; 100)</code> sur un <code>IEnumerable&lt;T&gt;</code>, la lambda est compilée en un <code>Func&lt;Product, bool&gt;</code>, un délégué standard que le runtime exécute en interne. C'est le principe du LINQ-to-Objects.</p><p>Lorsque vous appelez la même méthode sur un <code>IQueryable&lt;T&gt;</code>, le compilateur C# enveloppe la lambda dans un <code>Expression&lt;Func&lt;Product, bool&gt;&gt;</code> à la place. Il s'agit d'une structure de données qui représente la <em>structure</em> du code plutôt que sa forme exécutable. L'arbre d'expression peut être inspecté, analysé et traduit dans un autre langage au moment de l'exécution.</p>// IEnumerable: the lambda is a compiled delegate
IEnumerable&lt;Product&gt; local = products.Where(p =&gt; p.Price &gt; 100);

// IQueryable: the lambda is an expression tree, a data structure
IQueryable&lt;Product&gt; remote = queryable.Where(p =&gt; p.Price &gt; 100);<p>L’interface <code>IQueryProvider</code> est le point d’extension. Tout fournisseur peut implémenter <code>CreateQuery&lt;T&gt;</code> et <code>Execute&lt;T&gt;</code> pour traduire ces arbres d’expressions dans une langue cible. Entity Framework utilise ceci pour émettre du SQL. Le fournisseur LINQ to ES|QL l'utilise pour émettre ES|QL.</p><p>L'arbre d'expression de la requête ci-dessus ressemble à ceci :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt521838e8b9c36649/6a1705b1839dfa5f40dcfdfe/f864cd18a390831f8d28503a29b5835efb1842f7-1000x720.png" alt="Arbre d'expression de l'exemple de requête." /><p><em>Arbre d'expression de l'exemple de requête.</em></p><p>L'arbre est imbriqué de l'intérieur vers l'extérieur : <code>Take</code> englobe <code>OrderByDescending</code>, qui englobe <code>Where</code>, qui englobe <code>From</code>, qui englobe la racine constante <code>EsqlQueryable&lt;Product&gt;</code>. Le prédicat <code>Where</code> est lui-même un sous-arbre des nœuds <code>BinaryExpression</code> pour les opérateurs <code>&amp;&amp;</code>, <code>&gt;=</code>, et les opérateurs <code>==</code>, avec des feuilles <code>MemberExpression</code> pour les accès aux propriétés et des captures de fermeture pour les variables <code>minPrice</code> et <code>brand</code>. C'est cette structure de données que le fournisseur parcourt pour produire le code ES|QL final.</p><h2>Sous le capot : le pipeline de traduction</h2><p>Le chemin d'une expression LINQ vers les résultats de la requête suit un pipeline en six étapes :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt930670a505dd61ea/6a1705b3b339d58a54769ecf/2a2c772b63d720f61fc9a28b2f85668fa2db8d38-1999x1036.png" alt="Aperçu du pipeline de traduction." /><p><em>Aperçu du pipeline de traduction.</em></p><h3>1. Capture de l'arbre d'expressions</h3><p>Lorsque vous chaînez <code>.Where()</code>, <code>.OrderBy()</code>, <code>.Take()</code> et d’autres opérateurs sur un <code>IQueryable&lt;T&gt;</code>, l’infrastructure standard de LINQ construit un arbre d’expressions. <code>EsqlQueryable&lt;T&gt;</code> met en œuvre <code>IQueryable&lt;T&gt;</code> et délègue à <code>EsqlQueryProvider</code>.</p><h3>2. Traduction</h3><p>Lors de l'exécution de la requête (par énumération, appel de <code>ToList()</code> ou utilisation de <code>await foreach)</code>), le <code>EsqlExpressionVisitor</code> parcourt l'arbre d'expressions de l'intérieur vers l'extérieur. Il envoie chaque appel de méthode LINQ à un visiteur spécialisé :</p><p>Visiteur</p><p>Est traduit</p><p>En</p><p>WhereClauseVisitor</p><p>.Where(predicate)</p><p>Condition WHERE</p><p>SelectProjectionVisitor</p><p>.Select(selector)</p><p>EVAL + KEEP + RENAME</p><p>GroupByVisitor</p><p>.GroupBy().Select()</p><p>STATS ... BY</p><p>OrderByVisitor</p><p>.OrderBy() / .ThenBy()</p><p>Champ SORT [ASC\|DESC]</p><p>EsqlFunctionTranslator</p><p>EsqlFunctions.*, Math.*, méthodes string</p><p>Plus de 80 fonctions ES|QL</p><p>Lors de la traduction, les variables C# référencées dans les expressions sont capturées comme des paramètres nommés.</p><h3>3. Modèle de requête</h3><p>Les visiteurs ne produisent pas directement des chaînes de caractères. À la place, ils produisent des objets <code>QueryCommand</code> , une représentation intermédiaire immuable. Un objet <code>FromCommand</code>, un objet <code>WhereCommand</code>, un objet <code>SortCommand</code> et un objet <code>LimitCommand</code>, chacun représentant une commande de traitement ES|QL. Ces objets sont ensuite regroupés dans un modèle <code>EsqlQuery</code>.</p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt788c9936976f2f62/6a1705b50e2e4910da419ff0/2adc349b6cf655b96b7b3e826a134e8a17fe42fd-1999x1036.png" alt="Modèle de requête et schéma de commande." /><p><em>Modèle de requête et schéma de commande.</em></p><p>Ce modèle intermédiaire est découplé de l'arbre d'expression et du format de sortie. Il peut être inspecté, intercepté (via <code>IEsqlQueryInterceptor</code>) ou modifié avant d'être formaté.</p><h3>4. Formatage</h3><p><code>EsqlFormatter</code> parcourt chaque <code>QueryCommand</code> dans l'ordre et produit la chaîne ES|QL finale. Chaque commande devient une ligne, séparée par l'opérateur pipe (|) utilisé par ES|QL pour chaîner les commandes de traitement. Les identificateurs contenant des caractères spéciaux sont automatiquement échappés par des guillemets inversés.</p><h3>5. Exécution</h3><p>La chaîne ES|QL formatée et les paramètres capturés sont envoyés au point de terminaison <code>/_query</code> d'Elasticsearch sous forme de charge utile JSON. L'interface <code>IEsqlQueryExecutor</code> masque la couche transport, où l'architecture de packages en couches prend tout son sens.</p><h3>6. Matérialisation</h3><p><code>EsqlResponseReader</code> transmet la réponse JSON sans mettre en mémoire tampon l'ensemble des résultats. Un arbre <code>ColumnLayout</code>, précalculé une fois par requête, mappe les noms de colonnes ES|QL plats (comme <code>address.street</code>, <code>address.city</code>) aux propriétés POCO imbriquées. Chaque ligne est assemblée dans une instance <code>T</code> et renvoyée une par une via <code>IEnumerable&lt;T&gt;</code> ou <code>IAsyncEnumerable&lt;T&gt;</code>.</p><h2>L'architecture en couches</h2><p>La fonctionnalité LINQ to ES|QL est répartie sur trois packages :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt662bd0dd8861b6b6/6a1705b7a929cf7086ae08a2/41b8aae860ecdc2480edcb1c1d4cc9b03cfb78c9-1999x1036.png" alt="Architecture du package." /><p><em>Architecture des packages.</em>
<a href="https://www.nuget.org/packages/Elastic.Esql"><strong><code>Elastic.Esql</code></strong></a> est le moteur de traduction pur. Il ne dépend d'aucun HTTP et intègre les visiteurs d'expressions, le modèle de requêtes, le formateur et le lecteur de réponses. Vous pouvez l'utiliser de manière autonome pour créer et analyser des requêtes ES|QL sans connexion à Elasticsearch, ce qui est utile pour les tests, la journalisation des requêtes ou la création de votre propre couche d'exécution.</p>// Translation-only: no Elasticsearch connection needed
var provider = new EsqlQueryProvider();
var query = new EsqlQueryable&lt;Product&gt;(provider)
    .From("products")
    .Where(p =&gt; p.InStock)
    .OrderByDescending(p =&gt; p.Price);

Console.WriteLine(query.ToEsqlString());
// FROM products | WHERE in_stock == true | SORT price_usd DESC<p><a href="https://www.nuget.org/packages/Elastic.Clients.Esql"><strong><code>Elastic.Clients.Esql</code></strong></a> est un client ES|QL léger et autonome. Il ajoute l'exécution HTTP en plus de <code>Elastic.Esql</code> via <code>Elastic.Transport</code>. Si votre application n'a besoin que d'ES|QL et d'aucune autre API Elasticsearch, il s'agit de l'option de dépendance minimale.</p><p><a href="https://www.nuget.org/packages/Elastic.Clients.Elasticsearch"><strong><code>Elastic.Clients.Elasticsearch</code></strong></a> est le client complet Elasticsearch .NET. Il s'appuie également sur <code>Elastic.Esql</code> et expose le fournisseur LINQ via l'espace de noms <code>client.Esql</code>. C'est le point d'entrée recommandé pour la plupart des applications.</p><p>Les deux packages de la couche d'exécution fournissent leur propre implémentation de <code>IEsqlQueryExecutor</code>, l'interface de stratégie qui fait le lien entre la traduction et le transport.</p><p>Les trois packages sont compatibles avec Native AOT lorsqu'ils sont utilisés avec un <code>JsonSerializerContext</code> généré par la source. Pour le client complet, consultez la <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization#native-aot">documentation Native AOT</a>.</p><h2>Au-delà des bases</h2><p>L'exemple ci-dessus traitait du filtrage, du tri et de la pagination. Le fournisseur prend en charge un ensemble d'opérations plus étendu.</p><h3>Agrégations</h3><p><code>GroupBy</code>, associé aux fonctions d'agrégation dans <code>Select</code>, se traduit en ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/stats-by"><code>STATS ... BY</code></a>par :</p>var stats = client.Esql.Query&lt;Product, object&gt;(q =&gt; q
    .GroupBy(p =&gt; p.Brand)
    .Select(g =&gt; new
    {
        Brand = g.Key,
        Count = g.Count(),
        AvgPrice = g.Average(p =&gt; p.Price),
        MaxPrice = g.Max(p =&gt; p.Price)
    }));

// -&gt; FROM products | STATS COUNT(*), AVG(price_usd), MAX(price_usd) BY brand<h3>Projections</h3><p><code>Select</code>, avec des types anonymes, génère les commandes <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval"><code>EVAL</code></a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/keep"><code>KEEP</code></a> et <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/rename"><code>RENAME</code></a> :</p>var query = client.Esql.CreateQuery&lt;Product&gt;()
    .Select(p =&gt; new { ProductName = p.Name, p.Price, p.InStock });

// -&gt; FROM products | KEEP name, price_usd, in_stock | RENAME name AS ProductName<h3>Bibliothèque riche en fonctions</h3><p>Plus de 80 fonctions ES|QL sont disponibles via la classe <code>EsqlFunctions</code>, couvrant la gestion des dates et heures, des chaînes de caractères, des opérations mathématiques, des adresses IP, la correspondance de modèles et le calcul de scores. Les méthodes standard <code>Math.*</code> et <code>string.*</code> se traduisent également par :</p>.Where(p =&gt; p.Name.Contains("Pro"))       // -&gt; WHERE name LIKE "*Pro*"
.Where(p =&gt; EsqlFunctions.CidrMatch(      // -&gt; WHERE CIDR_MATCH(ip, "10.0.0.0/8")
    p.IpAddress, "10.0.0.0/8"))<h3>LOOKUP JOIN</h3><p>Les recherches par index croisé se traduisent en ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join"><code>LOOKUP JOIN</code></a>par :</p>var enriched = client.Esql.Query&lt;Product, object&gt;(q =&gt; q
    .LookupJoin&lt;Product, CategoryLookup, string, object&gt;(
        "category-lookup-index",
        product =&gt; product.Id,
        category =&gt; category.CategoryId,
        (product, category) =&gt; new { product.Name, category!.CategoryLabel }));<h3>Séquence d'échappement pour ES|QL brut</h3><p>Pour les fonctionnalités ES|QL non encore prises en charge par le fournisseur LINQ, vous pouvez ajouter des fragments bruts :</p>var results = client.Esql.Query&lt;Product&gt;(q =&gt; q
    .Where(p =&gt; p.InStock)
    .RawEsql("| EVAL discounted = price_usd * 0.9"));<h3>Requêtes asynchrones côté serveur</h3><p>Pour les requêtes de longue durée, soumettez-les pour un traitement en arrière-plan sur le serveur :</p>await using var asyncQuery = await client.Esql.SubmitAsyncQueryAsync&lt;Product&gt;(
    q =&gt; q.Where(p =&gt; p.InStock),
    asyncQueryOptions: new EsqlAsyncQueryOptions
    {
        WaitForCompletionTimeout = TimeSpan.FromSeconds(5),
        KeepAlive = TimeSpan.FromMinutes(10)
    });

await asyncQuery.WaitForCompletionAsync();
await foreach (var product in asyncQuery.AsAsyncEnumerable())
    Console.WriteLine(product.Name);<p>Les requêtes asynchrones côté serveur sont particulièrement utiles pour les requêtes analytiques de longue durée/le traitement de grands ensembles de données, qui peuvent dépasser les seuils de délai d'expiration habituels, ou dans les environnements sensibles aux délais d'expiration avec équilibreurs de charge, passerelles API ou proxys qui imposent des délais d'expiration HTTP stricts. Les requêtes asynchrones évitent les interruptions de connexion en découplant la soumission et la récupération des résultats.</p><h2>Premiers pas</h2><p>LINQ to ES|QL est disponible à partir de :</p><ul><li><p><strong>Elastic.Clients.Elasticsearch v9.3.4</strong> (branche 9.x)</p></li><li><p><strong>Elastic.Clients.Elasticsearch v8.19.18</strong> (branche 8.x)</p></li></ul><p>Installation depuis NuGet :</p><p><code>dotnet add package Elastic.Clients.Elasticsearch</code></p><p>Les points d’entrée sont sur <code>client.Esql</code>:</p><p>Méthode</p><p>Retours</p><p>Cas d'utilisation</p><p>Query&lt;T&gt;(...)</p><p>IEnumerable&lt;T&gt;</p><p>Exécution synchrone</p><p>QueryAsync&lt;T&gt;(...)</p><p>IAsyncEnumerable&lt;T&gt;</p><p>Streaming asynchrone</p><p>CreateQuery&lt;T&gt;()</p><p>IEsqlQueryable&lt;T&gt;</p><p>Composition et inspection avancées</p><p>SubmitAsyncQueryAsync&lt;T&gt;(...)</p><p>EsqlAsyncQuery&lt;T&gt;</p><p>Requêtes de longue durée côté serveur</p><p>Pour une description complète des fonctionnalités, notamment les options de requête, l'accès à plusieurs champs, les objets imbriqués et la gestion des champs à valeurs multiples, consultez la <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/linq-to-esql">documentation LINQ to ES|QL</a>.</p><h2>Conclusion</h2><p>LINQ to ES|QL apporte toute la puissance d'expression de LINQ to C# au langage de requêtes ES|QL d'Elasticsearch, vous permettant d'écrire des requêtes fortement typées et composables sans avoir à les concevoir manuellement. Grâce à la capture automatique des paramètres, la matérialisation en flux continu et une architecture de packages modulaire scalable, allant d'une simple traduction à un client Elasticsearch complet, il s'intègre naturellement aux applications .NET de toute taille. applications .NET de toute taille. Installez le client le plus récent, configurez vos expressions LINQ pour qu'elles pointent vers un index, et laissez le fournisseur gérer le reste.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/linq-esql-c-elasticsearch-net-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/linq-esql-c-elasticsearch-net-client</guid>
    <category><![CDATA[ES|QL]]></category>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Florian Bernd,Martijn Laarman]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa35fbcbbf4959f/6a1705b9dc55de19a4e00d07/e54132e915217063e9ed0ec45059c6cfc38e31dd-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Rapidité vs précision : mesurer le rappel de la recherche vectorielle quantifiée]]></title>
    <description><![CDATA[Comment mesurer le rappel pour la recherche vectorielle dans Elasticsearch avec une configuration minimale.]]></description>
    <content:encoded><![CDATA[<p>Tout le monde souhaite une recherche vectorielle instantanée. Or, les vecteurs de grande dimension sont volumineux. Un seul vecteur de type float-32 à 1 024 dimensions occupe une quantité importante de mémoire, et sa comparaison avec des millions d'autres est très coûteuse en calcul.</p><p>Pour résoudre ce problème, les moteurs de recherche comme Elasticsearch utilisent deux stratégies d'optimisation principales :</p><ol><li><p><strong>Recherche approximative (hierarchical navigable small world [HNSW])</strong> : au lieu de parcourir chaque document, nous construisons un graphe de navigation pour accéder rapidement au voisinage probable de la réponse.</p></li><li><p><strong>Quantification</strong> : nous compressons les vecteurs (par exemple, de nombres flottants 32 bits à des entiers 8 bits ou même à des valeurs binaires 1 bit) afin de réduire l'utilisation de la mémoire et d'accélérer les calculs.</p></li></ol><p>Mais l'optimisation s'accompagne souvent d'une taxe : la <strong>précision</strong>.</p><p>La crainte est légitime : "Si je compresse mes données et que je prends des raccourcis pendant la recherche, vais-je manquer les meilleurs résultats ?" "Cette optimisation dégrade-t-elle la pertinence de mon moteur de recherche ?"</p><p>Pour prouver que la quantification d'Elastic ne dégrade pas les résultats, nous avons construit un banc d'essai reproductible utilisant l'ensemble de données <a href="https://huggingface.co/datasets/fancyzhx/dbpedia_14"><strong>DBPedia-14</strong></a><a href="https://huggingface.co/datasets/fancyzhx/dbpedia_14"></a> pour calculer exactement la précision (en particulier <strong>le rappel)</strong> que nous sacrifions pour la vitesse lorsque nous utilisons les optimisations par défaut dans Elasticsearch.</p><p>tldr : c'est probablement beaucoup moins cher que vous ne le pensez. Consultez le <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/fast_vs_accurate_measuring_the_recall_of_quantized_vector_search/vector_recall_notebook.ipynb">notebook ici</a>, et essayez par vous-même.</p><h2><strong>Définitions (pour les non-experts)</strong></h2><p>Avant d'examiner le code, clarifions certains termes.</p><ul><li><p><strong>Pertinence ou rappel</strong> : la <strong>pertinence</strong> est subjective (ai-je trouvé des choses intéressantes ?). Le <strong>rappel</strong> est mathématique. Si la base de données contient 10 documents qui correspondent <em>parfaitement</em> à votre requête sur le plan mathématique et que le moteur de recherche en trouve neuf, votre rappel est de 90 % (ou 0,9).</p></li><li><p><strong>Recherche exacte (à plat)</strong> : parfois appelée méthode "force brute". Le moteur de recherche analyse chaque document dans un index et calcule la distance.</p><ul><li><p><em>Avantages</em> : rappel parfait à 100 %.</p></li><li><p><em>Inconvénients</em> : coût de calcul élevé et lenteur à grande échelle.</p></li></ul></li><li><p><strong>Recherche approximative (HNSW)</strong> : la méthode "raccourci". Le moteur de recherche construit un graphe <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">HNSW</a>. Il parcourt le graphe pour trouver les voisins les plus proches.</p><ul><li><p><em>Avantages</em> : extrêmement rapide et scalable.</p></li><li><p><em>Inconvénients</em> : vous risquez de manquer un voisin si le parcours du graphe s'arrête trop tôt.</p></li></ul></li></ul><h2><strong>L'expérience : exact versus approximatif</strong></h2><p>Pour tester le rappel, nous avons utilisé l'ensemble de données <strong>DBPedia-14</strong>, un grand ensemble de données de titres et de résumés répartis en 14 classes ontologiques, couramment utilisé pour l'entraînement et l'évaluation des modèles de catégorisation de texte. Plus précisément, nous nous concentrerons sur la catégorie "Film". Nous voulions comparer les paramètres de production optimisés à une vérité terrain mathématiquement parfaite.</p><p>Pour cette expérience, nous utilisons le modèle <a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text">jina-embeddings-v5-text-small</a>, un modèle multilingue de pointe qui fait référence dans le secteur de la représentation textuelle. Nous avons choisi ce modèle car il définit la norme actuelle pour les plongements lexicaux haute performance. En combinant l'excellente précision de Jina v5 avec la quantification native d'Elasticsearch, nous pouvons démontrer une architecture de recherche à la fois efficace en termes de calcul et garantissant une qualité de récupération optimale.</p><p>Nous avons configuré un index avec un double mapping. Nous avons ingéré le même texte dans deux champs différents simultanément :</p><ol><li><p><strong><code>content.raw</code></strong>avec le type : <code>flat</code>. Cela force Elasticsearch à effectuer une analyse exhaustive de l'ensemble des vecteurs Float32. Cette analyse renvoie des résultats de correspondance exacte qui serviront de référence.</p></li><li><p><strong><code>content</code></strong>avec le type <code>semantic_text</code>. Paramètres par défaut utilisant HNSW + Better Binary Quantization (BBQ). Il s'agit du paramétrage standard optimisé pour la production, permettant une correspondance approximative.</p></li></ol><h3><strong>Le test Recall@10</strong></h3><p>Pour notre métrique, nous avons utilisé Recall@10.</p><p>Nous avons choisi 50 films au hasard et lancé la même requête sur les deux champs.</p><ul><li><p>Si la recherche <strong>exacte (à plat)</strong> indique que les 10 premiers voisins sont les ID [1, 2, 3... 10].</p></li><li><p>Et que la recherche <strong>approximative (HNSW)</strong> renvoie les identifiants [1, 2, 3… 9, 99].</p></li><li><p>Nous avons trouvé neuf des 10 premiers correctement. Le score est <strong>0,9</strong>.</p></li></ul><p>Voici le mapping que nous avons utilisé :</p># The "Control Group": Forces exact brute-force scan
"raw": {
    "type": "semantic_text",
    "inference_id": ".jina-embeddings-v5-text-small",
    "index_options": {
        "dense_vector": {
            "type": "flat"
        }
    }
}<p><strong>Les résultats : la "ligne plate" du succès</strong></p><p>Nous avons effectué un test d'échelle, en rechargeant l'ensemble de données complet et en le testant par rapport à des index de 1 000 à 40 000 documents.</p><p>Voici ce qui est arrivé au score de rappel :</p><p>Documents</p><p>Score de rappel à 10</p><p>1 000</p><p>1,000 (100 %)</p><p>5 000</p><p>0,998 (100 %)</p><p>10 000</p><p>0,992 (99,4 %)</p><p>20,000</p><p>0,999 (99,0 %)</p><p>40 000</p><p>0,992 (98,8 %)</p><p>Les résultats étaient incroyablement stables. Même en augmentant l'échelle, la recherche approximative correspondait à la recherche exacte par force brute <strong>&gt;99 % du temps</strong>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8168a0a4946bade7/6a170e154a531b61b536a9eb/a4bfacb1d0cce6fdf6df0e1a9d4fc5d4007a66da-1999x1209.png" alt="stabilité de la recherche vectorielle : rappel vs taille de l'index" /><h2><strong>Pourquoi cela a-t-il si bien fonctionné ?</strong></h2><p>On pourrait s'attendre à ce que la compression des vecteurs en valeurs binaires nuise davantage à la précision. La raison en est liée à la façon dont Elasticsearch gère la récupération.</p><p>La plupart des modèles de plongement actuels génèrent des vecteurs Float32 en sortie, qui sont de grande taille. Pour optimiser la recherche, Elasticsearch utilise la quantification pour les vecteurs de grande dimension. Plus précisément, depuis la version 9.2, il utilise <a href="https://www.elastic.co/search-labs/blog/elasticsearch-9-1-bbq-acorn-vector-search">BBQ</a> par défaut.</p><p>BBQ utilise un mécanisme de <strong>rescoring</strong> :</p><ol><li><p><strong>Parcours</strong> : le moteur de recherche utilise les vecteurs compressés (quantifiés) pour parcourir rapidement le graphe HNSW. Grâce à la petite taille des vecteurs, il peut effectuer un suréchantillonnage efficace, générant ainsi une liste plus longue de candidats (par exemple, les 100 documents les plus similaires) sans dégradation des performances.</p></li><li><p><strong>Rescoring</strong> : une fois ces candidats identifiés, le système récupère les valeurs de pleine précision pour ces quelques documents uniquement afin de calculer le classement final et précis.</p></li></ol><p>Vous obtenez le meilleur des deux mondes : la rapidité de la quantification pour les opérations les plus lourdes et la précision des nombres à virgule flottante pour le tri final.</p><h2><strong>Pouvons-nous faire mieux ?</strong></h2><p>Il est important de noter que les résultats présentés ici utilisent les paramètres par défaut et un échantillon aléatoire de données. Considérez-les comme un point de départ performant. Bien que Jina v5 soit extrêmement puissant, ces scores de rappel ne constituent pas une garantie universelle pour tous les ensembles de données. Chaque ensemble de données présente ses propres spécificités, et même s'il est possible d'optimiser davantage les paramètres pour obtenir des performances encore meilleures, il est toujours conseillé de réaliser des tests comparatifs avec vos propres données afin d'en déterminer les limites.</p><h2><strong>Conclusion</strong></h2><p>Il s'agit d'un test à très petite échelle. L'objectif de cet exercice n'est pas de mesurer le modèle d'intégration ou BBQ en particulier, mais de démontrer comment mesurer facilement le rappel de votre ensemble de données avec une configuration minimale.</p><p>Si vous souhaitez effectuer ce test sur vos propres données, vous pouvez consulter le <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/fast_vs_accurate_measuring_the_recall_of_quantized_vector_search/vector_recall_notebook.ipynb">notebook ici</a> et essayer par vous-même.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/recall-vector-search-quantization</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/recall-vector-search-quantization</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Jeff Vestal]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt198c7085db96aa04/6a170e17cdacbfe88c7d2a86/09f03b9239d66c36763cdab3fafcdac207ff6d83-1280x720.png" length="0" type="image/png"/>
    <pubDate>Fri, 20 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Arrêt précoce adaptatif pour HNSW dans Elasticsearch]]></title>
    <description><![CDATA[Présentation d'une nouvelle stratégie adaptative d'arrêt précoce pour HNSW dans Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch utilise l'algorithme <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">Hierarchical Navigable Small World</a> (HNSW) pour effectuer une recherche vectorielle sur un graphe de proximité. HNSW est reconnu pour offrir un bon compromis entre la qualité des résultats des k plus proches voisins (kNN) et le coût associé.</p><p>Dans HNSW, la recherche s'effectue par expansion itérative des nodes candidats dans le graphe, en conservant un ensemble limité des voisins les plus proches découverts jusqu'à présent. Chaque expansion a un coût (opérations vectorielles, recherches aléatoires sur disque, etc.), et le bénéfice marginal de ce coût tend à diminuer à mesure que la recherche progresse.</p><p>Une façon d'optimiser le parcours du graphe HNSW est d'interrompre la recherche lorsque la probabilité marginale de trouver de nouveaux les plus proches n'augmente plus. C'est pourquoi, dans <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-dense-vector-hnsw-early-termination">Elasticsearch 9.2</a>, nous avons introduit un nouveau <a href="https://www.elastic.co/search-labs/blog/hnsw-knn-search-early-termination">mécanisme d'arrêt précoce</a>. Ce mécanisme interrompt la recherche lorsque la visite des nodes du graphe ne fournit pas suffisamment de nouveaux voisins les plus proches, de façon consécutive, pendant un nombre déterminé de fois.</p><p>Cet article explique comment nous avons amélioré le mécanisme mentionné d'arrêt précoce dans HNSW afin de mieux l'adapter à différents ensembles de données et distributions de données.</p><h2><strong>Arrêt précoce dans HNSW</strong></h2><p>Dans HNSW, la recherche se déroule en étendant itérativement les nodes candidats dans le graphe de proximité, en conservant un ensemble limité des voisins les plus proches découverts jusqu'à présent, jusqu'à avoir exploré l'ensemble du graphe ou répondu à certains critères d'arrêt précoce.</p><p>L'arrêt précoce n'est donc pas toujours une optimisation ; il <strong>fait partie intégrante de l'algorithme de recherche lui-même</strong>. Le moment où nous décidons d'interrompre la recherche détermine l'équilibre entre l'efficacité et le rappel. Dans Elasticsearch, il existe déjà plusieurs façons d'interrompre prématurément une requête sur HNSW :</p><ul><li><p>Un nombre maximal déterminé de nodes est exploré.</p></li><li><p>Un délai d'expiration déterminé est atteint.</p></li></ul><p>Bien que simples et prévisibles, ces règles sont largement <strong>indépendantes de ce qu'effectue réellement la recherche</strong>. De plus, elles servent principalement à garantir que la requête se termine dans un délai raisonnable pour l'utilisateur final.</p><p>Dans un <a href="https://www.elastic.co/search-labs/blog/hnsw-knn-search-early-termination">article de blog précédent</a>, nous avons introduit le concept de redondance dans HNSW. En bref, les calculs redondants se produisent lorsque HNSW continue d'évaluer de nouveaux nodes candidats qui ne permettent pas de trouver davantage de voisins les plus proches.</p><h2><strong>Patience : mesurer les progrès plutôt que les efforts</strong></h2><p>La notion de <em>patience</em> recadre l'arrêt précoce sur le <strong>progrès plutôt que sur l'effort</strong>.</p><p>Au lieu de demander :</p><p>"Combien d'étapes avons-nous franchies ?"</p><p>La nouvelle question devient :</p><p>« Quelle est la quantité de calcul que nous acceptons de gaspiller, jusqu'à ce que nous perdions espoir ? »</p><p>Lors d'une recherche HNSW, l'exploration précoce génère généralement des améliorations maximales de l'ensemble des k meilleurs candidats. Au cours des premières étapes de l'exploration du graphe HNSW, l'ensemble des voisins est mis à jour en continu à mesure que l'algorithme découvre des voisins de plus en plus proches du vecteur de requête. Avec le temps, ces améliorations deviennent plus rares à mesure que la recherche converge. L'<a href="https://cs.uwaterloo.ca/~jimmylin/publications/Teofili_Lin_ECIR2025.pdf">arrêt basé sur la patience</a> surveille ce schéma et interrompt la recherche lorsque les améliorations cessent de se produire pendant une période prolongée.</p><p>En pratique, lors de l'exploration du graphe HNSW, nous calculons également le taux de saturation de la file d'attente à chaque étape du parcours des nodes candidats. Ce taux mesure le pourcentage de voisins les plus proches restés inchangés lors de la visite du dernier node du graphe (ou l'inverse du nombre de nouveaux voisins introduits lors de la dernière itération). Si ce taux devient trop élevé pendant plusieurs itérations consécutives, l'exploration du graphe est interrompue.</p><p>D'un point de vue conceptuel, la patience considère la recherche HNSW comme un <strong>processus à rendements décroissants</strong>. Lorsque les rendements se stabilisent, continuer à explorer le graphe apporte peu d'avantages.</p><p>Ce recadrage est puissant car il lie directement l'arrêt aux <em>résultats observables</em> plutôt qu'à des limites fixes arbitraires.</p><p>L'avantage de cette technique d'arrêt précoce intelligent est que les explorations de graphes HNSW ont tendance à visiter un nombre plus restreint de nodes tout en conservant un rappel relatif quasi parfait.</p><p>Pour visualiser cela, nous pouvons tracer le nombre de rappels par node visité que nous avons obtenus avec l'arrêt précoce basé sur la patience (étiqueté <em><code>et=static</code></em>), par rapport au comportement par défaut du HNSW (étiqueté <em><code>et=no</code></em>) sur quelques ensembles de données, FinancialQA et Quora, ainsi que des modèles JinaV3 et E5-small.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfd0d692b9beb476a/6a170ef4dc55debf0be00e97/a9d07c5153ea64a2426c82487c36846030692bb9-1600x945.png" alt="Arrêt précoce adaptatif pour HNSW " /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt93509c251a1b641e/6a170ef6dc55dea2b3e00e9b/dac56125c4b16d1b596c9876b6ca9ac7b2dc87fa-1600x944.png" alt="Arrêt précoce adaptatif pour HNSW ES" /><h2><strong>Seuils statiques et dynamiques HNSW</strong></h2><p>Dans Elasticsearch, cela se traduit concrètement par l'utilisation de <strong>seuils statiques</strong>. Le premier seuil correspond au <strong>seuil de saturation</strong>, c'est-à-dire le niveau de saturation que nous considérons comme sous-optimal. Le second seuil correspond au nombre de nodes consécutifs du graphe pouvant être visités tout en maintenant une saturation de la file d'attente sous-optimale, soit le <strong>seuil de patience</strong>.</p><p>Lors de l'introduction de cette stratégie d'arrêt précoce dans Elasticsearch 9.2, nous avons opté pour des valeurs par défaut prudentes afin de maximiser le rappel tout en optimisant la latence et la consommation de mémoire. C'est pourquoi nous avons fixé le seuil de saturation à 100 % et le seuil de patience à 30 % (limité) de la valeur de <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-top-level-parameters:~:text=search%20request%20size.-,num_candidates,-(Optional%2C%20integer)%20The"><em><code>num_candidates</code></em></a> dans la requête KNN.</p><p>Dans de nombreux cas, ces paramètres ont donné de bons résultats. Cependant, deux requêtes demandant le même nombre de voisins peuvent avoir des comportements de convergence radicalement différents. Certaines requêtes rencontrent des voisinages locaux denses et saturent rapidement ; d'autres doivent parcourir de longs chemins épars avant de trouver des candidats compétitifs. Ces dernières se sont avérées les plus difficiles à gérer efficacement.</p><p>De ce fait, nous avons parfois constaté :</p><ul><li><p>Une surexploration pour les requêtes simples.</p></li><li><p>Un arrêt prématuré pour les requêtes complexes.</p></li></ul><p>Nous avons donc estimé que les valeurs de seuil déterminées codifiaient des hypothèses globales sur la convergence, alors que nous pouvions mieux adapter le HNSW à différentes dynamiques.</p><h2><strong>Rendre l'arrêt précoce de HNSW adaptatif</strong></h2><p>L'arrêt précoce adaptatif aborde ce problème sous un angle différent. Au lieu d'imposer des seuils d'arrêt prédéfinis, l'algorithme <strong>détermine le moment où il doit s'arrêter à partir de la dynamique de recherche elle-même.</strong></p><p>Ainsi, au lieu de comparer le taux de saturation de la file d'attente entre deux candidats consécutifs, nous avons décidé d'introduire à la fois un taux de découverte lissé instantané  (combien de nouveaux voisins ont été introduits pour une requête <em>q</em> lors de la dernière visite <em>i</em>), ainsi qu'une moyenne glissante  et un écart-type  d'un tel taux de découverte pendant la visite du graphe (en utilisant l'<a href="https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm">algorithme de Welford)</a>. Ces statistiques sur le taux de découverte sont calculées par requête, permettant ainsi de déterminer différents niveaux de patience pour chacune d'entre elles.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbfb1e123f1b026d/6a170ef7cf4f25d9bab2d216/1958be7ca4425ade66eaf621ada3533173183598-694x118.png" alt="" /><p>Les seuils auparavant statiques deviennent adaptatifs aux statistiques du taux de découverte : le seuil de saturation devient la moyenne mobile plus l'écart type, tandis que nous faisons en sorte que la patience s'adapte et évolue inversement avec l'écart type.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d4d91f464a9dc9d/6a170ef8d7c0223420de656a/f7ee4a55c24853b657df26052b275e8bd76cf0f9-654x156.png" alt="" /><p>Les règles de sortie précoce restent les mêmes ; la saturation survient lorsque le taux de découverte instantané est inférieur au seuil de saturation adaptatif. La visite du graphe s'arrête si la saturation persiste pendant un nombre d'explorations de candidats consécutives supérieur au seuil de patience adaptatif.</p><p>De cette façon, nous obtenons un comportement qui ne dépend pas du paramètre <em><code>num_candidates</code></em> dans la requête KNN (qui peut toujours être défini ou laissé par défaut, indépendamment d'une sortie anticipée) et qui s'adapte mieux à chaque requête et distribution vectorielle de manière dynamique.</p><p>Le rappel par node visité sur FinancialQA et Quora avec la stratégie adaptative (étiquetée <em><code>et=adaptive</code></em>) indique un rappel plus élevé par node visité, par rapport à la stratégie statique (<em><code>et=static</code></em>) et au comportement par défaut de HNSW (<em><code>et=no</code></em>).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blteab7ba53ae14da0e/6a170ef9961e69e072c4cfd5/2a906997d9a25d74c7038bd9661bc97581e7258e-1600x938.png" alt=" stratégie adaptative et comportement par défaut de HNSW" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6fb9e672d3698200/6a170efb67045b7b2b45c2ab/3a114911e232c351dbb814cea20e8b0f1415a717-1600x925.png" alt="" /><p>L'arrêt précoce adaptatif est activé par défaut dans Elasticsearch 9.3 pour les champs vectoriels denses HNSW (et peut éventuellement être désactivé via le <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-dense-vector-hnsw-early-termination">même paramètre de niveau d'index</a>).</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hnsw-elasticsearch-adaptive-early-termination</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hnsw-elasticsearch-adaptive-early-termination</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[À l'intérieur d'Elastic]]></category>
    <dc:creator><![CDATA[Tommaso Teofili]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt27b746cc1995e6b7/6a170efda29299de8ad010c6/e6d3186f609dd56dc5ffe33d70fa9e5cfa05b51f-1280x720.png" length="0" type="image/png"/>
    <pubDate>Mon, 02 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[La recherche vectorielle Elasticsearch est jusqu'à 8 fois plus rapide qu'OpenSearch]]></title>
    <description><![CDATA[Exploration des benchmarks de recherche vectorielle filtrée entre OpenSearch et Elasticsearch et pourquoi la performance de la recherche vectorielle est critique pour les systèmes d’ingénierie de contexte.]]></description>
    <content:encoded><![CDATA[<h2>Pourquoi la vitesse de recherche est importante pour les agents IA et l'ingénierie du contexte</h2><p>Sur un corpus de 20 millions de documents, nos benchmarks révèlent qu’Elasticsearch multiplie par 8 le débit d’OpenSearch en recherche vectorielle filtrée, avec un Recall@100 supérieur dans toutes les configurations évaluées. L’ingénierie de contexte va au-delà de la performance brute de la récupération vectorielle. Une pertinence maîtrisée (recherche hybride, filtrage), une exploitation simplifiée et des performances constantes sont tout aussi essentielles pour les équipes lors de l’évolution de leurs workflows. Comme les agents effectuent fréquemment des cycles itératifs de récupération et de raisonnement pour chaque demande, la latence de recherche agit comme un facteur multiplicatif. Toute optimisation ici améliore donc instantanément la réactivité de bout en bout et diminue les coûts d’exploitation.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9daec868eed84658/6a170bef6234e0c322db1a19/d5a52a07773f0942c2baa732dacfe782aac0f415-1600x683.png" alt="OpenSearch vs Elasticsearch : Benchmark du débit pour la recherche vectorielle filtrée" /><p>Pour l'ingénierie du contexte, la récupération n'est pas une étape unique. Les agents et les applications effectuent de manière répétée des exécutions de boucles, telles que récupérer → raisonner → récupérer, pour affiner les requêtes, vérifier les faits, assembler un contexte étayé et accomplir les tâches. Ce schéma est courant dans les workflows d'agents et la Retrieval-Augmented Generation (RAG) itérative. Comme la récupération peut être sollicitée de nombreuses fois pour une seule requête utilisateur, elle ajoute un délai à la réponse et/ou augmente les coûts d’infrastructure.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt718c72fb858e4c98/6a170bf00e2e496e7241a132/54ac476ff20a3cf93484298c9ae47612c12fc110-800x417.png" alt="L’ingénierie de contexte transforme un vaste ensemble de données contextuelles en une fenêtre de contexte limitée pour le LLM." /><h2>Pourquoi la performance de la recherche vectorielle est-elle critique ?</h2><p></p><p>Imaginez la réponse d’un assistant d’achat à cette requête : « Il me faut un sac à dos de type bagage à main à moins de 60 $, adapté à un ordinateur de 15 pouces, résistant à l’eau et disponible en livraison d’ici vendredi. »</p><p>Dans un environnement de production, il est rare que l’assistant lance une unique recherche vectorielle et en reste là. L’assistant lance une boucle de recherche afin d’élaborer le bon contexte, chaque phase étant soumise à des filtres précis : disponibilité, zone géographique, délais de livraison, image de marque ou encore conformité aux politiques internes.</p><p><strong>Étape 1 : interpréter l'intention et traduire en contraintes.</strong></p><p>L’agent transforme la requête en filtres structurés et en une recherche sémantique, comme suit :</p><ul><li><p>Filtres : en stock, livrable à l'utilisateur à son code postal, livraison avant vendredi, prix inférieur à 60 $, annonce valide</p></li><li><p>Requête vectorielle : « Sac à dos cabine ordinateur 15 pouces résistant à l’eau »</p></li></ul><p><strong>Étape 2 : récupérer les candidats, puis affiner.</strong></p><p>Elle répète souvent la récupération avec des variantes afin de ne pas manquer de bonnes correspondances :</p><ul><li><p>« sac à dos de voyage cabine compartiment ordinateur »</p></li><li><p>« sac à dos de trajet quotidien, résistant à l’eau, ordinateur 15 pouces »</p></li><li><p>« sac à dos de cabine léger »</p></li></ul><p>Chaque requête utilise les mêmes filtres d’éligibilité, car récupérer des éléments non pertinents ou indisponibles constitue un gaspillage de contexte.</p><p><strong>Étape 3 : Élargir la recherche pour confirmer les détails et réduire les risques.</strong></p><p>L’agent effectue une récupération supplémentaire pour valider les attributs déterminants du résultat final :</p><ul><li><p>Formulation concernant les matériaux et la résistance à l’eau</p></li><li><p>Dimensions et compatibilité du compartiment pour ordinateur portable</p></li><li><p>Modalités de retour et clauses de garantie</p></li><li><p>Options alternatives en cas de stock faible</p></li></ul><p>Ceci est l'ingénierie contextuelle en plusieurs étapes : Récupérer, raisonner, récupérer, assembler.</p><h2>L’importance de la latence et du rappel dans l’ingénierie de contexte</h2><p>Ces interactions peuvent impliquer des dizaines d’appels de récupération filtrés par session utilisateur. La latence par appel est donc un multiplicateur direct du temps de réponse de bout en bout, et un faible taux de rappel oblige à des tentatives supplémentaires ou amène l'agent à manquer des éléments éligibles, ce qui dégrade la qualité de la réponse.</p><p>À retenir : Dans les systèmes d’ingénierie de contexte, la recherche filtrée des plus proches voisins (ANN) n’est pas une simple requête unique. C’est une suite d’opérations sous contraintes : la performance de la recherche vectorielle se traduit donc directement en termes de latence, de débit et de coûts, alors même que le LLM capte toute l’attention en surface.</p><h2>Évaluation comparative</h2><h3>Résultats</h3><p>Dans le graphe 2, chaque point représente une configuration de test. Les performances optimales apparaissent dans le coin supérieur gauche : c’est là que le rappel est le plus important et la latence la plus réduite. Les données d’Elasticsearch se rapprochent davantage du coin supérieur gauche que celles d’OpenSearch, signe d’une vitesse et d’une précision accrues pour une même charge de travail.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb562b30a600cc8e1/6a170bf2cf4f253582b2d1b2/c50d1df00968cac18149a2799e6242fbe49b66a0-1600x990.png" alt=" Graphe 2 : rappel contre latence moyenne (rescore 1)." /><h4>Quelques informations clés</h4><ul><li><p><code>s_n_r_value</code>: Abréviation pour <code>size_numCandidates_rescoreOversample</code> (k et numCandidates sont égaux à numCandidates dans ces tests) ; par exemple, <code>100_500_1</code> signifie taille=100, candidats=500 et k=500, rescore oversample=1</p></li><li><p>Rappel : Mesure du Rappel@100 pour cette configuration spécifique</p></li><li><p>Latence moyenne (ms) : Temps de réponse moyen de bout en bout par requête</p></li><li><p>Débit : requêtes par seconde (QPS)</p></li><li><p>Pourcentage de rappel : Amélioration relative du rappel pour Elasticsearch comparativement à OpenSearch ((Elasticsearch - OpenSearch) / OpenSearch)</p></li><li><p>Latence Xs : latence moyenne d'OpenSearch divisée par la latence moyenne d'Elasticsearch</p></li><li><p>Débit Xs : débit Elasticsearch divisé par le débit OpenSearch</p></li></ul><p>Moteur</p><p>`s_n_r_value`</p><p>Rappel</p><p>Latence moyenne (ms)</p><p>Débit</p><p>Rappel %</p><p>Latence Xs</p><p>Débit Xs</p><p>Elasticsearch</p><p>100_250_1</p><p>0,7704</p><p>25</p><p>534,75</p><p>9,70 %</p><p>2,28</p><p>1,91</p><p>OpenSearch</p><p>100_250_1</p><p>0,7023</p><p>57,08</p><p>279,58</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_500_1</p><p>0,8577</p><p>25,42</p><p>524,14</p><p>7,20 %</p><p>2.4</p><p>2</p><p>OpenSearch</p><p>100_500_1</p><p>0,8001</p><p>60,9</p><p>262,12</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_750_1</p><p>0,8947</p><p>29,67</p><p>528,09</p><p>5,72 %</p><p>2,25</p><p>2,21</p><p>OpenSearch</p><p>100_750_1</p><p>0,8463</p><p>66,76</p><p>239,11</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_1000_1</p><p>0,9156</p><p>29,65</p><p>534,5</p><p>4,66 %</p><p>2,46</p><p>2,44</p><p>OpenSearch</p><p>100_1000_1</p><p>0,8748</p><p>72,88</p><p>219,01</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_1500_1</p><p>0,9386</p><p>31,84</p><p>497,3</p><p>3,38 %</p><p>2,71</p><p>2,68</p><p>OpenSearch</p><p>100_1500_1</p><p>0,9079</p><p>86,16</p><p>185,4</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_2000_1</p><p>0,9507</p><p>34,69</p><p>457,2</p><p>2,57 %</p><p>2,98</p><p>2,96</p><p>OpenSearch</p><p>100_2000_1</p><p>0,9269</p><p>103,36</p><p>154,55</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_2500_1</p><p>0,9582</p><p>37,9</p><p>418,43</p><p>1,99 %</p><p>3,28</p><p>3,26</p><p>OpenSearch</p><p>100_2500_1</p><p>0,9395</p><p>124,29</p><p>128,53</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_3000_1</p><p>0,9636</p><p>41,86</p><p>379,4</p><p>1,62 %</p><p>3,46</p><p>3,44</p><p>OpenSearch</p><p>100_3000_1</p><p>0,9482</p><p>144,67</p><p>110,34</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_4000_1</p><p>0,9705</p><p>50,28</p><p>316,21</p><p>1,06 %</p><p>3,87</p><p>3,85</p><p>OpenSearch</p><p>100_4000_1</p><p>0,9603</p><p>194,36</p><p>82,22</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_5000_1</p><p>0,9749</p><p>58,77</p><p>270,91</p><p>0,73 %</p><p>4,43</p><p>4,41</p><p>OpenSearch</p><p>100_5000_1</p><p>0,9678</p><p>260,33</p><p>61,38</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_6000_1</p><p>0,9781</p><p>66,75</p><p>238,59</p><p>0,52 %</p><p>4,91</p><p>4,89</p><p>OpenSearch</p><p>100_6000_1</p><p>0,973</p><p>327,44</p><p>48,81</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_7000_1</p><p>0,9804</p><p>74,64</p><p>213,49</p><p>0,38 %</p><p>5,28</p><p>5,27</p><p>OpenSearch</p><p>100_7000_1</p><p>0,9767</p><p>394,24</p><p>40,53</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_8000_1</p><p>0,9823</p><p>82,28</p><p>193,59</p><p>0,27 %</p><p>6,86</p><p>6,83</p><p>OpenSearch</p><p>100_8000_1</p><p>0,9797</p><p>564,14</p><p>28,33</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_9000_1</p><p>0,9837</p><p>90,08</p><p>176,96</p><p>0,16 %</p><p>7,63</p><p>7,61</p><p>OpenSearch</p><p>100_9000_1</p><p>0,9821</p><p>687,25</p><p>23,25</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_10000_1</p><p>0,9848</p><p>97,64</p><p>163,31</p><p>0,08 %</p><p>8,38</p><p>8,36</p><p>OpenSearch</p><p>100_10000_1</p><p>0,984</p><p>818,64</p><p>19,53</p><p></p><p></p><p></p><p>Par exemple, à <code>100_9000_1</code>, OpenSearch enregistre en moyenne 687 millisecondes par extraction, contre 90 millisecondes pour Elasticsearch, et dans une boucle de récupération en 10 étapes, cela représente environ 10 × (687 - 90) = six secondes de temps d’attente supplémentaire. </p><p>Découvrez les <a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.3-vs-os-3.5-vector-search/jingra/results/20260220">résultats complets</a>.</p><h3>Méthodologie</h3><p>À l’aide de Python pour l’envoi des requêtes et le suivi de la latence ainsi que des données statistiques, nous avons soumis les requêtes suivantes aux moteurs. N’oubliez pas que l’efficacité d’un moteur vectoriel repose sur l’ajustement de ses paramètres clés : le nombre de candidats analysés, le niveau d’agressivité du score de pertinence et la quantité de contexte fournie en retour. Ces configurations agissent directement sur le rappel (l’assurance de ne pas manquer la réponse adéquate) et sur la latence (la vélocité du traitement).</p><p>Pour nos tests, nous avons conservé les mêmes réglages de sélection de candidats, de nouveau calcul de score et de dimension de contexte que ceux d’un cycle de récupération itératif, puis nous avons mesuré l’efficacité d’Elasticsearch face à ce volume de données. Par la suite, nous avons testé OpenSearch avec une configuration identique afin d’établir un point de comparaison.</p><p>OpenSearch</p>GET &lt;INDEX_NAME&gt;/_search
{
  "query": {
    "knn": {
      "&lt;DENSE_VECTOR_FIELD_NAME&gt;": {
        "vector": [...],
        "k": &lt;NUMBER_OF_CANDIDATES&gt;,
        "method_parameters": {
          "ef_search": &lt;NUMBER_OF_CANDIDATES&gt;
        },
        "rescore": {
          "oversample_factor": &lt;OVERSAMPLE&gt;
        },
        "filter": {
          &lt;SOME_FILTER&gt;
        }
      }
    }
  },
  "size": &lt;RESULT_SIZE&gt;,
  "_source": {
    "excludes": [
      "&lt;DENSE_VECTOR_FIELD_NAME&gt;"
    ]
  }
}<ul><li><p><code>"size": &lt;RESULT_SIZE&gt;</code>: Nombre de résultats renvoyés au client. Pour ce benchmark, nous avons défini une taille de résultat de 100 pour l’évaluation du Rappel@100.</p></li><li><p><code>"k": &lt;NUMBER_OF_CANDIDATES&gt;</code>: Le nombre de candidats voisins les plus proches.</p></li><li><p><code>"ef_search": &lt;NUMBER_OF_CANDIDATES&gt;</code>: Lle nombre de vecteurs à examiner.</p></li><li><p><code>"oversample_factor": &lt;OVERSAMPLE&gt;</code>: Combien de vecteurs candidats sont récupérés avant réévaluation.</p></li></ul><p>Elasticsearch</p>GET &lt;INDEX_NAME&gt;/_search
{
  "query": {
    "knn": {
      "field": "&lt;DENSE_VECTOR_FIELD_NAME&gt;",
      "query_vector": [...],
      "k": &lt;NUMBER_OF_CANDIDATES&gt;,
      "num_candidates": &lt;NUMBER_OF_CANDIDATES&gt;,
      "rescore_vector": {
        "oversample": &lt;OVERSAMPLE&gt;
      },
      "filter": {
        &lt;SOME_FILTER&gt;
      }
    }
  },
  "size": &lt;RESULT_SIZE&gt;,
  "_source": {
    "excludes": [
      "&lt;DENSE_VECTOR_FIELD_NAME&gt;"
    ]
  }
}<ul><li><p><code>"size": &lt;RESULT_SIZE&gt;</code>: Nombre de résultats renvoyés au client. Pour ce benchmark, nous avons défini une taille de résultat de 100 pour l’évaluation du Rappel@100.</p></li><li><p><code>"k": &lt;NUMBER_OF_CANDIDATES&gt;</code>: Nombre de voisins les plus proches à renvoyer de chaque partition.</p></li><li><p><code>"num_candidates": &lt;NUMBER_OF_CANDIDATES&gt;</code>: Nombre de candidats au plus proche voisin à considérer par partition lors d'une opération de recherche <code>knn</code>.</p></li><li><p><code>"oversample": &lt;OVERSAMPLE&gt;</code>: Combien de vecteurs candidats sont récupérés avant réévaluation.</p></li></ul><p>Exemple</p><p><code>Knn</code> la requête, (<code>100_500_1</code>), serait la suivante :</p><p>OpenSearch</p>GET search_catalog_128/_search
{
  "query": {
    "knn": {
      "search_catalog_embedding": {
        "vector": [...],
        "k": 500,
        "method_parameters": {
          "ef_search": 500
        },
        "rescore": {
          "oversample_factor": 1
        },
        "filter": {
          "term": {
            "valid": true
          }
        }
      }
    }
  },
  "size": 100,
  "_source": {
    "excludes": [
      "search_catalog_embedding"
    ]
  }
}<p>Elasticsearch</p>GET search_catalog_128/_search
{
  "query": {
    "knn": {
      "field": "search_catalog_embedding",
      "query_vector": [...],
      "k": 500,
      "num_candidates": 500,
      "rescore_vector": {
        "oversample": 1
      },
      "filter": {
        "term": {
          "valid": true
        }
      }
    }
  },
  "size": 100,
  "_source": {
    "excludes": [
      "search_catalog_embedding"
    ]
  }
}<p>La configuration complète, ainsi que les scripts Terraform, les manifestes Kubernetes et le code de test de performance, sont disponibles dans ce <a href="https://github.com/elastic/competitive-benchmarking-studies">dépôt</a> dans le dossier <a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.3-vs-os-3.5-vector-search">es-9.3-vs-os-3.5-vector-search</a>.</p><h3>Configuration du clustering</h3><p>Nous avons utilisé six instances cloud de type e2-standard-16 pour nos tests, chacune équipée de 16 vCPUs et de 64 Go de mémoire vive. Nous avons configuré chaque pod Kubernetes hébergeant un node du moteur avec 15 vCPUs et 56 Go de RAM, en réservant 28 Go pour la mémoire de la JVM.</p><p>Les tests ont été effectués sur les versions 9.3.0 d’Elasticsearch et 3.5.0 d’OpenSearch. (Lucene 10,3,2). Comme les deux solutions reposent sur la même version de Lucene pour ce test, les écarts de performance constatés au niveau du débit et de la latence ne s’expliquent pas par le moteur de base, mais par la façon dont chaque plateforme orchestre la recherche kNN filtrée et les étapes de calcul de score. Pour ce test, nous avons configuré un index unique comportant trois shards primaires et une réplique (ce qui donne 6 partitions au total, soit 1 par node).</p><p>Nous avons par ailleurs mobilisé un serveur séparé, situé dans la même région, afin de faire tourner le client de test et de compiler les données de performance.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7c7bdd567395d5b7/6a170bf3c1e8a56ee2f882f6/f81002c9186e4c2d3e92f49d72418fee9860fc5e-761x401.png" alt="Configuration de clustering pour Elasticsearch et pour les benchmarks OpenSearch" /><h3>L’ensemble de données</h3><p></p><p>Pour ce test de performance, nous avons exploité un catalogue e-commerce de 20 millions de documents (embeddings), afin de simuler une recherche vectorielle avec filtres à l’échelle d’une application de production.</p><p></p><p>Chaque document représente un élément de catalogue et comprend :</p><p></p><ul><li><p>Un vecteur dense à 128 dimensions utilisé pour la recherche approximative par kNN.</p></li><li><p>Champs de métadonnées structurés servant au filtrage (disponibilité, validité des produits, etc.), afin de simuler la récupération de vecteurs voisins restreinte à une sélection de données qualifiées, comme c’est souvent le cas en environnement réel.</p></li></ul><p></p><p>Nous avons opté pour ces données car elles reflètent la problématique critique des systèmes de production actuels : le besoin de filtrage systématique qui vient s’ajouter à la recherche vectorielle, exigeant une efficacité maximale tant en termes de précision que de rapidité. Par rapport à des bases de données de petite taille, l’utilisation de 20 millions de documents permet de mieux simuler la charge de travail et la complexité de sélection des candidats rencontrées par les moteurs de recherche vectorielle filtrée dans un environnement réel.</p><h2>Conclusion</h2><p>Pour les systèmes d’IA de nouvelle génération, notamment ceux qui reposent sur la gestion dynamique du contexte, la performance brute de la recherche par vecteurs est un élément structurant de l’expérience utilisateur. C’est un facteur multiplicateur. Dans les architectures où les agents enchaînent les étapes de recherche et de réflexion, l’efficacité de la récupération détermine non seulement la rapidité de la réponse finale, mais aussi la pertinence des informations transmises au LLM.</p><p>Elasticsearch a fait preuve d’une supériorité constante lors de nos tests, affichant un meilleur rappel et une latence réduite par rapport à OpenSearch, particulièrement lorsque la précision de la recherche repose sur l’identification du document spécifique plutôt que sur une vague ressemblance vectorielle. Sur un ensemble de données contrôlé, la différence est nette, et en production, ces gains s’accumulent au fil de volumes massifs d’appels de récupération, améliorant la réactivité, augmentant la marge de capacité et réduisant les coûts d’infrastructure.</p><h3>Lecture complémentaire</h3><ol><li><p><a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">Qu'est-ce que l'ingénierie contextuelle ?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/series/context-engineering-hybrid-search-evolution">L'évolution du rechercher hybride et de l'ingénierie contextuelle</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/context-engineering-relevance-ai-agents-elasticsearch">L'impact de la pertinence dans l'ingénierie du contexte pour les agents IA</a></p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/opensearch-vs-elasticsearch-filtered-vector-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/opensearch-vs-elasticsearch-filtered-vector-search</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Sachin Frayne]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4b38e5114bbf098c/6a170bf560084b3a6c3c459d/fb7ee623925ca6696d643e437ce8efe5fe749079-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Indexation vectorielle jusqu'à 12 fois plus rapide dans Elasticsearch avec NVIDIA cuVS : accélération GPU : chapitre 2]]></title>
    <description><![CDATA[Découvrez comment Elasticsearch atteint un débit d'indexation près de 12 fois supérieur grâce à l'indexation vectorielle accélérée par GPU et NVIDIA cuVS.]]></description>
    <content:encoded><![CDATA[<p>Plus tôt cette année, Elastic a annoncé la <a href="https://ir.elastic.co/news/news-details/2025/Elastic-Brings-Enterprise-Data-to-NVIDIA-AI-Factories/default.aspx">collaboration</a> avec NVIDIA pour apporter l'accélération GPU à Elasticsearch, en intégrant <a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS</a>—comme détaillé lors d'une <a href="https://www.nvidia.com/en-us/on-demand/session/gtc25-S71286/">session à NVIDIA GTC</a> et dans divers <a href="https://www.elastic.co/search-labs/blog/gpu-accelerated-vector-search-elasticsearch-nvidia">blogs</a>. Cet article fait le point sur les efforts de co-ingénierie menés avec l'équipe de recherche vectorielle de NVIDIA.</p><h2>Récapitulatif</h2><p>Tout d'abord, faisons le point sur la situation. Elasticsearch s'est imposé comme une base de données vectorielle puissante, offrant un ensemble complet de fonctionnalités et des performances élevées pour la recherche de similitudes à grande échelle. Avec des capacités telles que la quantification scalaire, la quantification binaire améliorée (<a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">BBQ</a>), les opérations vectorielles <a href="https://www.elastic.co/blog/accelerating-vector-search-simd-instructions">SIMD</a> et des algorithmes plus efficaces en termes d'espace disque comme <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">DiskBBQ</a>, il offre déjà des options efficaces et flexibles pour gérer les charges de travail vectorielles.</p><p>En intégrant NVIDIA cuVS en tant que module accessible pour les tâches de recherche vectorielle, nous visons à améliorer considérablement les performances et l'efficacité de l'indexation vectorielle afin de mieux prendre en charge les charges de travail vectorielles à grande échelle.</p><h2>Le défi</h2><p>L'un des défis les plus complexes dans la création d'une base de données vectorielle haute performance est la construction de l'index vectoriel, le graphe <a href="https://arxiv.org/abs/1603.09320">HNSW</a>. La construction de l'index est rapidement dominée par des millions, voire des milliards d'opérations arithmétiques, car chaque vecteur est comparé à de nombreux autres. De plus, les opérations liées au cycle de vie des index, telles que la compression et les fusions, peuvent augmenter davantage la charge de calcul globale liée à l'indexation. À mesure que les volumes de données et les intégrations vectorielles associées augmentent de manière exponentielle, les GPU de calcul accéléré, conçus pour le parallélisme massif et les calculs mathématiques à haut débit, sont idéalement positionnés pour gérer ces charges de travail.</p><h2>Installez le plugin Elasticsearch-GPU</h2><p><a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS</a> est une bibliothèque open source CUDA-X pour la recherche vectorielle accélérée par GPU et le clustering de données, permettant une création rapide d'index et une récupération d'embeddings pour les charges de travail liées à l'IA et aux recommandations.</p><p>Elasticsearch utilise cuVS via <a href="https://mvnrepository.com/artifact/com.nvidia.cuvs/cuvs-java">cuvs-java</a>, une bibliothèque open-source développée par la communauté et gérée par NVIDIA. La bibliothèque cuvs-java est légère et repose sur l'<a href="https://docs.rapids.ai/api/cuvs/nightly/c_api/">API cuVS C</a> en utilisant <a href="https://openjdk.org/projects/panama/">Panama</a> Foreign Function pour exposer les fonctionnalités cuVS d'une manière idiomatique Java, tout en restant moderne et performante.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc7fd7361099da05a/6a17e920be608670af00477f/5f6daa1eb07f704a6707d9e6b7ccb81d0abaa8c9-566x419.png" alt="Fonctionnement d'Elasticsearch avec NVIDIA cuVS, l'indexation CPU et GPU" /><p>La bibliothèque cuvs-java est intégrée dans un <a href="https://github.com/elastic/elasticsearch/pull/135545">nouveau plug-in Elasticsearch</a> ; par conséquent, l'indexation vectorielle sur le GPU peut être effectuée sur le même node et processus Elasticsearch, sans qu'il soit nécessaire de provisionner du code ou du matériel externe. Lors de la création d'index, si la bibliothèque cuVS est installée et qu'un GPU est présent et configuré, Elasticsearch utilisera le GPU pour accélérer le processus d'indexation vectorielle. Les vecteurs sont transmis au GPU, qui crée un un graphe <a href="https://arxiv.org/abs/2308.15136">CAGRA</a>. Ce graphe est ensuite converti au format HNSW, ce qui le rend immédiatement disponible pour la rechercher vectorielle sur le processeur. Le format final du graphe construit est identique à celui qui serait construit sur le CPU ; cela permet à Elasticsearch d'exploiter les GPU pour une indexation vectorielle à haut débit lorsque le matériel sous-jacent le prend en charge, tout en libérant la puissance du CPU pour d'autres tâches (recherche simultanée, traitement des données, etc.).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt485f55f29d6df5c4/6a17e922be6086dcf3004785/3ea255bd9bfd7983f78143c5eba999d2149d72be-671x356.png" alt="" /><h2>Accélération de la création d'index</h2><p>Dans le cadre de l'intégration de l'accélération GPU dans Elasticsearch, plusieurs améliorations ont été apportées à cuvs-java, en mettant l'accent sur l'efficacité de l'entrée/sortie de données et l'invocation de fonctions. L'une des principales améliorations est l'utilisation de <a href="https://github.com/rapidsai/cuvs/blob/2cf5fa7666d703dccbe655f8214656b0952bb69b/java/cuvs-java/src/main/java/com/nvidia/cuvs/CuVSMatrix.java">cuVSMatrix</a> pour modéliser de manière transparente les vecteurs, qu'ils se trouvent sur le tas Java, hors tas ou dans la mémoire du GPU. Cela permet aux données de se déplacer efficacement entre la mémoire et le GPU, en évitant les copies inutiles de milliards de vecteurs potentiels.</p><p>Grâce à cette abstraction sous-jacente sans copie, le transfert vers la mémoire GPU et la récupération du graphe peuvent être effectués directement. Pendant l'indexation, les vecteurs sont d'abord mis en mémoire tampon sur le tas Java, puis envoyés au GPU pour construire le graphe CAGRA. Le graphe est ensuite récupéré à partir du GPU, converti au format HNSW et persisté sur le disque.</p><p>Au moment de la fusion, les vecteurs sont déjà stockés sur le disque, contournant ainsi entièrement le tas Java. Les fichiers d'index sont mappés en mémoire et les données sont transférées directement dans la mémoire du GPU. La conception s'adapte également facilement à différentes largeurs de bits, telles que float32 ou int8, et s'étend naturellement à d'autres schémas de quantification.</p><h2>Roulement de tambour... alors, comment ça fonctionne ?</h2><p>Avant d'examiner les chiffres, un peu de contexte s'impose. La fusion des segments dans Elasticsearch s'exécute généralement automatiquement en arrière-plan pendant l'indexation, ce qui rend difficile l'évaluation comparative de manière isolée. Pour obtenir des résultats reproductibles, nous avons utilisé la fusion forcée pour déclencher explicitement la fusion des segments dans une expérience contrôlée. Comme la fusion forcée effectue les mêmes opérations de fusion sous-jacentes que la fusion en arrière-plan, ses performances servent d'indicateur utile des améliorations attendues, même si les gains exacts peuvent différer selon les charges de travail d'indexation réelles.</p><p>Passons maintenant aux chiffres.</p><p>Nos premiers résultats de référence sont très prometteurs. Nous avons exécuté une évaluation comparative sur une instance AWS <code>g6.4xlarge</code> avec un stockage NVMe connecté localement. Un seul node d'Elasticsearch a été configuré pour utiliser le nombre optimal par défaut de threads d'indexation (8, soit un pour chaque noyau physique) et pour désactiver la<a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/merge"> limitation de fusion</a> (qui est moins applicable avec les disques NVMe rapides).</p><p>Pour l'ensemble de données, nous avons utilisé 2,6 millions de vecteurs avec 1 536 dimensions provenant de l' <a href="https://github.com/elastic/rally-tracks/blob/master/openai_vector/README.md">OpenAI Rally vector track</a>, encodés sous forme <a href="https://github.com/elastic/elasticsearch/pull/137072">de chaînes base64</a> et indexés sous forme de float32 <em>hnsw</em>. Dans tous les scénarios, les graphes créés atteignent des niveaux de rappel allant jusqu'à 95 %. Voici nos conclusions :</p><ul><li><p><strong>Débit d'indexation :</strong> en transférant la construction des graphiques vers le GPU pendant les vidages de mémoire tampon, nous multiplions le débit par environ 12.</p></li><li><p><strong>Fusion forcée :</strong> une fois l'indexation terminée, le GPU continue d'accélérer la fusion des segments, multipliant par environ 7 la vitesse de la phase de fusion forcée.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfea4ee13b5a3b10d/6a17e923e9ea879c6aa9c616/f60ea9ee5996e456f393ffd195ee7eada6e5a7c2-948x387.png" alt="" /><ul><li><p><strong>Utilisation du processeur :</strong> le transfert de la construction du graphe vers le GPU réduit considérablement l'utilisation moyenne et maximale du processeur. Les graphiques ci-dessous illustrent l'utilisation du CPU pendant l'indexation et la fusion, et mettent en évidence à quel point elle est plus faible lorsque ces opérations sont exécutées sur le GPU. La réduction de l'utilisation du CPU pendant l'indexation GPU permet de libérer des cycles CPU qui peuvent être réaffectés à l'amélioration des performances de recherche.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt80ff9c53f9b6884a/6a17e925445de9ee4b4d0187/5e680a5fc41700a877f3d8b2e5ce18ebd3f37a0b-1600x562.png" alt="" /><ul><li><p><strong>Rappel :</strong> la précision reste pratiquement identique entre les exécutions CPU et GPU, le graphique généré par le GPU affichant un rappel légèrement supérieur.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5cbe084eca27b8e4/6a17e926faa913317093c8b7/48a2b7758606bd321712b7d8378cd2640e652a4e-1384x544.png" alt="" /><h2>Comparaison selon un autre critère : le prix</h2><p>La comparaison précédente utilisait intentionnellement un matériel identique, la seule différence étant l'utilisation ou non du GPU lors de l'indexation. Cette configuration est utile pour isoler les effets du calcul brut, mais nous pouvons également examiner la comparaison du point de vue des coûts.</p><p>Pour un prix horaire à peu près équivalent à celui de la configuration accélérée par GPU, il est possible de provisionner une configuration uniquement sur processeur avec environ deux fois plus de ressources CPU et mémoire comparables : 32 vCPU (AMD EPYC) et 64 Go de RAM, permettant de doubler le nombre de threads d’indexation à 16.</p><p>Afin de garantir l'équité et la cohérence de la comparaison, nous avons réalisé cette expérience uniquement sur processeur sur une instance AWS g6.8xlarge, avec le GPU explicitement désactivé. Cela nous a permis de maintenir toutes les autres caractéristiques matérielles constantes tout en évaluant le compromis coût-performance entre l'accélération GPU et l'indexation uniquement sur processeur.</p><p>L'instance de processeur plus puissante montre effectivement une performance améliorée par rapport aux benchmarks de la section ci-dessus, comme on pouvait s'y attendre. Cependant, lorsque nous comparons cette instance de processeur plus puissante aux résultats originaux accélérés par GPU, le GPU offre toujours des gains de performance substantiels : <strong>~5x</strong> d'amélioration dans le débit d'indexation, et <strong>~6x </strong>dans la fusion forcée, tout en construisant des graphes qui atteignent des niveaux de rappel allant jusqu'à <strong>95%.</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt94b5eb6f95ba307d/6a17e928abe0f255d4dfea35/8ffa58cae3ad175ef2932a351aeef4c34a1407b9-948x394.png" alt="" /><h2>Conclusion</h2><p>Dans les scénarios de bout en bout, l'accélération GPU avec NVIDIA cuVS permet d'améliorer de près de 12 fois le débit d'indexation et de réduire de 7 fois la latence de fusion forcée, tout en diminuant considérablement l'utilisation du processeur. Cela démontre que l'indexation vectorielle et les charges de travail de fusion bénéficient considérablement de l'accélération GPU. Sur une comparaison ajustée en fonction des coûts, l'accélération GPU continue d'offrir des gains de performances substantiels, avec un débit d'indexation environ 5 fois supérieur et des opérations de fusion forcée 6 fois plus rapides.</p><p>L'indexation vectorielle accélérée par GPU est actuellement prévue pour la préversion technique dans Elasticsearch 9.3, dont la sortie est prévue début 2026.</p><p>Plus d'informations à venir.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-gpu-accelerated-vector-indexing-nvidia</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-gpu-accelerated-vector-indexing-nvidia</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Chris Hegarty,Hemant Malik,Corey Nolet,Manas Singh,Mithun Radhakrishnan,Mayya Sharipova,Lorenzo Dematte,Ben Frederickson]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1248d51633bd75d9/6a17e92ae9ea8714b3a9c61a/08f7469a4daaf67b7c5999585aae179b6680c78d-896x746.png" length="0" type="image/png"/>
    <pubDate>Wed, 03 Dec 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[Amélioration de la pertinence des modèles d'intégration multilingues grâce à un système hybride de classement des recherches]]></title>
    <description><![CDATA[Découvrez comment améliorer la pertinence des résultats de recherche du modèle d'intégration multilingue E5 en utilisant le reranker de Cohere et la recherche hybride dans Elasticsearch.]]></description>
    <content:encoded><![CDATA[<h2>Introduction</h2><p>Dans la <a href="https://www.elastic.co/search-labs/blog/multilingual-embedding-model-deployment-elasticsearch">dernière partie de cette série</a>, nous avons déployé le modèle E5 pré-entraîné d'Elastic (ainsi que d'autres modèles d'intégration de texte multilingue de Hugging Face) et nous nous sommes plongés dans la génération d'intégrations vectorielles denses à partir de vos données textuelles à l'aide d'Elasticsearch et de Kibana. Dans ce blog, nous examinerons les résultats de ces encastrements et mettrons en évidence les avantages significatifs de l'utilisation d'un modèle multilingue.</p><p>Maintenant que nous avons notre index <code>coco_multilingual</code>, la recherche nous donnera des documents en plusieurs langues, avec le champ "en" pour référence :</p># GET coco_multilingual/_search
    {
       "_index": "coco_multilingual",
       "_id": "WAiXQJYBgf6odR9bLohZ",
       "_score": 1,
       "_source": {
         "description": "Ein Parkmeßgerät auf einer Straße mit Autos",
         "en": "A row of parked cars sitting next to parking meters.",
         "language": "de",
         "vector_description": {...}
       }
     },
     . . .<h2>Effectuer une recherche en anglais</h2><p>Essayons d'effectuer la recherche en anglais et voyons ce qu'il en est :</p>GET coco_multi/_search
{
"size": 10,
"_source": [
  "description", "language", "en"
],
"knn": {
  "field": "vector_description.predicted_value",
  "k": 10,
  "num_candidates": 100,
  "query_vector_builder": {
    "text_embedding": {
      "model_id": ".multilingual-e5-small_linux-x86_64_search",
      "model_text": "query: kitty"
    }
  }
}
}{
       "_index": "coco_multi",
       "_id": "JQiXQJYBgf6odR9b6Yz0",
       "_score": 0.9334303,
       "_source": {
         "description": "Eine Katze, die in einem kleinen, gepackten Koffer sitzt.",
         "en": "A brown and white cat is in a suitcase.",
         "language": "de"
       }
     },
      {
       "_index": "coco_multi",
       "_id": "3AiXQJYBgf6odR9bFod6",
       "_score": 0.9281012,
       "_source": {
         "description": "Una bambina che tiene un gattino vicino a una recinzione blu.",
         "en": "A little girl holding a kitten next to a blue fence.",
         "language": "it"
       }
     },
     . . .<p>Ici, même si la requête semble faussement simple, nous recherchons les enchâssements numériques du mot "kitty" dans tous les documents, dans toutes les langues, sous le capot. Et comme nous effectuons une recherche vectorielle, nous pouvons rechercher sémantiquement tous les mots susceptibles d'être liés à "kitty" : "chat", "chaton", "félin", "gatto" (italien), "mèo" (vietnamien), 고양이 (coréen), 猫 (chinois), etc. Ainsi, même si ma requête est en anglais, nous pouvons rechercher du contenu dans toutes les autres langues. Par exemple, la recherche d'un chat l<code>ying on something</code> donne des documents en italien, en néerlandais ou en vietnamien. Une question d'efficacité !</p><h2>Recherche de contenu dans d'autres langues</h2>GET coco_multi/_search
{  
 "size": 100,
 "_source": [
   "description", "language", "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 50,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: kitty lying on something"
     }
   }
 }
}{
 "description": "A black kitten lays on her side beside remote controls.",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "en"
},
{
 "description": "un gattino sdraiato su un letto accanto ad alcuni telefoni ",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "it"
},
{
 "description": "eine Katze legt sich auf ein ausgestopftes Tier",
 "en": "a cat lays down on a stuffed animal",
 "language": "de"
},
{
 "description": "Một chú mèo con màu đen nằm nghiêng bên cạnh điều khiển từ xa.",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "vi"
}
. . .<p>De même, une recherche par mot-clé pour "chat" en coréen ("고양이") donnera également des résultats significatifs. Ce qui est spectaculaire ici, c'est que nous n'avons même pas de documents en coréen dans cet index !</p>GET coco_multi/_search
{
 "size": 100,
 "_source": [
   "description", "language", "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 50,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: 고양이"
     }
   }
 }
} {
       {
         "description": "eine Katze legt sich auf ein ausgestopftes Tier",
         "en": "a cat lays down on a stuffed animal",
         "language": "de"
       }
     },
     {
       {
         "description": "Một con chó và con mèo đang ngủ với nhau trên một chiếc ghế dài màu cam.",
         "en": "A dog and cat lying  together on an orange couch. ",
         "language": "vi"
       }
     },<p>Cela fonctionne parce que le modèle d'intégration représente le sens dans un espace sémantique partagé, ce qui permet de retrouver des images pertinentes même si la requête est formulée dans une langue différente de celle des légendes indexées.</p><h2>Augmenter la pertinence des résultats de recherche grâce à la recherche hybride et au reranking</h2><p>Nous sommes heureux que les résultats pertinents soient apparus comme prévu. Mais dans le monde réel, par exemple dans le commerce électronique ou dans les applications RAG qui doivent se limiter aux 5 à 10 premiers résultats les plus pertinents, nous pouvons utiliser un modèle de classement pour hiérarchiser les résultats les plus pertinents.</p><p>Par exemple, une requête demandant "quelle est la couleur du chat ?" en vietnamien donnera un grand nombre de résultats, mais les 1 ou 2 premiers ne seront pas forcément les plus pertinents.</p>GET coco_multi/_search
{
 "size": 20,
 "_source": [
   "description",
   "language",
   "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 20,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: con mèo màu gì?"
     }
   }
 }
}<p>Les résultats mentionnent tous le chat, ou une forme de couleur :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt979f5944b1708042/6a17ef76420229ff6829f6aa/33e1e887dbbdd1066cfedc7375f5e3b46538529e-859x847.png" alt="" /><p>Améliorons donc cela ! Intégrons le modèle de rerank multilingue de <a href="https://cohere.com/blog/rerank-3pt5">Cohere</a>pour améliorer le raisonnement correspondant à notre question.</p>PUT _inference/rerank/cohere_rerank
{
 "service": "cohere",
 "service_settings": {
   "api_key": "your_api_key",
   "model_id": "rerank-v3.5"
 },
 "task_settings": {
   "top_n": 10,
   "return_documents": true
 }
}


GET coco_multi/_search
{
"size": 10,
"_source": [
  "description",
  "language",
  "en"
],
"retriever": {
  "text_similarity_reranker": {
    "retriever": {
      "rrf": {
        "retrievers": [
          {
            "knn": {
              "field": "vector_description.predicted_value",
              "k": 50,
              "num_candidates": 100,
              "query_vector_builder": {
                "text_embedding": {
                  "model_id": ".multilingual-e5-small_linux-x86_64_search",
                  "model_text": "query: con mèo màu gì?" // English: What color is the cat?
                }
              }
            }
          }
        ],
        "rank_window_size": 100,
        "rank_constant": 0
      }
    },
    "field": "description",
    "inference_id": "cohere_rerank",
    "inference_text": "con mèo màu gì?"
  }
}
} {
       "_index": "coco_multi",
       "_id": "rQiYQJYBgf6odR9bBYyH",
       "_score": 1.5501487,
       "_source": {
         "description": "Hai cái điện thoại được đặt trên một cái chăn cạnh một con mèo con màu đen.",
         "en": "A black kitten lays on her side beside remote controls.",
         "language": "vi"
       }
     },
     {
       "_index": "coco_multi",
       "_id": "swiXQJYBgf6odR9b04uf",
       "_score": 1.5427427,
       "_source": {
         "description": "Một con mèo sọc nâu nhìn vào máy quay.", // Real translation: A brown striped cat looks at the camera 
         "en": "This cat is sitting on a porch near a tire.",
         "language": "vi"
       }
     },<p>Maintenant, avec les premiers résultats, notre application peut répondre en toute confiance que la couleur du chaton est noire ou brune avec des rayures. Ce qui est encore plus intéressant ici, c'est que notre recherche vectorielle a détecté une omission dans la légende anglaise de l'ensemble de données original. Il est capable de trouver le chat à rayures brunes alors que la traduction anglaise de référence a omis ce détail. C'est la force de la recherche vectorielle.</p><h2>Conclusion</h2><p>Dans ce blog, nous avons présenté l'utilité d'un modèle d'intégration multilingue, et comment tirer parti d'Elasticsearch pour intégrer les modèles afin de générer des intégrations, et d'améliorer efficacement la pertinence et la précision avec une recherche hybride et un reranker. Vous pouvez <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">créer votre propre cluster Cloud</a> pour essayer la recherche <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5">sémantique multilingue en utilisant notre modèle E5 prêt</a> à l'emploi sur la langue et l'ensemble de données de votre choix.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multilingual-embedding-model-hybrid-search-reranking</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multilingual-embedding-model-hybrid-search-reranking</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[Opérations]]></category>
    <dc:creator><![CDATA[Quynh Nguyen]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf625e3f63fcd9f54/6a17ef7796142a61f8eb1bcd/d341b04acecc8eeec321f5404e1643447ecc8526-720x420.png" length="0" type="image/png"/>
    <pubDate>Mon, 03 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Déploiement d'un modèle d'intégration multilingue dans Elasticsearch]]></title>
    <description><![CDATA[Apprenez à déployer un modèle d'intégration multilingue e5 pour la recherche vectorielle et l'extraction multilingue dans Elasticsearch.]]></description>
    <content:encoded><![CDATA[<h2>Introduction</h2><p>Dans un monde d'utilisateurs globaux, la recherche d'informations multilingues (CLIR) est cruciale. Au lieu de limiter les recherches à une seule langue, le CLIR vous permet de trouver des informations dans <em>n'importe quelle</em> langue, ce qui améliore l'expérience de l'utilisateur et rationalise les opérations. Imaginez un marché mondial où les clients du commerce électronique pourraient rechercher des articles dans leur langue et où les bons résultats apparaîtraient, sans qu'il soit nécessaire de localiser les données à l'avance. Ou encore, où les chercheurs universitaires peuvent rechercher des articles dans leur langue maternelle, avec nuance et complexité, même si la source est dans une autre langue.</p><p>Les modèles d'intégration de textes multilingues nous permettent justement de le faire. Les emboîtements sont un moyen de représenter le sens d'un texte sous forme de vecteurs numériques. Ces vecteurs sont conçus de manière à ce que les textes ayant des significations similaires soient situés à proximité les uns des autres dans un espace à haute dimension. Les modèles d'intégration de textes multilingues sont spécifiquement conçus pour représenter dans un espace vectoriel similaire les mots et les phrases ayant la même signification dans différentes langues.</p><p>Les modèles tels que le logiciel libre Multilingual E5 sont formés sur des quantités massives de données textuelles, souvent à l'aide de techniques telles que l'apprentissage contrastif. Dans cette approche, le modèle apprend à distinguer les paires de textes dont le sens est similaire (paires positives) de ceux dont le sens est différent (paires négatives). Le modèle est entraîné à ajuster les vecteurs qu'il produit de manière à maximiser la similarité entre les paires positives et à minimiser la similarité entre les paires négatives. Pour les modèles multilingues, ces données d'entraînement comprennent des paires de textes dans différentes langues qui sont des traductions l'une de l'autre, ce qui permet au modèle d'apprendre un espace de représentation commun pour plusieurs langues. Les enchâssements résultants peuvent ensuite être utilisés pour diverses tâches de NLP, y compris la recherche multilingue, où la similarité entre les enchâssements de texte est utilisée pour trouver des documents pertinents quelle que soit la langue de la requête.</p><h2>Avantages de la recherche vectorielle multilingue</h2><ul><li><p><strong>Nuance</strong>: La recherche vectorielle excelle à capturer le sens sémantique, allant au-delà de la correspondance des mots clés. Ceci est crucial pour les tâches qui nécessitent de comprendre le contexte et les subtilités de la langue.</p></li><li><p><strong>Compréhension multilingue</strong>: Permet de rechercher efficacement des informations dans plusieurs langues, même lorsque la requête et les documents utilisent un vocabulaire différent.</p></li><li><p><strong>Pertinence</strong>: Fournit des résultats plus pertinents en se concentrant sur la similarité conceptuelle entre les requêtes et les documents.</p></li></ul><p>"Prenons l'exemple d'un chercheur universitaire qui étudie l'impact des médias sociaux sur le discours politique" dans différents pays. Grâce à la recherche vectorielle, ils peuvent saisir des requêtes telles que "l'impatto dei social media sul discorso politico" (italien) ou "ảnh hưởng của mạng xã hội đối với diễn ngôn chính trị" (vietnamien) et trouver des articles pertinents en anglais, espagnol ou toute autre langue indexée. En effet, la recherche vectorielle identifie les articles qui traitent du <em>concept</em> de l'influence des médias sociaux sur la politique, et pas seulement ceux qui contiennent les mots clés exacts. Cela améliore considérablement l'étendue et la profondeur de leurs recherches.</p><h2>Se lancer</h2><p>Voici comment configurer CLIR en utilisant Elasticsearch - avec le modèle E5 qui est fourni dans la boîte. Nous utiliserons l'<a href="https://huggingface.co/datasets/romrawinjp/multilingual-coco">ensemble de données multilingues COCO</a>, qui contient des légendes d'images dans plusieurs langues, pour nous aider à visualiser deux types de recherches :</p><ol><li><p>Requêtes et termes de recherche dans d'autres langues sur un ensemble de données en anglais, et</p></li><li><p>Requêtes en plusieurs langues à partir d'un ensemble de données contenant des documents en plusieurs langues.</p></li></ol><p>Ensuite, nous exploiterons la puissance de la recherche hybride et du reranking pour améliorer encore les résultats de la recherche.</p><h2>Produits requis</h2><ul><li><p>Python 3.6+</p></li><li><p>Elasticsearch 8+</p></li><li><p>Client Elasticsearch Python : pip install elasticsearch</p></li></ul><h2>Ensemble de données</h2><p>L'<a href="https://huggingface.co/datasets/romrawinjp/multilingual-coco">ensemble de données COCO</a> est un ensemble de données de sous-titrage à grande échelle. Chaque image de l'ensemble de données est légendée dans plusieurs langues différentes, avec plusieurs traductions disponibles par langue. À des fins de démonstration, nous indexerons chaque traduction comme un document individuel, avec la première traduction anglaise disponible à titre de référence.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc7e508e9a7dffe8/6a17f3e2b1e113249579f394/d4f0632529c71a22fbdecf21c9f4f0bb64b8e69c-1600x567.png" alt="" /><h3>Étape 1 : télécharger l'ensemble de données multilingues COCO</h3><p>Pour simplifier le blog et faciliter le suivi, nous chargeons ici les 100 premières lignes du restval dans un fichier JSON local à l'aide d'un simple appel à l'API. Vous pouvez également utiliser la bibliothèque de données HuggingFace pour charger le jeu de données complet ou des sous-ensembles du jeu de données.</p>import requests
import json
import os
### Download multilingual coco dataset into a json file (for easy viewing)
### Here we are retrieving first 100 rows for this example
### Alternatively, you can use `datasets` library from Hugging Face
url = "https://datasets-server.huggingface.co/rows?dataset=romrawinjp%2Fmultilingual-coco&amp;config=default&amp;split=restval&amp;offset=0&amp;length=100"
response = requests.get(url)


if response.status_code == 200:
   data = response.json()
   output_file = "multilingual_coco_sample.json" 
   ### Loading the downloaded content into a json file locally
   with open(output_file, "w", encoding="utf-8") as f:
       json.dump(data, f, indent=4, ensure_ascii=False)
   print(f"Data successfully downloaded and saved to {output_file}")
else:
   print(f"Failed to download data: {response.status_code}")
   print(response.text)<p>Si les données sont chargées avec succès dans un fichier JSON, vous devriez voir quelque chose de similaire à ce qui suit :</p><p><code>Data successfully downloaded and saved to multilingual_coco_sample.json</code></p><h3>Étape 2 : (Démarrer Elasticsearch) et indexer les données dans Elasticsearch</h3><p>a) Démarrez votre serveur Elasticsearch local.</p><p>b) Lancer le client Elasticsearch.</p>from elasticsearch import Elasticsearch
from getpass import getpass


# Initialize Elasticsearch client
es = Elasticsearch(getpass("Host: "), api_key=getpass("API Key: "))


index_name = "coco"


# Create the index if it doesn't exist
if not es.indices.exists(index=index_name):
   es.indices.create(index=index_name, body=mapping)<p>c) Données d'index</p># Load the JSON data
with open('./multilingual_coco_sample.json', 'r') as f:
   data = json.load(f)


rows = data["rows"]
# List of languages to process
languages = ["en", "es", "de", "it", "vi", "th"]


# For each image, we will process each individual caption as its own document
bulk_data = []
for data in rows:
   row = data["row"]
   image = row.get("image")
   image_url = image["src"]


   # Process each language
   for lang in languages:
       # Skip if language not present in this row
       if lang not in row:
           continue


       # Get all descriptions for this language
 # along with first available English caption for reference
       descriptions = row[lang]
       first_eng_caption = row["en"][0]


       # Prepare bulk indexing data
       for description in descriptions:
           if description == "":
               continue
           # Add index operation
           bulk_data.append(
               {"index": {"_index": index_name}}
           )
           # Add document
           bulk_data.append({
               "language": lang,
               "description": description,
               "en": first_eng_caption,
               "image_url": image_url,
           })


# Perform bulk indexing
if bulk_data:
   try:
       response = es.bulk(operations=bulk_data)
       if response["errors"]:
           print("Some documents failed to index")
       else:
           print(f"Successfully bulk indexed {len(bulk_data)} documents")
   except Exception as e:
       print(f"Error during bulk indexing: {str(e)}")


print("Indexing complete!")<p>Une fois les données indexées, vous devriez voir quelque chose de similaire à ce qui suit :</p><p><code>Successfully bulk indexed 4840 documents</code></p><p><code>Indexing complete!</code></p><h3>Étape 3 : Déployer le modèle formé E5</h3><p>Dans Kibana, accédez à la page Stack Management &gt; <strong>Trained Models</strong>, et cliquez sur <strong>Deploy</strong> pour le modèle .multilingual-e5-small_linux-x86_64. option. Ce modèle E5 est un petit ordinateur multilingue optimisé pour linux-x86_64, que l'on peut utiliser dès sa sortie de l'emballage. En cliquant sur "Déployer", vous accédez à un écran où vous pouvez ajuster les paramètres de déploiement ou les configurations des vCPUs. Par souci de simplicité, nous utiliserons les options par défaut, en sélectionnant les ressources adaptatives, ce qui permettra de dimensionner automatiquement notre déploiement en fonction de l'utilisation.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfbc09867063a8e6f/6a17f3e3148009a295b4889d/95cd8f352425d1db2d04b00c3c88d1e71d1ef19a-1600x440.png" alt="" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt264a2016341e9b6f/6a17f3f0e8fbce18de3a1aa7/1599d99949dda8267acc58f400a403a3af5373ef-1600x655.png" alt="" /><p>Si vous souhaitez utiliser d'autres modèles d'intégration de texte, vous pouvez le faire en option. Par exemple, pour utiliser le BGE-M3, vous pouvez utiliser <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/eland/machine-learning#ml-nlp-pytorch">le client Python Eland d'Elastic</a> pour importer le modèle de HuggingFace.</p>export MODEL_ID="bge-m3"
export HUB_MODEL_ID="BAAI/bge-m3"
export CLOUD_ID={{CLOUD_ID}}
export ES_API_KEY={{API_KEY}}
docker run -it --rm docker.elastic.co/eland/eland \
eland_import_hub_model --cloud-id $CLOUD_ID --es-api-key $ES_API_KEY --hub-model-id $HUB_MODEL_ID --es-model-id $MODEL_ID --task-type text_embedding --start<p>Accédez ensuite à la page Modèles formés pour déployer le modèle importé avec les configurations souhaitées.</p><h3>Étape 4 : Vectorisation ou création d'enchâssements pour les données d'origine avec le modèle déployé</h3><p>Pour créer les enchâssements, nous devons d'abord créer un pipeline d'ingestion qui nous permettra de prendre le texte et de le faire passer par le modèle d'enchâssement de texte d'inférence. Vous pouvez le faire dans l'interface utilisateur de Kibana ou via l'API d'Elasticsearch.</p><p><strong>Pour ce faire via l'interface Kibana</strong>, après avoir déployé le modèle entraîné, cliquez sur le bouton <strong>Test </strong>. Cela vous permettra de tester et de prévisualiser les éléments intégrés générés. Créez une nouvelle vue de données pour l'index <code>coco</code> , définissez Data view sur la vue de données coco nouvellement créée, et définissez Field sur <code>description</code> car c'est le champ pour lequel nous voulons générer des embeddings.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt24a2b9a9a5111bbc/6a17f3f13e9e452c7fba15d5/cfe189e13dc118d325e7fb90bdace0c912e29f51-1088x1600.png" alt="" /><p>Cela fonctionne très bien ! Nous pouvons maintenant créer le pipeline d'ingestion et réindexer nos documents originaux, les faire passer par le pipeline et créer un nouvel index avec les embeddings. Pour ce faire, cliquez sur <strong>Créer un pipeline</strong>, ce qui vous guidera tout au long du processus de création du pipeline, avec des processeurs auto-remplis nécessaires pour vous aider à créer les embeddings.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte39c5ad52702103d/6a17f3f3e9ea87dd05a9c734/1e043c1c3279b66fbdf19c06b41e76e613043998-1600x1126.png" alt="" /><p>L'assistant peut également remplir automatiquement les processeurs nécessaires pour gérer les défaillances lors de l'ingestion et du traitement des données.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63e002a79d177cf4/6a17f3f596142a3e91eb1c48/8804d31b4f869078e3b2245040bbb0ab1720a94a-1600x1084.png" alt="" /><p>Créons maintenant le pipeline d'ingestion. Je nomme le pipeline <code>coco_e5</code>. Une fois le pipeline créé avec succès, vous pouvez immédiatement l'utiliser pour générer les embeddings en réindexant les données indexées d'origine vers un nouvel index dans l'assistant. Cliquez sur <strong>Réindexer </strong>pour lancer le processus.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt39243d9ad1779fdf/6a17f3f696142a13eaeb1c4c/e34b1b18f5b24420d4581fe4d657c569926c2023-1600x1126.png" alt="" /><h2>Pour des configurations plus complexes, nous pouvons utiliser l'API Elasticsearch.</h2><p>Pour certains modèles, en raison de la manière dont ils ont été entraînés, il peut être nécessaire d'ajouter certains textes à l'entrée réelle avant de générer les enchâssements, faute de quoi les performances s'en trouveront dégradées.</p><p>Par exemple, avec le e5, le modèle s'attend à ce que le texte d'entrée suive "passage : {content of passage}". Utilisons les pipelines d'ingestion pour y parvenir : Nous allons créer un nouveau pipeline d'ingestion <strong>vectorize_descriptions</strong>. Dans ce pipeline, nous allons créer un nouveau champ temporaire <code>temp_desc</code>, ajouter "passage : " au texte <code>description</code>, faire passer <code>temp_desc</code> par le modèle pour générer des enchâssements de texte, puis supprimer le champ <code>temp_desc</code>.</p>PUT _ingest/pipeline/vectorize_descriptions
{
"description": "Pipeline to run the descriptions text_field through our inference text embedding model",
"processors": [
 {
   "set": {
     "field": "temp_desc",
     "value": "passage: {{description}}"
   }
 },
 {
   "inference": {     
"field_map": {
       "temp_desc": "text_field"
     },
     "model_id": ".multilingual-e5-small_linux-x86_64_search",
     "target_field": "vector_description"
   }
 },
 {
   "remove": {
     "field": "temp_desc"
   }
 }
]
}<p>En outre, nous pourrions vouloir spécifier le <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-quantization">type de quantification</a> que nous voulons utiliser pour le vecteur généré. Par défaut, Elasticsearch utilise <code>int8_hnsw</code>, mais ici je veux <a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Better Binary Quantization</a> (ou <code>bqq_hnsw</code>), qui réduit chaque dimension à une précision d'un seul bit. Cela permet de réduire l'empreinte mémoire de 96% (ou 32x) au prix d'une plus grande précision. J'opte pour ce type de quantification parce que je sais que j'utiliserai plus tard un reranker pour améliorer la perte de précision.</p><p>Pour ce faire, nous allons créer un nouvel index nommé <strong>coco_multi</strong>, et spécifier les mappings. La magie réside ici dans le champ vector_description, où nous spécifions que le type de l'<strong>index_options</strong>est <strong>bbq_hnsw</strong>.</p>PUT coco_multi
{
 "mappings": {
   "properties": {
     "description": {
       "type": "text"
     },
     "en": {
       "type": "text"
     },
     "image_url": {
       "type": "keyword"
     },
     "language": {
       "type": "keyword"
     },
     "vector_description.predicted_value": {
       "type": "dense_vector",
       "dims": 384,
       "index": "true",
       "similarity": "cosine",
       "index_options": {
         "type": "bbq_hnsw" 
       }
     }
   }
 }
}<p>Nous pouvons maintenant réindexer les documents originaux dans un nouvel index, avec notre pipeline d'ingestion qui va "vectoriser" ou créer des embeddings pour le champ des descriptions.</p>POST _reindex?wait_for_completion=false
{
 "source": {
   "index": "coco"
 },
 "dest": {
   "index": "coco_multilingual",
   "pipeline": "vectorize_descriptions"
 }
}<p>Et c'est tout ! Nous avons déployé avec succès un modèle multilingue avec Elasticsearch et Kibana et appris étape par étape comment créer les vector embeddings avec vos données avec Elastic, soit via l'interface utilisateur Kibana, soit avec l'API Elasticsearch. Dans la deuxième partie de cette série, nous explorerons les résultats et les nuances de l'utilisation d'un modèle multilingue. En attendant, vous pouvez <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">créer votre propre cluster Cloud</a> pour essayer <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5">la recherche sémantique multilingue en utilisant notre modèle E5 prêt à</a> l'emploi sur la langue et l'ensemble de données de votre choix.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multilingual-embedding-model-deployment-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multilingual-embedding-model-deployment-elasticsearch</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[Opérations]]></category>
    <dc:creator><![CDATA[Quynh Nguyen]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt59254226694f93a6/6a17f3f81480098988b488a1/8f2aa7bebb6b2f701e274ba7282273f9ab4abed6-720x432.png" length="0" type="image/png"/>
    <pubDate>Wed, 22 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Filtrage de la recherche vectorielle : Garder la pertinence]]></title>
    <description><![CDATA[Il ne suffit pas d'effectuer une recherche vectorielle pour trouver les résultats les plus similaires à une requête. Le filtrage est souvent nécessaire pour réduire les résultats de la recherche. Cet article explique comment fonctionne le filtrage pour la recherche vectorielle dans Elasticsearch et Apache Lucene.]]></description>
    <content:encoded><![CDATA[<p>La recherche vectorielle ne suffit pas pour trouver des résultats pertinents. Il est très courant d'utiliser des critères de filtrage qui permettent de réduire les résultats de la recherche et d'éliminer les résultats non pertinents.</p><p>Comprendre le fonctionnement du filtrage dans la recherche vectorielle vous aidera à équilibrer les compromis entre performance et rappel, et à découvrir certaines des optimisations utilisées pour rendre la recherche vectorielle plus performante lorsque le filtrage est utilisé.</p><h2>Pourquoi le filtrage ?</h2><p>La recherche vectorielle a révolutionné la manière dont nous trouvons des informations pertinentes dans de grands ensembles de données, en nous permettant de découvrir des éléments sémantiquement similaires à une requête.</p><p>Toutefois, il ne suffit pas de trouver des articles similaires. Nous devons souvent réduire les résultats de la recherche en fonction de critères ou d'attributs spécifiques.</p><p>Imaginez que vous recherchiez un produit dans un magasin de commerce électronique. Une recherche purement vectorielle peut vous montrer des articles visuellement similaires, mais vous pouvez aussi vouloir filtrer par fourchette de prix, marque, disponibilité ou évaluations des clients. Sans filtrage, vous seriez confronté à un vaste éventail de produits similaires, ce qui rendrait difficile de trouver exactement ce que vous cherchez.</p><p>Le filtrage permet un contrôle précis des résultats de la recherche, garantissant que les éléments récupérés ne sont pas seulement alignés sur le plan sémantique, mais qu'ils répondent également à toutes les exigences nécessaires. L'expérience de recherche est ainsi beaucoup plus précise, efficace et conviviale.</p><p>C'est là qu'Elasticsearch et Apache Lucene excellent - l'utilisation d'un filtrage efficace sur différents types de données est l'une des principales différences avec les autres bases de données vectorielles.</p><h2>Filtrage pour la recherche vectorielle exacte</h2><p>Il existe deux manières principales d'effectuer des recherches de vecteurs exacts :</p><ul><li><p>Utilisation d'un type d'index <code>flat</code> pour votre champ dense_vector. Ainsi, les recherches sur <code>knn</code> utilisent la recherche exacte au lieu de la recherche approximative.</p></li><li><p>Utilisation d'une <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-script-score-query#vector-functions">requête script_score</a> qui utilise des fonctions vectorielles pour calculer le score. Ceci peut être utilisé avec n'importe quel type d'index.</p></li></ul><p>Lors de l'exécution d'une recherche vectorielle exacte, tous les vecteurs sont comparés à la requête. Dans ce cas, le filtrage améliore les performances, car seuls les vecteurs qui passent le filtre doivent être comparés.</p><p>Cela n'a pas d'incidence sur la qualité du résultat, car tous les vecteurs sont pris en compte de toute façon. Nous filtrons simplement à l'avance les résultats qui ne sont pas intéressants, afin de réduire le nombre d'opérations.</p><p>C'est très important, car il peut être plus performant d'exécuter une recherche exacte plutôt qu'une recherche approximative lorsque les filtres appliqués donnent un petit nombre de documents.</p><p>La règle de base est d'utiliser la recherche exacte lorsque moins de 10 000 documents passent le filtre. Les index <a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">BBQ</a> sont beaucoup plus rapides pour les comparaisons, il est donc logique d'utiliser la recherche exacte lorsque les index basés sont inférieurs à 100k. Consultez <a href="https://www.elastic.co/search-labs/blog/knn-exact-vs-approximate-search">cet article de blog</a> pour plus de détails.</p><p>Si vos filtres sont toujours très restrictifs, vous pouvez envisager une indexation axée sur la recherche exacte plutôt que sur la recherche approximative en utilisant un type d'index <code>flat</code> plutôt qu'un index basé sur HNSW. Pour plus de détails, voir <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-params">les propriétés de index_options</a>.</p><h2>Filtrage pour la recherche vectorielle approximative</h2><p>Lors de l'exécution d'une recherche vectorielle approximative, nous échangeons la précision des résultats contre la performance. Les structures de données de recherche vectorielle telles que HNSW recherchent efficacement les voisins les plus proches sur des millions de vecteurs. Ils se concentrent sur la récupération des vecteurs les plus similaires en effectuant le moins possible de comparaisons de vecteurs, qui sont coûteuses à calculer.</p><p>Cela signifie que les autres attributs de filtrage ne font pas partie des données vectorielles. Les différents types de données ont leurs propres structures d'indexation qui sont efficaces pour les trouver et les filtrer, comme les dictionnaires de termes, les listes d'écritures et les valeurs doc.</p><p>Étant donné que ces structures de données sont distinctes du mécanisme de recherche vectorielle, comment appliquer le filtrage à la recherche vectorielle ? Il existe deux options : appliquer les filtres après la recherche vectorielle (post-filtrage) ou avant la recherche vectorielle (préfiltrage).</p><p>Chacune de ces options présente des avantages et des inconvénients. Voyons cela de plus près !</p><h3>Post-filtrage</h3><p>Le post-filtrage applique des filtres après que la recherche vectorielle a été effectuée. Cela signifie que les filtres sont appliqués après que les k résultats vectoriels les plus similaires ont été trouvés.</p><p>Il est évident que nous pouvons potentiellement obtenir moins de k résultats après avoir appliqué les filtres aux résultats. Nous pourrions bien sûr obtenir plus de résultats à partir de la recherche vectorielle (valeur k plus élevée), mais nous ne serons pas sûrs d'obtenir k ou plus après avoir appliqué les filtres.</p><p>L'avantage du post-filtrage est qu'il ne modifie pas le comportement de la recherche vectorielle lors de l'exécution - la recherche vectorielle n'est pas consciente du filtrage. En revanche, il modifie le nombre final de résultats obtenus.</p><p>Voici un exemple de post-filtrage à l'aide de la <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query">requête knn</a>. Vérifier que la clause de filtrage est distincte de la requête knn :</p>{
  "query": {
    "bool": {
      "must": {
        "knn": {
          "field": "image-vector",
          "query_vector": [54, 10, -2],
          "k": 5,
          "num_candidates": 50
        }
      },
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}<p>Le post-filtrage est également disponible pour la recherche knn en utilisant le <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/filter-search-results#post-filter">post-filtre</a>:</p>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, 2],
    "k": 5,
    "num_candidates": 50
  },
  "post_filter": {
    "term": {
      "file-type": "png"
    }
  }
}<p>Gardez à l'esprit que vous devez utiliser une section de post-filtrage explicite avec la recherche knn. Si vous n'utilisez pas de post-filtre, la recherche knn <a href="https://www.elastic.co/docs/solutions/search/vector/knn#_combine_approximate_knn_with_other_features">combinera les résultats des plus proches voisins</a> avec d'autres requêtes ou filtres au lieu d'effectuer un post-filtre.</p><h3>Préfiltrage</h3><p>L'application de filtres avant la recherche vectorielle permet d'abord d'extraire les documents qui satisfont aux filtres, puis de transmettre ces informations à la recherche vectorielle.</p><p>Lucene utilise les <a href="https://github.com/apache/lucene/blob/7a60d7ce92392181e137361336e5196bd486cdd9/lucene/core/src/java/org/apache/lucene/util/BitSet.java">BitSets</a> pour stocker efficacement les documents qui satisfont aux conditions du filtre. La recherche vectorielle parcourt ensuite le graphe HNSW en tenant compte des documents qui satisfont à la condition. Avant d'ajouter un candidat aux résultats, il vérifie qu'il est contenu dans le BitSet des documents valides.</p><p>Cependant, le candidat doit être exploré et comparé à la requête, même s'il ne s'agit pas d'un document valide. L'efficacité de HNSW repose sur la connexion entre les vecteurs du graphe : si nous cessons d'explorer un candidat, cela signifie que nous risquons d'ignorer également ses voisins.</p><p>Imaginez que vous conduisiez pour vous rendre à une station-service. Si vous écartez les routes qui ne comportent pas de station-service, il est peu probable que vous arriviez à destination. Les autres routes ne sont peut-être pas celles dont vous avez besoin, mais elles vous <em>relient à</em> votre destination. Idem pour les vecteurs sur un graphique HNSW !</p><p>Il s'ensuit que l'application d'un préfiltrage est moins performante que la non-application de filtres. Nous devons effectuer le travail sur <em>tous les</em> vecteurs que nous visitons dans notre recherche, et nous devons rejeter ceux qui ne correspondent pas au filtre. Nous travaillons davantage et prenons plus de temps pour obtenir nos meilleurs résultats.</p><p>Voici un exemple de préfiltrage dans le DSL de requête Elasticsearch. Vérifiez que la clause de filtrage fait désormais partie de la section knn :</p>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, -2],
    "k": 5,
    "num_candidates": 50,
    "filter": {
      "term": {
        "file-type": "png"
      }
    }
  }
}<p>Le préfiltrage est disponible à la fois pour la <a href="https://www.elastic.co/docs/solutions/search/vector/knn#knn-search-filter-example">recherche knn</a> et la <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-filtering">requête knn</a>:</p>{
  "query": {
    "knn": {
      "field": "image-vector",
      "query_vector": [-5, 9, -12],
      "k": 5,
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}<h4>Optimisation du préfiltrage</h4><p>Il existe quelques optimisations que nous pouvons appliquer pour garantir la performance du préfiltrage.</p><p>Nous pouvons passer à la recherche exacte si le filtre est très restrictif. Lorsqu'il y a peu de vecteurs à comparer, il est plus rapide d'effectuer une recherche exacte sur les quelques documents qui satisfont le filtre.</p><p>Il s'agit d'une optimisation appliquée automatiquement dans <a href="https://github.com/apache/lucene/blob/eb876b618da5d04c1ad14b04a48321638318493a/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L218">Lucene</a> et Elasticsearch.</p><p>Une autre méthode d'optimisation consiste à ignorer les vecteurs qui ne satisfont pas au filtre. Au lieu de cela, cette méthode vérifie les voisins des vecteurs filtrés qui passent le filtre. Cette approche réduit effectivement le nombre de comparaisons puisque les vecteurs filtrés ne sont pas pris en compte, et continue d'explorer les vecteurs connectés au chemin actuel.</p><p>Cet algorithme est ACORN-1, et le processus est décrit en détail dans <a href="https://www.elastic.co/search-labs/blog/filtered-hnsw-knn-search">ce billet de blog.</a></p><h2>Filtrage à l'aide de la sécurité au niveau du document</h2><p><a href="https://www.elastic.co/docs/deploy-manage/users-roles/cluster-or-deployment-auth/controlling-access-at-document-field-level#document-level-security">Document Level Security (DLS)</a> est une fonctionnalité d'Elasticsearch qui spécifie les documents que les rôles d'utilisateurs peuvent récupérer.</p><p>La DLS est réalisée à l'aide de requêtes. Une requête peut être associée aux index pour un rôle, ce qui limite effectivement les documents qu'un utilisateur appartenant à ce rôle peut extraire des index.</p><p>L'interrogation sur le rôle est utilisée comme filtre pour <a href="https://github.com/elastic/elasticsearch/blob/c3a1cb34294e902a9f46d7e840ea09965019f456/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java#L92">extraire les documents qui y correspondent</a> et qui sont mis en cache sous la forme d'un ensemble de bits. Ce BitSet est ensuite utilisé pour envelopper le lecteur Lucene sous-jacent, de sorte que seuls les documents renvoyés par la requête sont considérés comme <em>vivants, c'est-à-dire</em>qu'ils existent dans l'index et n'ont pas été supprimés.</p><p>Étant donné que les documents sont <a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L196">extraits du lecteur</a> pour effectuer la requête knn, seuls les documents disponibles pour l'utilisateur seront pris en compte. S'il existe un préfiltre, les documents DLS <a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L204">y seront ajoutés</a>.</p><p>Cela signifie que le filtrage DLS fonctionne comme un préfiltre pour la recherche vectorielle approximative, avec les mêmes implications en termes de performances et d'optimisations.</p><p>Le DLS avec recherche exacte présente les mêmes avantages que l'application de n'importe quel filtre - moins il y a de documents extraits du DLS, plus la recherche exacte est performante. Tenez également compte du nombre de documents renvoyés par le DLS - si les rôles du DLS sont très restrictifs, vous pouvez envisager d'utiliser la recherche exacte au lieu de la recherche approximative.</p><h2>Analyse comparative</h2><p>Chez Elasticsearch, nous voulons nous assurer que le filtrage de la recherche vectorielle est efficace. Nous disposons d'<a href="https://elasticsearch-benchmarks.elastic.co/#tracks/so_vector/nightly/default/90d">un benchmark spécifique pour le filtrage vectoriel</a> qui effectue des recherches vectorielles approximatives avec différents filtrages afin de s'assurer que la recherche vectorielle continue à récupérer des résultats pertinents aussi rapidement que possible.</p><p>Vérifiez les <a href="https://elasticsearch-benchmark-analytics.elastic.co/app/dashboards#/view/43b63e80-5ba2-11ed-aede-a742809feed4?_g=(refreshInterval:(pause:!t,value:60000),time:(from:'2025-05-28T01:27:58.456Z',to:'2025-06-30T13:53:26.430Z'))&amp;_a=()">améliorations apportées</a> lors de l'introduction d'ACORN-1. Pour les tests où seuls 2% des vecteurs passent le filtre, le temps de latence des requêtes est réduit à 55% de la durée initiale :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt820cb0b715cf291c/6a17e227dbb4ff8d49fb5615/3eac3748a33376fc97d957364a5c1f5108d5c58b-1023x896.png" alt="" /><h2>Conclusion</h2><p>Le filtrage fait partie intégrante de la recherche. S'assurer que le filtrage est performant dans la recherche vectorielle, et comprendre les compromis et les optimisations, c'est ce qui fait l'efficacité et la précision d'une recherche.</p><p>Le filtrage a un impact sur les performances de la recherche vectorielle :</p><ul><li><p>La recherche exacte est plus rapide lorsque l'on utilise le filtrage. Vous pouvez envisager d'utiliser la recherche exacte au lieu de la recherche approximative si votre filtrage est suffisamment restrictif. Il s'agit d'une optimisation automatique dans Elasticsearch.</p></li><li><p>La recherche approximative est plus lente lorsque l'on utilise le préfiltrage. Le préfiltrage nous permet d'obtenir les k premiers résultats correspondant au filtre, au prix d'une recherche plus lente.</p></li><li><p>Le post-filtrage ne permet pas nécessairement de retrouver les k premiers résultats, car ils peuvent être filtrés par le filtre lorsqu'il est appliqué.</p></li></ul><p>Bon filtrage !</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-search-filtering</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-search-filtering</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[Lucene]]></category>
    <category><![CDATA[À l'intérieur d'Elastic]]></category>
    <dc:creator><![CDATA[Carlos Delgado]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt39ede1736e0f1456/6a17e2282f4a5c5031fa8843/03b1dd4c7bda4fbabd8e374bc2e4f12d5be6ef5f-1600x1150.png" length="0" type="image/png"/>
    <pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Mapping embeddings to Elasticsearch field types : semantic_text, dense_vector, sparse_vector]]></title>
    <description><![CDATA[Discuter comment et quand utiliser semantic_text, dense_vector, ou sparse_vector, et comment ils sont liés à la génération d'embedding.]]></description>
    <content:encoded><![CDATA[<p>L'utilisation d'enchâssements pour améliorer la pertinence et la précision de la recherche d'informations s'est considérablement développée au fil des ans. Des outils comme Elasticsearch ont évolué pour prendre en charge ce type de données grâce à des types de champs spécialisés tels que les vecteurs denses, les vecteurs épars et le texte sémantique. Cependant, pour obtenir de bons résultats, il est essentiel de comprendre comment faire correspondre correctement les embeddings aux types de champs Elasticsearch disponibles : <code>semantic_text</code>, <code>dense_vector</code>, et <code>sparse_vector</code>.</p><p>Dans cet article, nous examinerons ces types de champs, quand utiliser chacun d'entre eux et comment ils sont liés à la génération d'encarts et aux stratégies d'utilisation, à la fois lors de l'indexation et de l'interrogation.</p><h2>Type de vecteur dense</h2><p>Le type de champ <code>dense_vector</code> dans Elasticsearch est utilisé pour stocker des vecteurs denses, qui sont des représentations numériques de données telles que du texte, des images et de l'audio, où presque toutes les dimensions sont pertinentes... Ces vecteurs sont générés à l'aide de modèles d'intégration fournis par des plateformes telles que OpenAI, Cohere ou Hugging Face, et sont conçus pour capturer la signification sémantique globale des données, même si elles ne partagent pas de termes exacts avec d'autres documents.</p><p>Dans Elasticsearch, les vecteurs denses peuvent avoir jusqu'à 4096 dimensions en fonction du modèle utilisé. Par exemple, le modèle all-MiniLM-L6-v2 génère des vecteurs de 384 dimensions, tandis que le modèle text-embedding-ada-002 d'OpenAI produit des vecteurs de 1536 dimensions.</p><p>Le champ <code>dense_vector</code> est généralement adopté comme type par défaut pour stocker ce type d'intégration lorsqu'un plus grand contrôle est nécessaire, comme l'utilisation de vecteurs pré-générés, l'application de fonctions de similarité personnalisées ou l'intégration avec des modèles externes.</p><h3>Quand et pourquoi utiliser le type dense_vector ?</h3><p>Les vecteurs denses sont excellents pour capturer la similarité sémantique entre des phrases, des paragraphes ou des documents entiers. Ils fonctionnent très bien lorsque l'objectif est de comparer le sens global des textes, même s'ils ne partagent pas les mêmes termes.</p><p>Le champ de vecteurs denses est idéal lorsque vous disposez déjà d'un pipeline externe de génération d'incrustations utilisant des modèles fournis par des plateformes telles que OpenAI, Cohere ou Hugging Face et que vous souhaitez uniquement stocker et interroger ces vecteurs manuellement. Ce type de champ offre une grande compatibilité avec les modèles d'intégration et une souplesse totale en matière de génération et d'interrogation, ce qui permet de contrôler la manière dont les vecteurs sont produits, indexés et utilisés lors de la recherche.</p><p>En outre, il prend en charge différentes formes de recherche sémantique, avec des requêtes telles que k-NN ou script_score pour les cas où il est nécessaire d'ajuster la logique de classement. Ces possibilités font du vecteur dense un outil idéal pour des applications telles que RAG (Retrieval-Augmented Generation), les systèmes de recommandation et les recherches personnalisées basées sur la similarité.</p><p>Enfin, le champ vous permet de personnaliser la logique de pertinence, en utilisant des fonctions telles que <code>cosineSimilarity</code>, <code>dotProduct</code> ou <code>l2norm</code> pour adapter le classement aux besoins de votre cas d'utilisation. </p><p>Les vecteurs denses restent la meilleure option pour ceux qui ont besoin de flexibilité, de personnalisation et de compatibilité avec des cas d'utilisation avancés comme ceux mentionnés ci-dessus.</p><h3>Comment utiliser la requête pour un type de vecteur dense ?</h3><p>Les recherches sur les champs définis comme <strong><code>dense_vector</code></strong> utilisent la requête des k-voisins les plus proches. Cette requête est chargée de trouver les documents dont le vecteur dense est le plus proche du vecteur de la requête. Vous trouverez ci-dessous un exemple d'application d'une requête k-NN à un champ vectoriel dense :</p>{
  "knn": {
    "field": "my_dense_vector",
    "k": 10,
    "num_candidates": 50,
    "query_vector": [/* vector generated by model */]
  }
}<p>Outre la requête k-NN, s'il est nécessaire de personnaliser la notation des documents, il est également possible d'utiliser la requête script_score, en la combinant avec des fonctions de comparaison vectorielle telles que <strong>cosineSimilarity, dotProduct ou l2norm</strong> pour calculer la pertinence d'une manière plus contrôlée. Voir l'exemple :</p>{
"script_score": {
    "query": { "match_all": {} },
    "script": {
      "source": "cosineSimilarity(params.query_vector,
'my_dense_vector') + 1.0",
      "params": {
        "query_vector": [/* vector */]
      }
    }
  }
}<p>Si vous souhaitez aller plus loin, je vous recommande d'explorer l'article <a href="https://www.elastic.co/search-labs/blog/vector-search-set-up-elasticsearch">Comment configurer la recherche vectorielle dans Elasticsearch.</a></p><p></p><h2>Type de vecteur épars</h2><p>Le type de champ <strong><code>sparse_vector</code></strong> est utilisé pour stocker des vecteurs épars, qui sont des représentations numériques où la plupart des valeurs sont nulles et où seuls quelques termes ont des poids significatifs. Ce type de vecteur est courant dans les modèles basés sur les termes tels que SPLADE ou ELSER (Elastic Learned Sparse EncodeR).</p><h3>Quand et pourquoi utiliser un type de vecteur peu dense ?</h3><p>Les vecteurs épars sont idéaux lorsque vous avez besoin d'une recherche plus précise en termes lexicaux, sans sacrifier l'intelligence sémantique. Ils représentent le texte sous forme de paires jeton/valeur, en ne mettant en évidence que les termes les plus pertinents avec les poids associés, ce qui permet de gagner en clarté, en contrôle et en efficacité.</p><p>Ce type de champ est particulièrement utile lorsque vous générez des vecteurs basés sur des termes, comme dans les modèles ELSER ou SPLADE, qui attribuent des poids différents à chaque mot en fonction de son importance relative dans le texte.</p><p>Pour les cas où vous souhaitez contrôler l'influence de mots spécifiques dans la requête, les types de vecteurs épars vous permettent d'ajuster manuellement le poids des termes afin d'optimiser le classement des résultats.</p><p>Parmi les principaux avantages, citons la transparence de la recherche, puisqu'il est possible de comprendre clairement pourquoi un document a été jugé pertinent, et l'efficacité du stockage, puisque seuls les jetons dont la valeur n'est pas nulle sont enregistrés, contrairement aux vecteurs denses qui stockent toutes les dimensions.</p><p>En outre, les vecteurs épars sont le complément idéal des stratégies de recherche hybrides et peuvent même être combinés avec des vecteurs denses pour associer la précision lexicale à la compréhension sémantique.</p><h3>Comment utiliser l'interrogation pour le type de vecteur épars ?</h3><p>La requête <strong><code>sparse_vector</code></strong> vous permet de rechercher des documents sur la base d'un vecteur de requête au format jeton/valeur. Vous trouverez ci-dessous un exemple de requête :</p>{
  "query": {
    "sparse_vector": {
      "field": "field_sparse",
      "query_vector": {
        "token1": 0.6,
        "token2": 0.2,
        "token3": 0.9
      }
    }
  }
}<p>Si vous préférez utiliser un modèle formé, il est possible d'utiliser un point final d'inférence qui transforme automatiquement le texte de la requête en un vecteur peu dense :</p>{
  "query": {
    "sparse_vector": {
      "field": "field_sparse",
      "inference_id": "the inference ID to produce the token/weights",
      "query": "search text"
    }
  }
}<p>Pour approfondir ce sujet, je vous suggère de lire <a href="https://www.elastic.co/search-labs/blog/sparse-vector-embedding">Understanding sparse vector embeddings with trained ML models (Comprendre les encastrements de vecteurs épars avec des modèles ML formés).</a></p><h2>Type de texte sémantique</h2><p>Le type de champ <strong><code>semantic_text</code></strong> est le moyen le plus simple et le plus direct d'utiliser la recherche sémantique dans Elasticsearch. Il gère automatiquement la génération de l'intégration, à la fois au moment de l'indexation et de l'interrogation, par le biais d'un point final d'inférence. Cela signifie que vous n'avez pas à vous soucier de générer ou de stocker des vecteurs manuellement.</p><h3>Quand et pourquoi utiliser le texte sémantique ?</h3><p>Le champ <code>semantic_text</code> est idéal pour ceux qui veulent commencer avec un minimum d'effort technique et sans avoir à manipuler les vecteurs manuellement. Ce champ automatise des étapes telles que la génération de l'encastrement et le mappage de la recherche vectorielle, ce qui rend l'installation plus rapide et plus pratique.</p><p>Vous devriez envisager d'utiliser <code>semantic_text</code> si vous appréciez la <strong>simplicité et l'abstraction</strong>, car il <strong>élimine la complexité de la configuration manuelle des mappings, de la génération d'embedding et des pipelines d'ingestion</strong>. Il suffit de sélectionner le modèle d'inférence et Elasticsearch s'occupe du reste.</p><p>Parmi les principaux avantages, citons la <strong>génération automatique de l'intégration,</strong> effectuée à la fois pendant l'indexation et l'interrogation, et le <strong>mappage prêt à l'emploi</strong>, qui est préconfiguré pour prendre en charge le modèle d'inférence sélectionné.</p><p>En outre, le champ offre une <strong>prise en charge native du découpage automatique des textes longs (text chunking</strong>), ce qui permet de diviser les textes volumineux en passages plus petits, chacun ayant sa propre intégration, ce qui améliore la précision de la recherche. La productivité s'en trouve grandement améliorée, en particulier pour les équipes qui souhaitent fournir rapidement de la valeur ajoutée sans avoir à s'occuper de l'ingénierie sous-jacente de la recherche sémantique.</p><p>Cependant, bien que <code>semantic_text</code> offre rapidité et simplicité, cette approche présente certaines limites. Il permet d'utiliser des modèles standard du marché, pour autant qu'ils soient disponibles en tant que points de terminaison d'inférence dans Elasticsearch. Cependant, <strong>il ne prend pas en charge les encastrements générés de l'extérieur</strong>, comme c'est le cas avec le champ <code>dense_vector</code>.</p><p>Si vous avez besoin de plus de contrôle sur la manière dont les vecteurs sont générés, si vous souhaitez utiliser vos propres incorporations ou si vous devez combiner plusieurs champs pour des stratégies avancées, les champs <code>dense_vector</code> et <code>sparse_vector</code> offrent la flexibilité nécessaire pour des scénarios plus personnalisés ou spécifiques à un domaine.</p><h3>Comment utiliser la requête pour le type de texte sémantique ?</h3><p>Avant <strong><code>semantic_text</code></strong>, il était nécessaire d'utiliser une requête différente selon le type d'intégration (dense ou éparse). Une requête <code>sparse_vector</code> a été utilisée pour les champs peu denses, tandis que les champs <code>dense_vector</code> ont nécessité des requêtes KNN.</p><p>Avec le type de texte sémantique, la recherche est effectuée à l'aide de la <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-semantic-query">requête sémantique</a>, qui génère automatiquement le vecteur de la requête et le compare avec les enchâssements des documents indexés. Le type <strong><code>semantic_text</code></strong> vous permet de définir un point final d'inférence pour intégrer la requête, mais si aucun n'est spécifié, le même point final que celui utilisé lors de l'indexation sera appliqué à la requête.</p>{
  "query": {
    "semantic": {
      "field": "semantic_text_field",
      "query": "search text"
    }
  }
}<p>Pour en savoir plus, je vous propose de lire l'article <a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Elasticsearch new semantic_text mapping : Simplifier la recherche sémantique</a>.</p><h2>Conclusion</h2><p>Lors du choix de la méthode de mappage des embeddings dans Elasticsearch, il est essentiel de comprendre comment vous souhaitez générer les vecteurs et quel est le niveau de contrôle dont vous avez besoin sur ceux-ci. Si vous recherchez la simplicité, le champ de texte sémantique permet une recherche sémantique automatique et évolutive, ce qui le rend idéal pour de nombreux cas d'utilisation initiaux. Lorsqu'un contrôle plus poussé, des performances plus fines ou une intégration avec des modèles personnalisés sont nécessaires, les champs de vecteurs denses et de vecteurs clairsemés offrent la flexibilité nécessaire.</p><p>Le type de champ idéal dépend de votre cas d'utilisation, de l'infrastructure disponible et de la maturité de votre pile d'apprentissage automatique. Plus important encore, Elastic offre les outils nécessaires pour construire des systèmes de recherche modernes et hautement adaptables.</p><h2>Références</h2><ul><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html">Type de champ texte sémantique</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/sparse-vector.html">Type de champ vectoriel épars</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html">Type de champ vectoriel dense</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html">Requête sémantique</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-sparse-vector-query.html">Requête sur les vecteurs épars</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">Recherche kNN</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Nouveau mappage semantic_text d'Elasticsearch : Simplifier la recherche sémantique</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/sparse-vector-embedding">Comprendre les encastrements de vecteurs épars à l'aide de modèles ML entraînés</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/mapping-embeddings-to-elasticsearch-field-types</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/mapping-embeddings-to-elasticsearch-field-types</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[Mappings]]></category>
    <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt72cd3c2601b22886/6a17083b0c4857259901a9dc/f98fdff837db55b466780c0bae672aa6f6c3a966-1200x628.png" length="0" type="image/png"/>
    <pubDate>Tue, 13 May 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Comment mettre en œuvre une meilleure quantification binaire (BBQ) dans votre cas d'utilisation ?]]></title>
    <description><![CDATA[Expliquez pourquoi vous devriez mettre en œuvre une meilleure quantification binaire (BBQ) dans votre cas d'utilisation et comment le faire.]]></description>
    <content:encoded><![CDATA[<p>La recherche vectorielle constitue la base de la mise en œuvre d'une recherche sémantique pour le texte ou d'une recherche de similarité pour les images, les vidéos ou les fichiers audio. Dans le cas de la recherche vectorielle, les vecteurs sont des représentations mathématiques de données qui peuvent être énormes et parfois lentes. La meilleure quantification binaire (ci-après dénommée BBQ) est une méthode de compression pour les vecteurs. Il vous permet de trouver les bonnes correspondances tout en réduisant les vecteurs pour les rendre plus rapides à rechercher et à traiter. Cet article traite de BBQ et de rescore_vector, un champ disponible uniquement pour les indices quantifiés et qui permet de rescorer automatiquement les vecteurs.</p><p>Toutes les requêtes complètes et les résultats mentionnés dans cet article peuvent être trouvés dans notre <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/how-and-why-bbq">dépôt de code Elasticsearch Labs</a>.</p><h2>Pourquoi mettre en œuvre une meilleure quantification binaire (BBQ) dans votre cas d'utilisation ?</h2>Remarque : pour une compréhension approfondie du fonctionnement mathématique du BBQ, veuillez consulter la <a href="https://www.elastic.co/fr/search-labs/blog/bbq-implementation-into-use-case#further-learning">section "Apprentissage complémentaire"</a> ci-dessous. Dans le cadre de ce blog, l'accent est mis sur la mise en œuvre.<p>Bien que les mathématiques soient intrigantes, elles sont essentielles si vous voulez comprendre pourquoi vos recherches vectorielles restent précises. En fin de compte, il s'agit d'une question de compression, car il s'avère qu'avec les algorithmes actuels de recherche vectorielle, vous êtes limité par la vitesse de lecture des données. Par conséquent, si vous pouvez placer toutes ces données dans la mémoire, vous bénéficiez d'un gain de vitesse significatif par rapport à la lecture à partir du stockage<a href="https://sre.google/static/pdf/rule-of-thumb-latency-numbers-letter.pdf">(la mémoire est environ 200 fois plus rapide que les disques SSD</a>).</p><p>Il y a quelques points à garder à l'esprit :</p><ul><li><p>Les indices basés sur les graphes, tels que <a href="https://arxiv.org/pdf/1603.09320">HNSW</a> (Hierarchical Navigable Small World), sont les plus rapides pour la recherche vectorielle.</p><ul><li><p>HNSW : un algorithme de recherche approximative du plus proche voisin qui construit une structure de graphe multicouche pour permettre des recherches de similarité efficaces en haute dimension.</p></li></ul></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt760bd95c206bfa8f/6a17e2ad505ac393f7ad8a95/590f3b3c72a76023a38a0436cd9ff90a9f80e936-1964x1262.png" alt="HNSW : un algorithme de recherche approximative du plus proche voisin qui construit une structure de graphe multicouche pour permettre des recherches de similarité efficaces en haute dimension." /><ul><li><p>La vitesse de HNSW est fondamentalement limitée par la vitesse de lecture des données à partir de la mémoire ou, dans le pire des cas, à partir du stockage.</p><ul><li><p>L'idéal est de pouvoir charger tous les vecteurs stockés dans la mémoire.</p></li></ul></li><li><p>Les modèles d'intégration produisent généralement des vecteurs avec une précision float32, soit 4 octets par nombre à virgule flottante.</p></li><li><p>Enfin, selon le nombre de vecteurs et/ou de dimensions que vous avez, vous pouvez très rapidement manquer de mémoire pour conserver tous vos vecteurs.</p></li></ul><p>Si l'on prend cela pour acquis, on constate qu'un problème se pose rapidement dès que l'on commence à ingérer des millions, voire des milliards de vecteurs, chacun ayant potentiellement des centaines, voire des milliers de dimensions. La section intitulée "<a href="https://www.elastic.co/fr/search-labs/blog/bbq-implementation-into-use-case#approximate-numbers-on-the-compression-ratios">Chiffres approximatifs sur les taux de compression</a>" fournit quelques chiffres approximatifs.</p><h2>De quoi avez-vous besoin pour commencer ?</h2><p>Pour commencer, vous aurez besoin des éléments suivants :</p><ul><li><p>Si vous utilisez Elastic Cloud ou on-prem, vous aurez besoin d'une version d'Elasticsearch supérieure à 8.18. Alors que BBQ a été introduit dans la version 8.16, dans cet article, vous utiliserez <code>vector_rescore</code>, qui a été introduit dans la version 8.18.</p></li><li><p>En outre, vous devrez également vous assurer qu'il existe un <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/8.18/ml-settings.html">nœud d'apprentissage automatique (ML)</a> dans votre cluster. (Remarque : un nœud ML doté d'un minimum de 4 Go est nécessaire pour charger le modèle, mais vous aurez probablement besoin de nœuds beaucoup plus grands pour des charges de travail de production complètes).</p></li><li><p>Si vous utilisez Serverless, vous devrez sélectionner une instance optimisée pour les vecteurs.</p></li><li><p>Vous aurez également besoin d'une connaissance de base des bases de données vectorielles. Si vous n'êtes pas encore familiarisé avec les concepts de recherche vectorielle dans Elastic, vous pouvez consulter les ressources suivantes :</p><ul><li><p><a href="https://www.elastic.co/fr/search-labs/blog/elastic-vector-database-practical-example">Naviguer dans une base de données vectorielle élastique</a></p></li><li><p><a href="https://www.elastic.co/fr/blog/retrieval-augmented-generation-explained">Les grandes idées derrière la génération augmentée de recherche</a></p></li></ul></li></ul><h2>Meilleure implémentation de la quantification binaire (BBQ)</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt18df00df95ff2ca7/6a17e2af414c6411989450df/4d388078495566f0527e931e0c2e38facdce83c6-1503x748.png" alt="Mise en œuvre d'Elasticsearch bbq." /><p>Pour que ce blog reste simple, vous utiliserez les fonctions intégrées lorsqu'elles sont disponibles. Dans ce cas, vous disposez du modèle d'intégration vectorielle <a href="https://www.elastic.co/fr/guide/en/machine-learning/8.17/ml-nlp-e5.html"><code>.multilingual-e5-small</code></a> qui s'exécutera directement dans Elasticsearch sur un nœud d'apprentissage automatique. Notez que vous pouvez remplacer le modèle <code>text_embedding</code> par l'intégrateur de votre choix<a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/8.18/infer-service-openai.html">(OpenAI</a>, <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/8.18/infer-service-google-ai-studio.html">Google AI Studio</a>, <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/8.18/infer-service-cohere.html">Cohere</a> et bien d'autres). Si le modèle que vous préférez n'est pas encore intégré, vous pouvez également <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/8.18/bring-your-own-vectors.html">apporter vos propres encastrements vectoriels denses</a>).</p><p>Tout d'abord, vous devrez créer un point final d'inférence pour générer des vecteurs pour un morceau de texte donné. Vous exécuterez toutes ces commandes à partir de la console Kibana <a href="https://www.elastic.co/fr/guide/en/kibana/8.18/console-kibana.html">Dev</a> Tools. Cette commande permet de télécharger le site <code>.multilingual-e5-small</code>. S'il n'existe pas encore, il configurera votre point d'accès ; cette opération peut prendre une minute. Vous pouvez voir le résultat attendu dans le fichier <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/01-create-an-inference-endpoint-output.json">01-create-an-inference-endpoint-output.json</a> dans le dossier Outputs. </p>PUT _inference/text_embedding/my_e5_model
{
  "service": "elasticsearch",
  "service_settings": {
    "num_threads": 1,
    "model_id": ".multilingual-e5-small",
    "adaptive_allocations": {
      "enabled": true,
      "min_number_of_allocations": 1
    }
  }
}<p>Une fois qu'il est revenu, votre modèle est configuré et vous pouvez tester que le modèle fonctionne comme prévu à l'aide de la commande suivante. Vous pouvez voir le résultat attendu dans le fichier <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/02-embed-text-output.json">02-embed-text-output.json</a> dans le dossier Outputs.</p>POST _inference/text_embedding/my_e5_model
{
  "input": "my awesome piece of text"
}<p>Si vous rencontrez des problèmes liés au fait que votre modèle formé n'est affecté à aucun nœud, il se peut que vous deviez démarrer votre modèle manuellement.</p>POST _ml/trained_models/.multilingual-e5-small/deployment/_start<p>Créons maintenant un nouveau mappage avec deux propriétés, un champ de texte standard (<code>my_field</code>) et un champ vectoriel dense (<code>my_vector</code>) avec 384 dimensions pour correspondre à la sortie du modèle d'intégration. Vous pouvez également passer outre l'adresse <code>index_options.type to bbq_hnsw</code>. Vous pouvez voir le résultat attendu dans le fichier <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/03-create-byte-qauntized-index-output.json">03-create-byte-qauntized-index-output.json</a> dans le dossier Outputs.</p>PUT bbq-my-byte-quantized-index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text"
      },
      "my_vector": {
        "type": "dense_vector",
        "dims": 384,
        "index_options": {
          "type": "bbq_hnsw"
        }
      }
    }
  }
}<p>Pour s'assurer qu'Elasticsearch génère vos vecteurs, vous pouvez utiliser un <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/8.18/ingest.html">pipeline d'ingestion</a>. Ce pipeline nécessite trois éléments : le point final (<code>model_id</code>), le site <code>input_field</code> pour lequel vous souhaitez créer des vecteurs et le site <code>output_field</code> dans lequel vous souhaitez stocker ces vecteurs. La première commande ci-dessous crée un pipeline d'ingestion d'inférence, qui utilise le <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/current/inference-apis.html">service d'inférence </a>sous le capot, et la seconde teste le bon fonctionnement du pipeline. Vous pouvez voir le résultat attendu dans le fichier <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/04-create-and-simulate-ingest-pipeline-output.json">04-create-and-simulate-ingest-pipeline-output.json</a> dans le dossier Outputs. </p>PUT _ingest/pipeline/my_inference_pipeline
{
  "processors": [
    {
      "inference": {
        "model_id": "my_e5_model",
        "input_output": [
          {
            "input_field": "my_field",
            "output_field": "my_vector"
          }
        ]
      }
    }
  ]
}

POST _ingest/pipeline/my_inference_pipeline/_simulate
{
  "docs": [
    {
      "_source": {
        "my_field": "my awesome text field"
      }
    }
  ]
}<p>Vous êtes maintenant prêt à ajouter des documents à l'aide des deux premières commandes ci-dessous et à tester le fonctionnement de vos recherches à l'aide de la troisième commande. Vous pouvez vérifier le résultat attendu dans le fichier <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/05-bbq-index-output.json">05-bbq-index-output.json</a> dans le dossier Outputs. </p>PUT bbq-my-byte-quantized-index/_doc/1?pipeline=my_inference_pipeline
{
    "my_field": "my awesome text field"
}

PUT bbq-my-byte-quantized-index/_doc/2?pipeline=my_inference_pipeline
{
    "my_field": "some other sentence"
}

GET bbq-my-byte-quantized-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}<p>Comme nous l'avons recommandé dans <a href="https://www.elastic.co/fr/search-labs/blog/better-binary-quantization-lucene-elasticsearch#lucene-benchmarking">cet article</a>, le recalage et le suréchantillonnage sont conseillés lorsque vous passez à des quantités de données non triviales, car ils permettent de maintenir une précision de rappel élevée tout en bénéficiant des avantages de la compression. À partir de la version 8.18 d'Elasticsearch, vous pouvez le faire de cette façon en utilisant <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/8.18/knn-search.html#dense-vector-knn-search-rescoring">rescore_vector.</a> Le résultat attendu se trouve dans le fichier <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/06-bbq-search-8-18-output.json">06-bbq-search-8-18-output.json</a> dans le dossier Outputs.</p>GET bbq-my-byte-quantized-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "rescore_vector": {
              "oversample": 3
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}<p>Comment ces résultats se comparent-ils à ceux que vous obtiendriez avec des données brutes ? Si vous refaites tout ce qui précède mais avec <code>index_options.type: hnsw</code>, vous verrez que les scores sont très comparables. Vous pouvez voir le résultat attendu dans le fichier <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/07-raw-vector-output.json">07-raw-vector-output.json</a> dans le dossier Outputs.</p>PUT my-raw-vector-index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text"
      },
      "my_vector": {
        "type": "dense_vector",
        "dims": 384,
        "index_options": {
          "type": "hnsw"
        }
      }
    }
  }
}

PUT my-raw-vector-index/_doc/1?pipeline=my_inference_pipeline
{
    "my_field": "my awesome text field"
}

PUT my-raw-vector-index/_doc/2?pipeline=my_inference_pipeline
{
    "my_field": "some other sentence"
}

GET my-raw-vector-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}<h2>Chiffres approximatifs sur les taux de compression</h2><p>Les exigences en matière de stockage et de mémoire peuvent rapidement devenir un défi important lorsque l'on travaille avec la recherche vectorielle. La décomposition suivante illustre comment les différentes techniques de quantification réduisent considérablement l'empreinte mémoire des données vectorielles.</p><p>Vecteurs (V)</p><p>Dimensions (D)</p><p>brut (V x D x 4)</p><p>int8 (V x (D x 1 + 4))</p><p>int4 (V x (D x 0,5 + 4))</p><p>bbq (V x (D x 0,125 + 4))</p><p>10,000,000</p><p>384</p><p>14.31GB</p><p>3.61GB</p><p>1.83GB</p><p>0.58GB</p><p>50,000,000</p><p>384</p><p>71.53GB</p><p>18.07GB</p><p>9.13GB</p><p>2.89GB</p><p>100,000,000</p><p>384</p><p>143.05GB</p><p>36.14GB</p><p>18.25GB</p><p>5.77GB</p><h2>Conclusion</h2><p>BBQ est une optimisation que vous pouvez appliquer à vos données vectorielles pour les compresser sans sacrifier la précision. Il convertit les vecteurs en bits, ce qui vous permet d'effectuer des recherches efficaces dans les données et de faire évoluer vos flux de travail d'IA pour accélérer les recherches et optimiser le stockage des données.</p><h2>Poursuite de l'apprentissage</h2><p>Si vous souhaitez en savoir plus sur le barbecue, n'hésitez pas à consulter les ressources suivantes :</p><ul><li><p><a href="https://www.elastic.co/fr/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Quantification binaire (BBQ) dans Lucene et Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/fr/search-labs/blog/bit-vectors-elasticsearch-bbq-vs-pq">Meilleure quantification binaire (BBQ) vs quantification par produit</a></p></li><li><p><a href="https://www.elastic.co/fr/search-labs/blog/optimized-scalar-quantization-elasticsearch">Quantification scalaire optimisée : Une quantification binaire encore meilleure</a></p></li><li><p><a href="https://www.youtube.com/watch?v=04NzMt2Nigc">Meilleure quantification binaire (BBQ) : De l'octet au BBQ, le secret d'une meilleure recherche vectorielle par Ben Trent</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/bbq-implementation-into-use-case</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/bbq-implementation-into-use-case</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[Les bases]]></category>
    <dc:creator><![CDATA[Sachin Frayne,Jessica Garson]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3dd0495b536b2615/6a17e2b0414c6488459450e3/66842055367cdd795532b01c167f2a4b03dc65e3-1200x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 23 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch BBQ vs. OpenSearch FAISS : Comparaison des performances de la recherche vectorielle]]></title>
    <description><![CDATA[Comparaison des performances entre Elasticsearch BBQ et OpenSearch FAISS.]]></description>
    <content:encoded><![CDATA[<p><strong>Recherche vectorielle avec quantification binaire : Elasticsearch avec BBQ est 5 fois plus rapide qu'OpenSearch avec FAISS</strong>. Elastic a reçu des demandes de notre communauté pour clarifier les différences de performance entre Elasticsearch et OpenSearch, en particulier dans le domaine de la recherche sémantique/recherche vectorielle. Nous avons donc réalisé ces tests de performance pour fournir des comparaisons claires et basées sur des données.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta8792b5284e7a553/6a1709b9961e69a084c4cee6/7f4f8d08f7bee188423e4e65f0caefc7e34f0355-1600x681.png" alt="Elasticsearch BBQ vs OpenSearch FAISS - Vitesse &amp; débit Comparaison du rappel" /><h2>Démonstration de quantification binaire</h2><p>Le stockage de vecteurs à haute dimension dans leur forme originale peut nécessiter beaucoup de mémoire. Les techniques de quantification compriment ces vecteurs dans une représentation compacte, ce qui réduit considérablement l'empreinte mémoire. La recherche s'effectue alors dans l'espace compressé, ce qui réduit la complexité des calculs et accélère les recherches, en particulier dans les grands ensembles de données.</p><p>Elastic s'engage à faire de Lucene un moteur vectoriel très performant. Nous avons introduit une <a href="https://www.elastic.co/fr/search-labs/blog/better-binary-quantization-lucene-elasticsearch">meilleure quantification binaire</a> (BBQ) dans Elasticsearch 8.16, en plus de Lucene, et l'avons fait évoluer dans les versions 8.18 et 9.0. BBQ repose sur une nouvelle approche de la <a href="https://www.elastic.co/fr/search-labs/blog/optimized-scalar-quantization-elasticsearch">quantification scalaire</a> qui réduit les dimensions float32 en bits, ce qui permet une réduction de la mémoire de ~95% tout en conservant une qualité de classement élevée.</p><p>OpenSearch, quant à lui, utilise plusieurs moteurs vectoriels : nmslib (aujourd'hui obsolète), Lucene et FAISS. Dans un <a href="https://www.elastic.co/fr/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison">blog précédent</a>, nous avons comparé Elasticsearch et OpenSearch pour la recherche vectorielle. Nous avons utilisé trois ensembles de données différents et testé différentes combinaisons de moteurs et de configurations sur les deux produits.</p><p>Ce blog se concentre sur les algorithmes de quantification binaire actuellement disponibles dans les deux produits. Nous avons testé Elasticsearch avec BBQ et OpenSearch avec la <a href="https://opensearch.org/docs/latest/search-plugins/knn/knn-vector-quantization/#binary-quantization">quantification binaire de FAISS</a> en utilisant la piste Rallye <a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a>.</p><p>L'objectif principal était d'évaluer la performance des deux solutions avec le même niveau de rappel. Que signifie le terme <em>"rappel"</em>? Le rappel est un indicateur qui mesure le nombre de résultats pertinents retrouvés par un système de recherche.</p><p>Dans cette évaluation, recall@k est particulièrement important, <em>k</em> représentant le nombre de premiers résultats pris en compte. Le <strong>rappel@10</strong>, le <strong>rappel@50 et le rappel@100</strong> mesurent donc le nombre de vrais résultats pertinents qui apparaissent respectivement dans les 10, 50 et 100 premiers éléments retrouvés. Le rappel est exprimé sur une échelle de 0 à 1 (ou de 0% à 100% précision). C'est important car nous parlons de KNN approximatif (ANN) et non de KNN exact, où le rappel est toujours de 1 (100%).</p><p>Pour chaque valeur de <em>k</em>, nous avons également spécifié <em>n, </em>qui est le nombre de candidats pris en compte avant d'appliquer le classement final. Cela signifie que pour Rappel@10, Rappel@50 et Rappel@100, le système récupère d'abord <em>n</em> candidats à l'aide de l'algorithme de quantification binaire, puis les classe pour déterminer si les <em>k</em> premiers résultats contiennent les éléments pertinents attendus.</p><p>En contrôlant <em>n</em>, nous pouvons analyser le compromis entre l'efficacité et la précision. Un <em>n</em> plus élevé <strong>augmente</strong> généralement le rappel, car plus de candidats sont disponibles pour le classement, mais il augmente également <strong>la</strong> latence et<strong> diminue le </strong>débit. Inversement, un <em>n</em> plus faible accélère la recherche mais peut réduire la mémorisation si trop peu de candidats pertinents sont inclus dans l'ensemble initial.</p><p>Dans cette comparaison, Elasticsearch a démontré une latence plus faible et un débit plus élevé qu'OpenSearch sur des configurations identiques.</p><h2>Méthodologie</h2><p>La configuration complète, ainsi que les scripts Terraform, les manifestes Kubernetes et la piste Rallye spécifique sont disponibles dans ce <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/tree/bbq">dépôt</a> sous <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/tree/bbq/rally-custom/custom_tracks/elasticsearch/openai_vector_bq"><em>openai_vector_bq</em></a>.</p><p>Comme pour les benchmarks précédents, nous avons utilisé un cluster Kubernetes composé de :</p><ul><li><p>1 pool de nœuds pour Elasticsearch 9.0 avec 3 machines <code>e2-standard-32</code> (128GB RAM et 32 CPUs)</p></li><li><p>1 pool de nœuds pour OpenSearch 2.19 avec 3 machines <code>e2-standard-32</code> (128GB RAM et 32 CPUs)</p></li><li><p>1 pool de nœuds pour Rallye avec 2 machines <code>e2-standard-4</code> (16GB RAM et 4 CPUs)</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt934e988e96a5849f/6a1709bbacf08838f9be9ac1/169bb6033f6eebfd1b177b3446bf916fde4ee5c5-1600x856.png" alt="Configuration de la méthodologie Elasticsearch BBQ vs Opensearch FAISS" /><p>Nous avons mis en place un cluster Elasticsearch version 9.0 et un cluster OpenSearch version 2.19.</p><p>Elasticsearch et OpenSearch ont été testés avec la même configuration : nous avons utilisé <a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a> Rally track avec <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/commit/b97d5d95c22c8cf862f2030964524bdd156a5da3">quelques modifications</a> - qui utilise 2,5 millions de documents de l'<a href="https://huggingface.co/datasets/BeIR/nq">ensemble de données NQ</a> enrichis avec des embeddings générés à l'aide du <a href="https://openai.com/blog/new-and-improved-embedding-model">modèle text-embedding-ada-002</a> d'OpenAI.</p>{
  "source-file": "open_ai_corpus-initial-indexing.json.bz2",
  "document-count": 2580961,
  "compressed-bytes": 32076749416,
  "uncompressed-bytes": 90263571686
}<p>Les résultats portent sur la latence et le débit mesurés à différents niveaux de rappel (rappel@10, rappel@50 et rappel@100) en utilisant 8 clients simultanés pour effectuer des opérations de recherche. Nous avons utilisé un seul arbre et aucune réplique.</p><p>Nous avons exécuté les combinaisons suivantes de k-n-rescore, par exemple 10-2000-2000, ou <em>k:10</em>, <em>n:2000</em> et <em>rescore:2000</em> permet de retrouver les k (10) premiers candidats sur n candidats (2000) en appliquant un rescore sur 2000 résultats (ce qui équivaut à un "facteur de suréchantillon" de 1). Chaque recherche a été exécutée 10 000 fois avec 1000 recherches comme échauffement :</p><p></p><p><u><strong>Rappel@10</strong></u></p><ul><li><p>10-40-40</p></li><li><p>10-50-50</p></li><li><p>10-100-100</p></li><li><p>10-200-200</p></li><li><p>10-500-500</p></li><li><p>10-750-750</p></li><li><p>10-1000-1000</p></li><li><p>10-1500-1500</p></li><li><p>10-2000-2000</p></li></ul><p><u><strong>Rappel@50</strong></u></p><ul><li><p>50-150-150</p></li><li><p>50-200-200</p></li><li><p>50-250-250</p></li><li><p>50-500-500</p></li><li><p>50-750-750</p></li><li><p>50-1000-1000</p></li><li><p>50-1200-1200</p></li><li><p>50-1500-1500</p></li><li><p>50-2000-2000</p></li></ul><p><u><strong>Rappel@100</strong></u></p><ul><li><p>100-200-200</p></li><li><p>100-250-250</p></li><li><p>100-300-300</p></li><li><p>100-500-500</p></li><li><p>100-750-750</p></li><li><p>100-1000-1000</p></li><li><p>100-1200-1200</p></li><li><p>100-1500-1500</p></li><li><p>100-2000-2000</p></li></ul><p>Pour reproduire le benchmark, les manifestes Kubernetes pour rally-elasticsearch et rally-opensearch ont toutes les variables pertinentes externalisées dans un ConfigMap, disponible <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/bbq/k8s/rally-openai_vector-es-bq.yml">ici</a> (ES) et <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/bbq/k8s/rally-openai_vector-os-bq.yml">ici</a> (OS). Le paramètre <em>search_ops</em> peut être personnalisé pour tester n'importe quelle combinaison de k, n et rescore.</p><h3>Configuration d'OpenSearch Rally</h3><p><code>/k8s/rally-openai_vector-os-bq.yml</code></p>apiVersion: v1
kind: ConfigMap
metadata:
  name: rally-params-os
  labels:
    app: rally-opensearch
data:
  user-tags.json: |
    {
      "product": "OpenSearch",
      "product-version": "OpenSearch-2.19.0",
      "product-label": "OpenSearch-2.19-faiss",
      "benchmark-run": "19-feb-recall@100"
    }
  track-params.json: |
    {
      "mapping_type": "vectors-only-mapping-with-docid",
      "standalone_search_clients": 8,
      "standalone_search_iterations": 5000,
      "ann_threshold": 0,
      "vector_mode": "on_disk",
      "compression_level": "32x",
      "vector_method_name": "hnsw",
      "vector_method_engine": "faiss",
      "search_ops": [
        [100, 200, 200],
        [100, 250, 250],
        [100, 300, 300],
        [100, 500, 500],
        [100, 750, 750],
        [100, 1000, 1000],
        [100, 1200, 1200],
        [100, 1500, 1500],
        [100, 2000, 2000]
      ]
    }<h3>Configuration de l'index Opensearch</h3><p>Les variables du ConfigMap sont ensuite utilisées pour la configuration de l'index, certains paramètres restant inchangés. La quantification sur 1 bit dans OpenSearch est configurée en <a href="https://opensearch.org/docs/latest/search-plugins/knn/knn-vector-quantization/#binary-quantization">réglant le niveau de compression sur "32x".</a></p><p><code>index-vectors-only-mapping-with-docid-mapping.json</code></p>{
  "settings": {
    {% if preload_pagecache %}
    "index.store.preload": [
      "vec", "vex", "vem", "veq", "veqm", "veb", "vebm"
    ],
    {% endif %}
    "index.number_of_shards": {{ number_of_shards | default(1) }},
    "index.number_of_replicas": {{ number_of_replicas | default(0) }},
    "index.knn": true,
    "index.knn.advanced.approximate_threshold": {{ ann_threshold | default(15000) }}
  },
  "mappings": {
    "dynamic": false,
    "properties": {
      "docid": {
        "type": "keyword"
      },
      "emb": {
        "type": "knn_vector",
        "dimension": 1536,
        "space_type": "innerproduct",
        "data_type": "float",
        "mode": {{ vector_mode | default("in_memory") | tojson }},
        "compression_level": {{ compression_level | default("32x") | tojson }},
        "method": {
          "name": {{ vector_method_name | default("hnsw") | tojson }},
          "engine": {{ vector_method_engine | default("faiss") | tojson }},
          "parameters": {
            "ef_construction": 100,
            "m": 16
          }
        }
      }
    }
  }
}<h3>Configuration d'Elasticsearch Rally</h3><p><code>/k8s/rally-openai_vector-es-bq.yml</code></p>apiVersion: v1
kind: ConfigMap
metadata:
  name: rally-params-es
  labels:
    app: rally-elasticsearch
data:
  user-tags.json: |
    {
      "product": "Elasticsearch",
      "product-version": "Elasticsearch-9.0.0-ade01164",
      "product-label": "Elasticsearch-9.0-BBQ",
      "benchmark-run": "19-feb-recall@100"
    }
  track-params.json: |
    {
      "mapping_type": "vectors-only-mapping-with-docid",
      "standalone_search_clients": 8,
      "standalone_search_iterations": 5000,
      "vector_index_type": "bbq_hnsw",
      "search_ops": [
        [100, 200, 200],
        [100, 250, 250],
        [100, 300, 300],
        [100, 500, 500],
        [100, 750, 750],
        [100, 1000, 1000],
        [100, 1200, 1200],
        [100, 1500, 1500],
        [100, 2000, 2000]
      ]
    }<h3>Configuration de l'index Elasticsearch</h3><p><code>index-vectors-only-mapping-with-docid-mapping.json</code></p>{
  "settings": {
    {# non-serverless-index-settings-marker-start #}
    {%- if build_flavor != "serverless" or serverless_operator == true -%}
    {% if preload_pagecache %}
    "index.store.preload": [ "vec", "vex", "vem", "veq", "veqm", "veb", "vebm" ],
    {% endif %}
    "index.number_of_shards": {{ number_of_shards | default(1) }},
    "index.number_of_replicas": {{ number_of_replicas | default(0) }}
    {%- endif -%}
    {# non-serverless-index-settings-marker-end #}
  },
  "mappings": {
    "dynamic": false,
    "properties": {
      "docid": {
        "type": "keyword"
      },
      "emb": {
        "type": "dense_vector",
        "element_type": "float",
        "dims": 1536,
        "index": true,
        "similarity": "dot_product",
        "index_options": {
          "type": {{ vector_index_type | default("bbq_hnsw") | tojson }},
          "ef_construction": 100,
          "m": 16
        }
      }
    }
  }
}<h2>Résultats</h2><p>Il y a plusieurs façons d'interpréter les résultats. Pour la latence et le débit, nous avons tracé un graphique simplifié et un graphique détaillé à chaque niveau de rappel. Il est facile de voir les différences si l'on considère que "plus c'est élevé, mieux c'est" pour chaque indicateur. Cependant, le temps de latence est un facteur négatif (plus il est faible, mieux c'est), tandis que le débit est un facteur positif. Pour les graphiques simplifiés, nous avons utilisé <strong>(rappel / latence) * 10000 </strong>(appelé simplement "vitesse") et<strong> rappel * débit</strong>, de sorte que les deux mesures signifient qu'une plus grande vitesse et un plus grand débit sont meilleurs. Allons-y.</p><h3>Rappel @ 10 - simplifié</h3><p>À ce niveau de rappel, Elasticsearch BBQ est jusqu'à <strong>5 fois plus rapide </strong>(3,9 fois plus rapide en moyenne) et a un <strong>débit 3,2 fois plus élevé</strong> en moyenne qu'OpenSearch FAISS.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3de6b3460127a8ae/6a1709bcb339d55975769f6f/d580ad53e8974bd3aa75957c413a0136c4e465c5-1600x681.png" alt="Elasticsearch BBQ est jusqu'à 5 fois plus rapide (3,9 fois plus rapide en moyenne) et a un débit 3,2 fois plus élevé en moyenne qu'OpenSearch FAISS pour la vitesse et le débit Rappel@10" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd47045013cac5e9d/6a1709be0e2e49599a41a088/18edce667fe36ab95033264ef8df6f352dda2425-2044x866.png" alt="Elasticsearch BBQ est jusqu'à 5 fois plus rapide (3,9 fois plus rapide en moyenne) et a un débit 3,2 fois plus élevé en moyenne qu'OpenSearch FAISS." /><h4>Rappel @ 10 - Détaillé</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0ecb02d4a2967dbd/6a1709bf14b270c04fe3c5c6/a7459b87e679f4ad963d0e2f1685499b40f6f050-1600x799.png" alt="Comparaison détaillée du temps de latence recall@10 Elasticsearch BBQ vs Opensearch FAISS" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt182da309ad6cfd39/6a1709c166c4f99077f8bfd8/c6036f55a13377654d296eb3148c7199e1965475-1600x799.png" alt="Comparaison détaillée du taux de rappel@10 entre Elasticsearch BBQ et Opensearch FAISS." /><p></p><p>tâche</p><p>latence.moyenne</p><p>débit.moyen</p><p>avg_recall</p><p>Elasticsearch-9.0-BBQ</p><p>10-100-100</p><p>11.70</p><p>513.58</p><p>0.89</p><p>Elasticsearch-9.0-BBQ</p><p>10-1000-100</p><p>27.33</p><p>250.55</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>10-1500-1500</p><p>35.93</p><p>197.26</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>10-200-200</p><p>13.33</p><p>456.16</p><p>0.92</p><p>Elasticsearch-9.0-BBQ</p><p>10-2000-2000</p><p>44.27</p><p>161.40</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>10-40-40</p><p>10.97</p><p>539.94</p><p>0.84</p><p>Elasticsearch-9.0-BBQ</p><p>10-50-50</p><p>11.00</p><p>535.73</p><p>0.85</p><p>Elasticsearch-9.0-BBQ</p><p>10-500-500</p><p>19.52</p><p>341.45</p><p>0.93</p><p>Elasticsearch-9.0-BBQ</p><p>10-750-750</p><p>22.94</p><p>295.19</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>10-100-100</p><p>35.59</p><p>200.61</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>10-1000-1000</p><p>156.81</p><p>58.30</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-1500-1500</p><p>181.79</p><p>42.97</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-200-200</p><p>47.91</p><p>155.16</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>10-2000-2000</p><p>232.14</p><p>31.84</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-40-40</p><p>27.55</p><p>249.25</p><p>0.92</p><p>OpenSearch-2.19-faiss</p><p>10-50-50</p><p>28.78</p><p>245.14</p><p>0.92</p><p>OpenSearch-2.19-faiss</p><p>10-500-500</p><p>79.44</p><p>97.06</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-750-750</p><p>104.19</p><p>75.49</p><p>0.96</p><h3>Rappel à 50 ans - simplifié</h3><p>À ce niveau de rappel, Elasticsearch BBQ est jusqu <strong>'à 5 fois plus rapide</strong> (4,2 fois plus rapide en moyenne) et a un <strong>débit 3,9 fois plus élevé</strong> en moyennequ'OpenSearch FAISS.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt486683f2c7a1d677/6a1709c2a6c2b995bde796a3/3189ffb330948b35854eeea9ae317d4846c14972-1600x681.png" alt="Comparaison des performances vectorielles de Recal @50 Elasticsearch BBQ vs Opensearch FAISS" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltba830c6741784682/6a1709c4a292990627d00ff7/607383d674dcf0b8f94bfb1a450063f52fcbeb15-2060x876.png" alt="Elasticsearch BBQ est jusqu'à 5 fois plus rapide (4,2 fois plus rapide en moyenne) et a un débit 3,9 fois plus élevé en moyenne qu'OpenSearch FAISS." /><h4>Résultats détaillés - Rappel à 50</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt821775e7f87e5ede/6a1709c6a6c2b90af3e796a7/ebfffe0b776aad31dd03d315cfbf5aa098b41226-1600x789.png" alt="Recall@50 Elasticsearch BBQ et Opensearch FAISS résultats de latence" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4f47784ed1017c15/6a1709c7e8fbce0fbe39fbf5/ae20cf870a65c400a2112bbad62eb56e244f549a-1600x799.png" alt="Recall@50 Elasticsearch BBQ et Opensearch FAISS résultats de débit" /><p></p><p>Tâche</p><p>Latence Moyenne</p><p>Débit moyen</p><p>Rappel moyen</p><p>Elasticsearch-9.0-BBQ</p><p>50-1000-1000</p><p>25.71</p><p>246.44</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-1200-1200</p><p>28.81</p><p>227.85</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-150-150</p><p>13.43</p><p>362.90</p><p>0.90</p><p>Elasticsearch-9.0-BBQ</p><p>50-1500-1500</p><p>33.38</p><p>202.37</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-200-200</p><p>12.99</p><p>406.30</p><p>0.91</p><p>Elasticsearch-9.0-BBQ</p><p>50-2000-2000</p><p>42.63</p><p>163.68</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-250-250</p><p>14.41</p><p>373.21</p><p>0.92</p><p>Elasticsearch-9.0-BBQ</p><p>50-500-500</p><p>17.15</p><p>341.04</p><p>0.93</p><p>Elasticsearch-9.0-BBQ</p><p>50-750-750</p><p>31.25</p><p>248.60</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>50-1000-1000</p><p>125.35</p><p>62.53</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-1200-1200</p><p>143.87</p><p>54.75</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-150-150</p><p>43.64</p><p>130.01</p><p>0.89</p><p>OpenSearch-2.19-faiss</p><p>50-1500-1500</p><p>169.45</p><p>46.35</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-200-200</p><p>48.05</p><p>156.07</p><p>0.91</p><p>OpenSearch-2.19-faiss</p><p>50-2000-2000</p><p>216.73</p><p>36.38</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-250-250</p><p>53.52</p><p>142.44</p><p>0.93</p><p>OpenSearch-2.19-faiss</p><p>50-500-500</p><p>78.98</p><p>97.82</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>50-750-750</p><p>103.20</p><p>75.86</p><p>0.96</p><h3>Rappel à 100</h3><p>À ce niveau de rappel, Elasticsearch BBQ est jusqu <strong>'à 5 fois plus rapide </strong>(en moyenne 4,6 fois plus rapide) et a un <strong>débit 3,9 fois plus élevé </strong>en moyenne qu'OpenSearch FAISS.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt380eb9165343a7b3/6a1709c9acf0882669be9ac5/d3f29db64cbde9956de1fa3ae64a75f15141a2bb-1600x681.png" alt="Rappeler @100 résultats Elasticsearch BBQ VS Opensearch FAISS" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt505ce8aa70898390/6a1709cb1949f73a37e7a9bb/10aff7f8c61fdac895b9ba9c5342baf239ba3ffc-2072x864.png" alt="Comparaison des performances d'Elasticsearch BBQ et d'Opensearch FAISS en termes de latence et de débit" /><h4>Résultats détaillés - Rappel à 100</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb2a73e29940e9d6b/6a1709cccf4f257615b2d138/8790fdf9512b850447f6875fb69969f6f1d4da5f-1600x799.png" alt="Résultats détaillés de la latence - Rappel @ 100 Elasticsearch BBQ vs Opensearch FAISS" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte2c91ca21da03d96/6a1709ce0c4857d3fc01aa39/d47032fac6c288cd4eedd9f25001e417b2fa9d65-1600x787.png" alt="Résultats détaillés du débit - Rappel @ 100 Elasticsearch BBQ vs Opensearch FAISS." /><p></p><p>tâche</p><p>latence.moyenne</p><p>débit.moyen</p><p>avg_recall</p><p>Elasticsearch-9.0-BBQ</p><p>100-1000-1000</p><p>27.82</p><p>243.22</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-1200-1200</p><p>31.14</p><p>224.04</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-1500-1500</p><p>35.98</p><p>193.99</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-200-200</p><p>14.18</p><p>403.86</p><p>0.88</p><p>Elasticsearch-9.0-BBQ</p><p>100-2000-2000</p><p>45.36</p><p>159.88</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-250-250</p><p>14.77</p><p>433.06</p><p>0.90</p><p>Elasticsearch-9.0-BBQ</p><p>100-300-300</p><p>14.61</p><p>375.54</p><p>0.91</p><p>Elasticsearch-9.0-BBQ</p><p>100-500-500</p><p>18.88</p><p>340.37</p><p>0.93</p><p>Elasticsearch-9.0-BBQ</p><p>100-750-750</p><p>23.59</p><p>285.79</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>100-1000-1000</p><p>142.90</p><p>58.48</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>100-1200-1200</p><p>153.03</p><p>51.04</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>100-1500-1500</p><p>181.79</p><p>43.20</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>100-200-200</p><p>50.94</p><p>131.62</p><p>0.83</p><p>OpenSearch-2.19-faiss</p><p>100-2000-2000</p><p>232.53</p><p>33.67</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>100-250-250</p><p>57.08</p><p>131.23</p><p>0.87</p><p>OpenSearch-2.19-faiss</p><p>100-300-300</p><p>62.76</p><p>120.10</p><p>0.89</p><p>OpenSearch-2.19-faiss</p><p>100-500-500</p><p>84.36</p><p>91.54</p><p>0.93</p><p>OpenSearch-2.19-faiss</p><p>100-750-750</p><p>111.33</p><p>69.95</p><p>0.94</p><h2>Améliorations apportées au barbecue</h2><p>BBQ a beaucoup évolué depuis sa première version. Pour Elasticsearch 8.16, à des fins de comparaison, nous avons inclus un benchmark de la version 8.16 avec le benchmark actuel, et nous pouvons voir comment le rappel et la latence se sont améliorés depuis.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f1f7ae1bb425a3a/6a1709cf67045bde8e45c192/45a0acfe5985bff28ccada76ec4eca190fe65f72-1600x799.png" alt="Améliorations de la latence et du rappel d'Elasticsearch 9.0 BBQ comparées à Elasticsearch 8.16 BBQ" /><p>Dans Elasticsearch 8.18 et 9.0, nous avons réécrit l'algorithme de base pour quantifier les vecteurs. Ainsi, si BBQ 8.16 était bien, les versions les plus récentes sont encore meilleures. Pour en savoir plus, <a href="https://www.elastic.co/fr/search-labs/blog/optimized-scalar-quantization-elasticsearch">cliquez ici</a> et <a href="https://www.elastic.co/fr/search-labs/blog/scalar-quantization-optimization">ici.</a> En bref, chaque vecteur est quantifié individuellement au moyen de quantiles scalaires optimisés. Ainsi, les utilisateurs bénéficient d'une plus grande précision dans la recherche vectorielle sans compromettre les performances, ce qui rend la recherche vectorielle d'Elasticsearch encore plus puissante.</p><h2>Conclusion</h2><p>Dans cette comparaison de performances entre Elasticsearch BBQ et OpenSearch FAISS, Elasticsearch surpasse de manière significative OpenSearch pour la recherche vectorielle, atteignant des vitesses de requête jusqu'à 5 fois plus rapides et un débit 3,9 fois plus élevé en moyenne pour différents niveaux de rappel.</p><p>Les principales conclusions sont les suivantes :</p><ul><li><p><strong>Recall@10</strong>: Elasticsearch BBQ est jusqu'à 5 fois plus rapide (3,9 fois plus rapide en moyenne) et a un débit 3,2 fois plus élevé en moyenne par rapport à OpenSearch FAISS.</p></li><li><p><strong>Recall@50</strong>: Elasticsearch BBQ est jusqu'à 5 fois plus rapide (4,2 fois plus rapide en moyenne) et a un débit 3,9 fois plus élevé en moyenne par rapport à OpenSearch FAISS.</p></li><li><p><strong>Recall@100</strong>: Elasticsearch BBQ est jusqu'à 5 fois plus rapide (4,6 fois plus rapide en moyenne) et a un débit 3,9 fois plus élevé en moyenne par rapport à OpenSearch FAISS.</p></li></ul><p>Ces résultats mettent en évidence les avantages d'Elasticsearch BBQ en termes d'efficacité et de performances, en particulier dans les scénarios de recherche vectorielle à haute dimension. La technique BBQ (Better Binary Quantization), introduite dans Elasticsearch 8.16, permet une réduction substantielle de la mémoire (~95%) tout en maintenant une qualité de classement élevée, ce qui en fait un choix supérieur pour les applications de recherche vectorielle à grande échelle.</p><p>Chez Elastic, nous innovons sans cesse pour améliorer Apache Lucene et Elasticsearch afin de fournir la meilleure base de données vectorielle pour les cas d'utilisation de recherche et d'extraction, y compris RAG (Retrieval Augmented Generation). Nos <a href="https://www.elastic.co/fr/search-labs/blog/optimized-scalar-quantization-elasticsearch">récentes avancées</a> ont considérablement augmenté les performances, rendant la recherche vectorielle plus rapide et plus efficace en termes d'espace qu'auparavant, en s'appuyant sur les gains de Lucene 10. Ce blog est une autre illustration de cette innovation.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-bbq-vs-opensearch-faiss</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-bbq-vs-opensearch-faiss</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Ugo Sangiorgi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd76ada1eebfe05cd/6a1709d1a29299ed29d00ffb/796de4829e29566f1f3efa2482f5c3e54b31b1d6-1536x1024.png" length="0" type="image/png"/>
    <pubDate>Tue, 15 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Base de données vectorielles Elasticsearch pour un ancrage natif dans la plate-forme Vertex AI de Google Cloud]]></title>
    <description><![CDATA[Découvrez comment Elasticsearch, le premier moteur d'ancrage natif tiers pour Vertex AI de Google Cloud, vous permet de créer des expériences GenAI personnalisées en ancrant les modèles Gemini dans les données de l'entreprise.]]></description>
    <content:encoded><![CDATA[<p>Elastic est ravi d'annoncer que la base de données vectorielle Elasticsearch est désormais intégrée à la plateforme Vertex AI de Google Cloud en tant que moteur de recherche d'informations pris en charge en mode natif, ce qui permet aux utilisateurs d'exploiter les forces multimodales des modèles Gemini de Google avec les capacités de recherche sémantique et hybride d'Elasticsearch, alimentées par l'IA.</p><p>Les développeurs peuvent désormais créer leurs applications RAG dans le cadre d'un parcours unifié, en fondant leurs expériences de chat sur leurs données privées d'une manière flexible et à code bas. Que vous construisiez des agents d'IA pour vos clients et vos employés internes ou que vous exploitiez la génération de LLM dans votre logiciel, la plateforme Vertex AI met la pertinence d'Elasticsearch à votre portée avec une configuration minimale. Cette intégration permet une adoption plus facile et plus rapide des modèles Gemini dans les cas d'utilisation en production, faisant passer la GenAI des PoC aux scénarios de la vie réelle.</p><p>Dans ce blog, nous vous guiderons dans l'intégration d'Elasticsearch avec la plateforme Vertex AI de Google Cloud pour un ancrage transparent des données et la création d'applications GenAI entièrement personnalisables. Découvrons comment.</p><h2>Les modèles Vertex AI et Gemini de Google Cloud sont ancrés dans vos données grâce à Elasticsearch</h2><p>Les utilisateurs qui tirent parti des services et outils Vertex AI pour créer des applications GenAI peuvent désormais accéder à la nouvelle option "Grounding" qui leur permet d'intégrer automatiquement leurs données privées dans leurs interactions conversationnelles. Elasticsearch fait désormais partie de cette fonctionnalité et peut être utilisé via les deux :</p><ul><li><p><a href="https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference">APIs</a> Vertex AI LLM, qui enrichissent directement les modèles Gemini de Google au moment de la génération (de préférence) ;</p></li><li><p><a href="https://cloud.google.com/generative-ai-app-builder/docs/grounded-gen">Grounded Generation API</a>, utilisée plutôt dans l'écosystème Vertex AI Agent Builder pour construire des expériences agentiques.</p></li></ul><p>Grâce à cette intégration, Elasticsearch, la <a href="https://www.elastic.co/fr/elasticsearch/vector-database">base de données vectorielle</a> la plus téléchargée et la plus déployée, apportera vos données d'entreprise pertinentes là où elles sont nécessaires dans vos chats internes et en contact avec les clients, ce qui est crucial pour l'adoption réelle de la GenAI dans les processus d'entreprise.</p><p>Les API susmentionnées permettront aux développeurs d'adopter cette nouvelle fonctionnalité dans leur code. Cependant, l'ingénierie et les tests rapides restent des étapes cruciales dans le développement de l'application et servent de terrain de jeu initial. Pour ce faire, Elasticsearch est conçu pour être facilement évalué par les utilisateurs dans l'outil de console Vertex AI Studio.</p><p>Il suffit de quelques étapes simples pour configurer les points de terminaison Elastic avec les paramètres souhaités (index à rechercher, nombre de documents à récupérer et modèle de recherche souhaité) dans l'onglet "Customize Grounding" de l'interface utilisateur, comme indiqué ci-dessous (notez que pour que cela fonctionne, vous devez saisir la clé API avec le mot "ApiKey" dans l'interface utilisateur et les exemples de code ci-dessous). Vous êtes maintenant prêt à générer vos propres connaissances !</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d313968acca1fea/6a17e624b1e113278079f250/69b3d979d18fa90742d9397f57975c586edf5d9f-1003x710.gif" alt="Les modèles Vertex AI et Gemini de Google Cloud s'appuient sur vos données avec Elasticsearch" /><h2>Des applications GenAI prêtes à la production en toute simplicité</h2><p>Elastic et Google Cloud s'efforcent d'offrir aux développeurs des expériences complètes et agréables. La connexion à Elastic en mode natif dans les API LLM et Grounding Generation réduit la complexité et les frais généraux lors de la création d'applications GAI sur Vertex AI, en évitant les API supplémentaires inutiles et l'orchestration des données tout en s'appuyant sur un seul appel unifié.</p><p>Voyons comment cela fonctionne dans les deux cas.</p><p>Le premier exemple est exécuté avec l'API LLM :</p>curl -X POST \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \https://us-central1-aiplatform.googleapis.com/v1beta1/projects/&lt;PROJECT_ID&gt;/locations/us-central1/publishers/google/models/gemini-2.0-flash-001:generateContent \
  -d '
{
  "contents": [
    {
      "role": "user",
      "parts": [
        {
          "text": "What's my company car policy?"
        }
      ]
    }
  ],
  "tools": [{
    "retrieval": {
      "externalApi": {
        "api_spec": "ELASTIC_SEARCH",
    "endpoint": "https://&lt;my-elastic-cluster&gt;.gcp.elastic-cloud.com:9243",
    "apiAuth": {
      "apiKeyConfig": {
            "apiKeyString": "ApiKey &lt;API_KEY&gt;"
      }
    },
    "elasticSearchParams": {
      "index": "&lt;my-index&gt;",
      "searchTemplate": "&lt;my-search-template&gt;"
    }
      }
    }
  }]
}<p>Dans l'exemple ci-dessus, avec le champ <code>retrieval</code> de l'API demandant la génération de contenu à Gemini 2.0 Flash, nous pouvons définir contextuellement un moteur de recherche pour la demande. Définir <code>api_spec</code> sur "ELASTIC_SEARCH" permet d'utiliser des paramètres de configuration supplémentaires tels que la clé API et le point de terminaison du cluster (nécessaires pour acheminer une requête vers votre cluster Elastic), l'index à partir duquel récupérer les données et le modèle de recherche à utiliser pour votre logique de recherche.</p><p>De même, le même résultat peut être obtenu avec l'API "Grounding Generation", en définissant le paramètre <code>groundingSpec</code>:</p>curl -X POST -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Content-Type: application/json" https://us-discoveryengine.googleapis.com/v1alpha/projects/&lt;PROJECT_ID&gt;/locations/global:generateGroundedContent -d '
{
  "contents": [{
    "role": "user",
    "parts": [{
      "text": "What do I need to patch a hole in my drywall?"
    }]
  }],
  "groundingSpec": {
    "groundingSources": [{
      "elasticSource": {
        "endpoint": "https://&lt;my-elastic-cluster&gt;.gcp.elastic-cloud.com:9243",
        "index": "&lt;my-index&gt;",
        "searchTemplate": "&lt;my-search-template",
        "apiKey": "projects/&lt;PROJECT_ID&gt;/secrets/api-key/versions/latest"
      }
    }]
  }
}
'<p>Dans les deux cas, la réponse contiendra les documents privés les plus pertinents trouvés dans Elasticsearch - et les sources de données connectées connexes - pour répondre à votre requête.</p><p>La simplicité ne doit cependant pas être confondue avec un manque de personnalisation pour répondre à vos besoins spécifiques et à vos cas d'utilisation. C'est dans cet esprit que nous l'avons conçu pour vous permettre d'adapter parfaitement la configuration de la recherche à votre scénario.</p><h2>Une recherche entièrement personnalisable au bout des doigts : modèles de recherche</h2><p>Pour personnaliser au maximum votre scénario de recherche, nous avons construit, en collaboration avec Google Cloud, l'expérience au-dessus de nos <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/current/search-template.html">modèles de recherche</a> bien connus. Les modèles de recherche Elasticsearch sont un excellent outil pour créer des requêtes de recherche dynamiques, réutilisables et faciles à maintenir. Ils permettent de prédéfinir et de réutiliser les structures de requête. Ils sont particulièrement utiles lors de l'exécution de requêtes similaires avec des paramètres différents, car ils permettent d'économiser du temps de développement et de réduire le risque d'erreurs. Les modèles peuvent inclure des espaces réservés pour les variables, ce qui rend les requêtes dynamiques et adaptables aux différents besoins de recherche.</p><p>Lorsque vous utilisez les API de Vertex AI et Elasticsearch pour l'ancrage, vous devez faire référence à un modèle de recherche souhaité - comme indiqué dans les extraits de code ci-dessus - où la logique de recherche est mise en œuvre et transmise à Elasticsearch. Les utilisateurs d'Elastic Power peuvent gérer, configurer et mettre à jour les approches de recherche de manière asynchrone et les adapter aux indices, modèles et données spécifiques de manière totalement transparente pour les utilisateurs de Vertex AI, les développeurs d'applications web ou les ingénieurs en intelligence artificielle, qui n'ont qu'à spécifier le nom du modèle dans l'API de base.</p><p>Cette conception permet une personnalisation complète, mettant à votre disposition les fonctionnalités de recherche étendues d'Elasticsearch dans un environnement Google Cloud AI, tout en garantissant la modularité, la transparence et la facilité d'utilisation pour les différents développeurs, même ceux qui ne sont pas familiers avec Elastic.</p><p>Lorsque vous avez besoin d'une recherche BM25, d'une recherche sémantique ou d'une approche hybride entre les deux (avez-vous déjà exploré les <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/current/retrievers-overview.html">récupérateurs</a>? (techniques de recherche composables en un seul appel à l'API de recherche), vous pouvez définir votre logique personnalisée dans un modèle de recherche, que Vertex AI peut automatiquement exploiter.</p><p>Cela s'applique également aux modèles d'intégration et de classement que vous choisissez pour gérer les vecteurs et les résultats. En fonction de votre cas d'utilisation, vous pouvez héberger des modèles sur les nœuds ML d'Elastic, utiliser un point de terminaison de service tiers via l'API d'inférence ou exécuter votre modèle local sur site. Cela est possible grâce à un modèle de recherche, dont nous verrons le fonctionnement dans la section suivante.</p><h2>Commencez par des modèles de référence, puis créez votre propre modèle.</h2><p>Pour vous aider à démarrer rapidement, nous avons fourni un ensemble d'exemples de modèles de recherche compatibles à utiliser comme référence initiale ; vous pouvez ensuite les modifier et construire vos modèles personnalisés :</p><ul><li><p>Recherche sémantique avec le modèle ELSER (vecteurs épars et regroupement)</p></li><li><p>Recherche sémantique avec le modèle multilingue e5 (vecteurs denses et regroupement)</p></li><li><p>Recherche hybride avec le modèle d'insertion de texte de Vertex AI</p></li></ul><p>Vous pouvez les trouver dans ce <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/Cloud-Vertex-AI/search-templates">repo GitHub</a>.</p><p>Prenons un exemple : la création d'embeddings avec les API Vertex AI de Google Cloud sur un catalogue de produits. Tout d'abord, nous devons créer le modèle de recherche dans Elasticsearch comme indiqué ci-dessous :</p>PUT _scripts/google-template-knn
{
  "script": {
    "lang": "mustache",
    "source": {
      "_source": {
        "excludes": [ "title_embedding", "description_embedding", "images" ]
      },
        "size": "{{num_hits}}",
          "knn" : [
          { 
            "field": "description_embedding",
            "k": 5,
            "num_candidates": 10,
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "googlevertexai_embeddings_004",
                "model_text": "{{query}}"
              }
            },
            "boost": 0.4
          },
          {
            "field": "title_embedding",
            "k": 5,
            "num_candidates": 10,
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "googlevertexai_embeddings_004",
                "model_text": "{{query}}"
            }
          },
          "boost": 0.6
          }
          ]
    }  
  }
}<p>Dans cet exemple, nous allons exécuter une recherche KNN sur deux champs en une seule recherche : <code>title_embedding</code> - le champ vectoriel contenant le nom du produit - et <code>description_embedding</code> - celui contenant la représentation de sa description.</p><p>Vous pouvez tirer parti de la syntaxe <code>excludes</code> pour éviter de renvoyer des champs inutiles au LLM, ce qui pourrait provoquer du bruit dans son traitement et avoir un impact sur la qualité de la réponse finale. Dans notre exemple, nous avons exclu les champs contenant des vecteurs et des urls d'images.</p><p>Les vecteurs sont créés à la volée au moment de la requête sur l'entrée soumise via un point de terminaison d'inférence de l'API d'intégration de Vertex AI, <code>googlevertexai_embeddings_004</code>, précédemment défini comme suit :</p>PUT /_inference/text_embedding/googlevertexai_embeddings_004
{
    "service": "googlevertexai",
    "service_settings": {
        "service_account_json": "&lt;your_service_account_key&gt;",
        "model_id": "text-embedding-004",
        "location": "us-central1",
        "project_id": "&lt;your_gcp_project&gt;"
    }
}<p>Vous trouverez des informations supplémentaires sur l'utilisation de l'API d'inférence d'Elastic <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/current/inference-apis.html">ici.</a></p><p>Nous sommes maintenant prêts à tester notre modèle de recherche :</p>GET product-catalog-with-embeddings/_search/template
{
  "id": "google-template-knn",
  "params": {
    "query": "What do I need to patch a hole in my drywall?",
    "index_name": "product-catalog-with-embeddings",
    "num_hits": 3
  }
}<p>Les champs <code>params</code> remplaceront les variables que nous avons définies dans les scripts modèles entre doubles crochets. Actuellement, les API Vertex AI LLM et Grounded Generation peuvent envoyer à Elastic les variables d'entrée suivantes :</p><ul><li><p>"query" - la requête de l'utilisateur à rechercher</p></li><li><p>"index_name" - le nom de l'index dans lequel la recherche doit être effectuée</p></li><li><p>"num_hits" - le nombre de documents que nous voulons retrouver dans la sortie finale</p></li></ul><p>Voici un exemple de résultat :</p>{
        "_index": "product-catalog-with-embeddings",
        "_id": "9ZQCm5IBcrGI1ivqV-f_",
        "_score": 0.4925191,
        "_ignored": [
          "description.keyword",
          "images.keyword"
        ],
        "_source": {
          "description": "DAP Eclipse Rapid Wall Repair Patch is a new, revolutionary product solution for repairing drywall damage. No more waiting for spackling to dry or messy sanding. DAP Eclipse allows you to patch drywall damage and paint immediately, allowing you to finish your project faster. This all-in-1, mess free solution not only provides a permanent, long-lasting repair but also superior impact resistance for areas that may see reoccurring impact, such as behind a door.",
          "availability": "InStock",
          "model_id": "googlevertexai_embeddings_004",
          "title": "4 in. Eclipse Wall Repair Patch (2-Pack)",
          "url": "https://www.myDIYwebsite.com/p/DAP-4-in-Eclipse-Wall-Repair-Patch-2-Pack-7079809164/317967195",
          "price": 23.96,
          "product_id": 317967195,
          "currency": "USD",
          "brand": "DAP"
        }<p>La requête ci-dessus est précisément ce que Vertex AI de Google Cloud exécutera en coulisses sur Elasticsearch en se référant au modèle de recherche créé précédemment. Les modèles Gemini utiliseront les documents de sortie pour fonder leur réponse : lorsque vous demandez "De quoi ai-je besoin pour réparer mes cloisons sèches ?", au lieu d'obtenir une suggestion générique, l'agent conversationnel vous fournira des produits spécifiques !</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte88a0c34242df6fa/6a17e6263e03d704504f2c2e/8c8b1374da7e9b2c17df8758acb448eed5b74e2d-1473x913.png" alt="Création d'une invite dans la plateforme Vertex AI de Google" /><h2>Parcours GenAI de bout en bout avec Elastic et Google Cloud</h2><p>Elastic s'associe à Google Cloud pour créer des expériences et des solutions GenAI de bout en bout, prêtes à la production. Comme nous venons de le voir, Elastic est le premier ISV à être intégré directement dans l'interface utilisateur et le SDK de la plateforme Vertex AI, ce qui permet des invites et des agents de modèles Gemini transparents et ancrés dans le sol, utilisant nos fonctions de recherche vectorielle. En outre, Elastic s'intègre à <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/current/infer-service-google-vertex-ai.html">Vertex AI</a> et aux modèles d'intégration, de reclassement et d'achèvement de <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/current/infer-service-google-ai-studio.html">Google AI Studio</a>pour créer et classer des vecteurs sans quitter le paysage Google Cloud, garantissant ainsi les principes de l'<a href="https://cloud.google.com/responsible-ai?hl=en">IA responsable. </a> En soutenant les approches multimodales, nous facilitons conjointement les applications dans divers formats de données.</p><p>Vous pouvez mettre au point, tester et exporter votre code de recherche GenAI via notre <a href="https://www.elastic.co/fr/search-labs/blog/vertex-ai-elasticsearch-playground-fast-rag-apps">terrain de jeu.</a></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a978835a5418eb9/6a17e628fbc5f8abff491a6a/b1fc48dba1713cd01c4d7f3296587d0c4e6e7e0c-862x651.png" alt="Parcours GenAI de bout en bout avec Elastic et Google Cloud " /><p>Mais il ne s'agit pas seulement de créer des applications de recherche : Elastic exploite les modèles Gemini pour renforcer les opérations informatiques, comme dans les <a href="https://www.elastic.co/fr/blog/elastic-google-vertex-ai-integration">fonctions Elastic AI Assistants, Attack Discovery et Automatic Import</a>, réduisant la fatigue quotidienne des analystes de sécurité et des SRE sur des tâches à faible valeur ajoutée, et leur permettant de se concentrer sur l'amélioration de leur activité. Elastic permet également une <a href="https://www.elastic.co/fr/guide/en/integrations/current/gcp_vertexai.html">surveillance complète de l'utilisation de Vertex AI,</a> en suivant les métriques et les journaux, comme les temps de réponse, les jetons et les ressources, afin de garantir des performances optimales. Ensemble, nous gérons le cycle de vie complet de la GenAI, depuis l'ingestion de données et la génération d'embedding jusqu'à l'ancrage avec la recherche hybride, tout en garantissant une observabilité et une sécurité robustes des outils de la GenAI avec des actions alimentées par LLM.</p><h2>Découvrez-en plus et essayez-le !</h2><p>Êtes-vous intéressé(e) par cet essai ? La fonctionnalité est actuellement disponible sur vos projets Google Cloud !</p><p>Si vous ne l'avez pas encore fait, l'une des façons les plus simples de démarrer avec Elastic Search AI Platform et d'explorer nos capacités est d'<a href="https://cloud.elastic.co/registration">essayer gratuitement Elastic Cloud</a> ou de vous abonner via <a href="https://console.cloud.google.com/marketplace/product/elastic-prod/elastic-cloud?pli=1">Google Cloud Marketplace</a>.</p><p><em>La publication et la date de publication de toute fonctionnalité ou fonction décrite dans le présent article restent à la seule discrétion d'Elastic. Toute fonctionnalité ou fonction qui n'est actuellement pas disponible peut ne pas être livrée à temps ou ne pas être livrée du tout. Elastic, Elasticsearch et les marques associées sont des marques commerciales, des logos ou des marques déposées d'Elasticsearch N.V. aux États-Unis et dans d'autres pays. Tous les autres noms de produits et d'entreprises sont des marques commerciales, des logos ou des marques déposées appartenant à leurs propriétaires respectifs.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-google-cloud-vertex-ai-native-grounding</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-google-cloud-vertex-ai-native-grounding</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Valerio Arvizzigno]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt973959b32f2070d6/6a17e62a6317304ec1585a5e/d1f1c8860f1f0b989ad698a882f869de7284ab78-1200x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Accélérer la fusion des graphiques de HNSW]]></title>
    <description><![CDATA[Explorez le travail que nous avons effectué pour réduire la charge de travail liée à la construction de plusieurs graphes HNSW, en particulier en réduisant le coût de la fusion des graphes.]]></description>
    <content:encoded><![CDATA[<p>Dans le passé, <a href="https://www.elastic.co/fr/search-labs/blog/multi-graph-vector-search">nous avons abordé</a> certains des défis posés par la recherche dans plusieurs <a href="https://www.elastic.co/fr/search-labs/blog/hnsw-graph">graphes de HNSW</a> et la manière dont nous avons pu les atténuer. À cette occasion, nous avons fait allusion à d'autres améliorations que nous avions prévues. Ce billet est l'aboutissement de ce travail.</p><p>Vous vous demandez peut-être pourquoi utiliser des graphiques multiples ? Il s'agit d'un effet secondaire d'un choix architectural de Lucene : les segments immuables. Comme pour la plupart des choix architecturaux, il y a des avantages et des inconvénients. Par exemple, nous avons récemment obtenu la certification Serverless Elasticsearch. Dans ce contexte, nous avons tiré des avantages très importants des segments immuables, notamment une réplication efficace de l'index et la possibilité de découpler le calcul de l'index et de la requête et de les faire évoluer indépendamment. Pour la quantification vectorielle, les fusions de segments nous donnent la possibilité de mettre à jour les paramètres pour les adapter aux caractéristiques des données. Dans le même ordre d'idées, nous pensons que la possibilité de mesurer les caractéristiques des données et de revoir les choix d'indexation présente d'autres avantages.</p><p>Dans ce billet, nous discuterons du travail que nous avons effectué pour réduire de manière significative la charge de travail liée à la construction de plusieurs graphes HNSW et, en particulier, pour réduire le coût de la fusion des graphes.</p><h3>Arrière-plan</h3><p>Afin de maintenir un nombre raisonnable de segments, Lucene vérifie périodiquement s'il doit fusionner des segments. Cela revient à vérifier si le nombre de segments actuel dépasse un nombre de segments cible, qui est déterminé par la taille du segment de base et la politique de fusion. Si le nombre est dépassé, Lucene fusionne les groupes de segments alors que la contrainte n'est pas respectée. Ce processus a été décrit en détail <a href="https://blog.mikemccandless.com/2011/02/visualizing-lucenes-segment-merges.html">dans d'autres documents.</a></p><p>Lucene choisit de fusionner des segments de taille similaire car cela permet d'obtenir une croissance logarithmique de l'amplification de l'écriture. Dans le cas d'un index vectoriel, l'amplification de l'écriture est le nombre de fois qu'un vecteur sera inséré dans un graphique. Lucene essaiera de fusionner les segments par groupes d'environ 10. Par conséquent, les vecteurs sont insérés dans un graphe environ {10}\left (\frac{n}{n_0}\right )fois, où  le nombre de vecteurs de l'index et  est le nombre de vecteurs du segment de base attendu. En raison de la croissance logarithmique, l'amplification de l'écriture est à un chiffre, même pour les indices les plus importants. Cependant, le temps total passé à fusionner les graphes est linéairement proportionnel à l'amplification de l'écriture.</p><p>Lors de la fusion des graphes HNSW, nous procédons déjà à une petite optimisation : nous conservons le graphe du plus grand segment et y insérons les vecteurs des autres segments. C'est la raison pour laquelle le facteur 9/10 est mentionné ci-dessus. Nous montrons ci-dessous comment nous pouvons faire beaucoup mieux en utilisant les informations de tous les graphiques que nous fusionnons.</p><h3>Fusion du graphique HNSW</h3><p>Auparavant, nous retenions le plus grand graphe et insérions des vecteurs provenant des autres en ignorant les graphes qui les contiennent. L'idée clé que nous utilisons ci-dessous est que chaque graphe HNSW que nous écartons contient des informations de proximité importantes sur les vecteurs qu'il contient. Nous aimerions utiliser cette information pour accélérer l'insertion d'au moins une partie des vecteurs.</p><p>Nous nous concentrons sur le problème de l'insertion d'un petit graphe  dans un graphe plus grand , puisqu'il s'agit d'une opération atomique que nous pouvons utiliser pour construire n'importe quelle politique de fusion.</p><p>La stratégie consiste à trouver un sous-ensemble de sommets de l' à insérer dans le grand graphe. Nous utilisons ensuite la connectivité de ces sommets dans le petit graphe pour accélérer l'insertion des sommets restants  Dans ce qui suit, nous utilisons  et  pour désigner les voisins d'un sommet  dans le petit et le grand graphe, respectivement. Schématiquement, le processus est le suivant.</p><p><code>MERGE-HNSW</code></p><p><code>Inputs </code><code> and </code></p><p><code>1</code><code>Find </code><code> to insert into </code><code> using COMPUTE-JOIN-SET</code>
<code>2</code><code>Insert each vertex </code><code> into </code>
<code>3</code><code>for </code><code> do</code>
<code>4</code>
<code>5</code>
<code>6</code><code>FAST-SEARCH-LAYER</code>
<code>7</code><code>SELECT-NEIGHBORS-HEURISTIC</code>
<code>8</code></p><p>Nous calculons l'ensemble  à l'aide d'une procédure que nous décrivons ci-dessous (ligne 1). Ensuite, nous insérons chaque sommet de  dans le grand graphe à l'aide de la procédure d'insertion HNSW standard (ligne 2). Pour chaque sommet que nous n'avons pas inséré, nous trouvons ses voisins que nous avons insérés et leurs voisins dans le grand graphe (lignes 4 et 5). Nous utilisons une procédure <code>FAST-SEARCH-LAYER</code> avec cet ensemble (ligne 6) pour trouver les candidats pour le site <code>SELECT-NEIGHBORS-HEURISTIC</code> de l'<a href="https://arxiv.org/pdf/1603.09320">article</a> HNSW (ligne 7). En fait, nous remplaçons <code>SEARCH-LAYER</code> pour trouver l'ensemble de candidats dans la méthode <code>INSERT</code> (Algorithme 1 de l'article), qui est par ailleurs inchangée. Enfin, nous ajoutons le sommet que nous venons d'insérer dans  (ligne 8).</p><p>Il est clair que pour que cela fonctionne, chaque sommet dans  doit avoir au moins un voisin dans  En fait, nous exigeons que pour chaque sommet dans  que  pour quelque  M, la connectivité maximale de la couche. Nous observons que dans les graphes HNSW réels, les degrés des sommets sont assez variés. La figure ci-dessous montre une fonction de densité cumulative typique du degré de sommet pour la couche inférieure d'un graphique Lucene HNSW.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc815e1a9c3bb0d06/6a17e178ec0f89308a5a6564/44f001b3a1bc6627e172fed5c52a02cfdcb4cd66-1324x898.png" alt="Graphique HNSW : Exemple de distribution des degrés des sommets" /><p>Nous avons étudié la possibilité d'utiliser une valeur fixe pour  et de la faire dépendre du degré du sommet. Ce deuxième choix conduit à des accélérations plus importantes avec un impact minimal sur la qualité du graphe, nous avons donc opté pour ce qui suit</p><p>Notez que | est égal au degré du sommet  dans le petit graphe par définition. Le fait d'avoir une limite inférieure de deux signifie que nous insérerons tous les sommets dont le degré est inférieur à deux.</p><p>Un simple argument de comptage suggère que si nous choisissons  avec soin, il nous suffit d'insérer directement dans   Plus précisément, nous colorons une arête du graphe si nous insérons exactement l'un de ses sommets d'extrémité dans  Nous savons alors que pour que chaque sommet de  ait au moins  voisins dans , nous devons colorer au moins \sum_  arêtes. En outre, nous nous attendons à ce que</p><p>Ici, \mathbb  [N_s(U)|\right] est le degré moyen des sommets dans le petit graphe. Pour chaque sommet , nous colorons au plus  arêtes. Par conséquent, le nombre total d'arêtes que nous prévoyons de colorer est au maximum de _U\left [|N_s(U)|\right]. Nous espérons qu'en choisissant  avec soin, nous colorerons un nombre d'arêtes proche de ce nombre et donc, pour couvrir tous les sommets,  doit satisfaire à</p><p>Ceci implique que  {1}{4}|V_s|=\frac{1}{5}|V_s|.</p><p>Si <code>SEARCH-LAYER</code> domine le temps d'exécution, cela signifie que nous pourrions accélérer le temps de fusion jusqu'à  Compte tenu de la croissance logarithmique de l'amplification de l'écriture, cela signifie que même pour de très grands indices, nous ne ferions que doubler le temps de construction par rapport à la construction d'un seul graphique.</p><p>Le risque de cette stratégie est de nuire à la qualité du graphique. Nous avons d'abord essayé avec une version sans option <code>FAST-SEARCH-LAYER</code>. Nous avons constaté que cela dégradait la qualité des graphes dans la mesure où le rappel en fonction de la latence était affecté, en particulier lors de la fusion en un seul segment. Nous avons ensuite exploré diverses alternatives en effectuant une recherche limitée dans le graphique. Finalement, le choix le plus efficace a été le plus simple. Utilisez <code>SEARCH-LAYER</code> mais avec une faible <code>ef_construction</code>. Ce paramétrage nous a permis d'obtenir des graphes d'excellente qualité tout en réduisant le temps de fusion d'un peu plus de 30% en moyenne.</p><h3>Calcul de l'ensemble de jonction</h3><p>La recherche d'un bon ensemble de jointures peut être formulée comme un problème de couverture de graphe HNSW. Une heuristique gourmande est une heuristique simple et efficace pour approximer les couvertures optimales des graphes. L'approche que nous adoptons consiste à sélectionner les sommets un par un pour les ajouter à  dans l'ordre décroissant des gains. Le gain est défini comme suit :</p><p>Ici,  représente le nombre de voisins d'un vecteur  dans  et  est la fonction indicatrice. Le gain comprend la variation du nombre de sommets que nous avons ajoutés à , c'est-à-dire \max , puisque nous nous rapprochons de notre objectif en ajoutant un sommet moins couvert. Le calcul du gain est illustré dans la figure ci-dessous pour le sommet central orange.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7a353d56fa09af5f/6a17e17a2f4a5c33f7fa8825/fe11cd55a7d94d9e0b0f4d47ea309a99075c355d-540x474.png" alt="Gain de sommet à ajouter à l'ensemble de jointure J dans le graphe HNSW" /><p>Nous conservons l'état suivant pour chaque sommet </p><ol><li><p>S'il est périmé,</p></li><li><p>Son gain ,</p></li><li><p>Le nombre de sommets adjacents dans  est noté </p></li><li><p>Un nombre aléatoire dans l'intervalle [0,1] qui est utilisé pour départager les ex-aequo.</p></li></ol><p>Le pseudo-code pour le calcul de l'ensemble de jonction est le suivant.</p><p><code>COMPUTE-JOIN-SET</code></p><p><code>Inputs </code></p><p><code>1</code>
<code>2</code>
<code>3</code><code>for </code><code> do</code>
<code>4</code>
<code>5</code>
<code>6</code>
<code>7</code><code>while </code><code> do
8</code><code> maximum gain vertex in </code>
<code>9</code><code>Remove the state for </code><code> from </code>
<code>10</code><code>if </code><code> is not stale then</code>
<code>11</code>
<code>12</code>
<code>13</code><code>for </code><code> do</code>
<code>14</code><code>mark </code><code> as stale if </code>
<code>15</code><code>mark neighbors of </code><code> stale if </code><code>
16</code><code>
17</code><code>else
18</code><code>
19</code><code>if </code><code> then
20</code><code>
21</code><code>return </code></p><p>Nous commençons par initialiser l'état dans les lignes 1 à 5.</p><p>À chaque itération de la boucle principale, nous extrayons d'abord le sommet de gain maximal (ligne 8), en brisant les égalités de manière aléatoire. Avant de procéder à toute modification, nous devons vérifier si le gain du sommet est périmé. En particulier, chaque fois que nous ajoutons un sommet à , nous affectons le gain d'autres sommets :</p><ol><li><p>Puisque tous ses voisins ont un voisin supplémentaire en , leurs gains peuvent changer (ligne 14)</p></li><li><p>Si l'un de ses voisins est maintenant entièrement couvert, tous les gains de ses voisins peuvent changer (lignes 14-16).</p></li></ol><p>Nous recalculons les gains paresseusement, c'est-à-dire que nous ne recalculons le gain d'un sommet que si nous voulons l'insérer dans  (lignes 18-20). Puisque les gains ne font que diminuer, nous ne pouvons jamais manquer un sommet que nous devrions insérer.</p><p>Notez que nous avons simplement besoin de suivre le gain total de sommets que nous avons ajoutés à  pour déterminer quand sortir. En outre, pendant que  {exit}au moins un sommet aura un gain non nul, de sorte que nous progressons toujours.</p><h3>Résultats</h3><p>Nous avons mené des expériences sur quatre ensembles de données qui, ensemble, couvrent les trois mesures de distance prises en charge (Euclidean, cosinus et produit intérieur) :</p><ol><li><p>quora-E5-small : 522931 documents, 384 dimensions et utilise la similarité cosinus,</p></li><li><p>cohere-wikipedia-v2 : 1M documents, 768 dimensions et utilise la similarité cosinus,</p></li><li><p>gist : 1M documents, 960 dimensions et utilise la distance euclidienne, et</p></li><li><p>cohere-wikipedia-v3 : 1M documents, 1024 dimensions et utilise le produit intérieur maximum.</p></li></ol><p>Pour chaque ensemble de données, nous évaluons deux niveaux de quantification :</p><ol><li><p>int8 - qui utilise un entier de 1 octet par dimension et</p></li><li><p>BBQ - qui utilise un seul bit par dimension.</p></li></ol><p>Enfin, pour chaque expérience, nous avons évalué la qualité de la recherche à deux profondeurs d'extraction et nous l'avons examinée après la construction de l'index, puis après la fusion forcée en un seul segment.</p><p>En résumé, nous obtenons des accélérations substantielles et constantes dans l'indexation et la fusion tout en maintenant la qualité du graphe et donc les performances de recherche dans tous les cas.</p><h4>Expérience 1 : quantification int8</h4><p>Les gains de vitesse moyens entre la ligne de base et le candidat, les changements proposés, sont les suivants :</p><p>Accélération du temps d'indexation : <strong>1,</strong>fois</p><p>Accélération de la fusion forcée : <strong>1,</strong>fois</p><p>Cela correspond à la répartition suivante des durées d'exécution</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8422be122ef1a9c9/6a17e17bbe6086522e00467d/5f9f6d487b4c74c4998aa47465912c1a9743e928-734x479.png" alt="Temps d'indexation et de fusion pour la stratégie de fusion de base et la stratégie de fusion candidate" /><p>Par souci d'exhaustivité, les horaires exacts sont les suivants</p><p></p><p>Index</p><p></p><p>Fusionner</p><p></p><p>Ensemble de données</p><p>ligne de base</p><p>candidat</p><p>Développer</p><p>candidat</p><p>quora-E5-petit</p><p>112.41s</p><p>81.55s</p><p>113.81s</p><p>70.87s</p><p>wiki-cohere-v2</p><p>158.1s</p><p>122.95s</p><p>425.20s</p><p>239.28s</p><p>liste</p><p>141.82s</p><p>119.26s</p><p>536.07s</p><p>279.05s</p><p>wiki-cohere-v3</p><p>211.86s</p><p>168.22s</p><p>654.97s</p><p>414.12s</p><p>Nous présentons ci-dessous les graphiques de rappel et de latence qui comparent le candidat (lignes pointillées) à la ligne de base à deux profondeurs de recherche : rappel@10 et rappel@100 pour les index avec plusieurs segments (le résultat final de notre stratégie de fusion par défaut après l'indexation de tous les vecteurs) et après la fusion forcée à un seul segment. Une courbe plus haute et plus à gauche est meilleure, ce qui signifie une meilleure mémorisation avec une latence plus faible.</p><p>Comme vous pouvez le constater, pour les indices de segments multiples, le candidat est meilleur pour l'ensemble de données Cohere v3 et légèrement moins bon, mais presque comparable, pour tous les autres ensembles de données. Après la fusion en un seul segment, les courbes de rappel sont presque identiques dans tous les cas.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf674622469bf6a90/6a17e17dfbc5f874a84919c9/9195297f19f90b172d832ba56bdada7b0d8a768b-985x392.png" alt="Rappel @10 et @100 vs latence après construction de l'index" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltde89f7e94ac823f4/6a17e17fe9ea876429a9c507/c866f32d579ae2b841e92ca733dd29c0e214ac6b-986x386.png" alt="Rappel @10 et @100 vs latence après fusion en un seul segment" /><h4>Expérience 2 : Quantification BBQ</h4><p>Les gains de vitesse moyens entre la ligne de base et le candidat sont les suivants :</p><p>Accélération du temps d'indexation : <strong>1,</strong>fois</p><p>Accélération de la fusion forcée : <strong>1,</strong>fois</p><p>Cela correspond à la répartition suivante des durées d'exécution</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5b0eb305222fa5c2/6a17e18125daab514f08a18c/11be0cf6b63480dc703409329357ea4847b55051-740x415.png" alt="Temps d'indexation et de fusion pour la stratégie de fusion de base et la stratégie de fusion candidate" /><p>Par souci d'exhaustivité, les horaires exacts sont les suivants</p><p></p><p>Index</p><p></p><p>Fusionner</p><p></p><p>Ensemble de données</p><p>ligne de base</p><p>candidat</p><p>Développer</p><p>candidat</p><p>quora-E5-petit</p><p>70.71s</p><p>58.25s</p><p>59.38s</p><p>40.15s</p><p>wiki-cohere-v2</p><p>203.08s</p><p>142.27s</p><p>107.27s</p><p>85.68s</p><p>liste</p><p>110.35s</p><p>105.52s</p><p>323.66s</p><p>202.2s</p><p>wiki-cohere-v3</p><p>313.43s</p><p>190.63s</p><p>165.98s</p><p>159.95s</p><p>Pour les indices de segments multiples, le candidat est meilleur pour presque tous les ensembles de données, à l'exception de cohere v2 où la ligne de base est légèrement meilleure. Pour les indices de segment unique, les courbes de rappel sont presque identiques dans tous les cas.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb12174abeadbcdd1/6a17e1822f4a5c4cc2fa8829/7da1be27bab5a7f5f9c9a1882ea35761a4a0ba5c-973x383.png" alt="Rappel @10 et @100 vs latence après construction de l'index" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf1b88d4f7fec9905/6a17e184414c64a8de9450b4/a26af30a56c55a12addee7e4fb7e8f606f366668-979x386.png" alt="Rappel @10 et @100 vs latence après fusion en un seul segment" /><h3>Conclusion</h3><p>L'algorithme présenté dans ce blog sera disponible dans la prochaine version de Lucene 10.2, ainsi que dans la version d'Elasticsearch qui est basée sur cette dernière. Les utilisateurs pourront profiter de l'amélioration des performances de fusion et de la réduction du temps de construction de l'index dans ces nouvelles versions. Ce changement fait partie de nos efforts continus pour rendre Lucene et Elasticsearch rapides et efficaces pour la recherche vectorielle et hybride.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hnsw-graphs-speed-up-merging</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hnsw-graphs-speed-up-merging</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[Lucene]]></category>
    <dc:creator><![CDATA[Thomas Veasey,Mayya Sharipova]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9fca6a01d0541cfb/6a17e187033c8d46486bb0e1/49a6c880f5dedd0fa502ece5be124824ee218cc0-1792x1024.png" length="0" type="image/png"/>
    <pubDate>Mon, 07 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Scaling des modèles d'interaction tardive dans Elasticsearch - 2e partie]]></title>
    <description><![CDATA[Cet article explore des techniques pour préparer les vecteurs d'interaction tardive aux charges de travail de production à grande échelle, notamment la réduction de l’espace disque et l’amélioration de l’efficacité des calculs.]]></description>
    <content:encoded><![CDATA[<p>Dans notre <a href="https://www.elastic.co/search-labs/blog/elastiacsearch-colpali-document-search">article précédent sur ColPali</a>, nous avons exploré comment créer des applications de recherche visuelle avec Elasticsearch. Nous nous sommes principalement concentrés sur la valeur que des modèles tels que ColPali apportent à nos applications, mais ils présentent des inconvénients en termes de performances par rapport à la recherche vectorielle avec des bi-encodeurs tels que E5.</p><p>S’appuyant sur les exemples de <a href="https://www.elastic.co/search-labs/blog/elastiacsearch-colpali-document-search">la première partie</a>, ce blog explore comment utiliser différentes techniques et la puissante boîte à outils de recherche vectorielle d’Elasticsearch afin de préparer les vecteurs d’interaction tardive pour des charges de production à grande échelle.</p><p>Les exemples complets de code sont disponibles sur <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/colpali">GitHub.</a></p><h2>Défis des modèles d’interaction tardive</h2><p>ColPali crée plus de 1000 vecteurs par page pour les documents dans notre index.</p><p>Travailler avec des vecteurs d'interaction tardive présente alors deux défis :</p><ol><li><p>Espace disque : l’enregistrement de tous ces vecteurs sur disque entraînera une consommation de stockage considérable, ce qui s’avérera coûteux à grande échelle..</p></li><li><p>Calcul : Lors du classement de nos documents avec la comparaison <code>maxSimDotProduct()</code> , nous devons comparer tous ces vecteurs pour chacun de nos documents avec les N vecteurs de notre requête.</p></li></ol><p>Examinons quelques techniques pour remédier à ces problèmes.</p><h2>Techniques pour optimiser les modèles d'interactions tardives</h2><h3>Vecteurs de bits</h3><p>Afin de réduire l'espace disque, nous pouvons compresser les images en vecteurs binaires. Nous pouvons utiliser une simple fonction Python pour transformer nos multi-vecteurs en vecteurs de bits :</p>def to_bit_vectors(embeddings: list) -&gt; list:
    return [
        np.packbits(np.where(np.array(embedding) &gt; 0, 1, 0))
        .astype(np.int8)
        .tobytes()
        .hex()
        for embedding in embeddings
    ]<p>Le concept de noyau de la fonction est simple : les valeurs supérieures à 0 deviennent 1, et les valeurs inférieures à 0. Cela aboutit à un tableau de 0 et de 1, que nous transformons ensuite en une chaîne hexadécimale représentant notre vecteur de bits.</p><p>Pour le mapping de notre index, nous avons défini le paramètre <code>element_type</code> sur <code>bit</code>:</p>mappings = {
    "mappings": {
        "properties": {
            "col_pali_vectors": {
                "type": "rank_vectors",
                "element_type": "bit"
            }
        }
    }
}

es.indices.create(index=INDEX_NAME, body=mappings)<p>Après avoir écrit tous nos nouveaux vecteurs de bits dans notre index, nous pouvons classer nos vecteurs de bits en utilisant le code suivant :</p>query = "What do companies use for recruiting?"
query_vector = to_bit_vectors(create_col_pali_query_vectors(query))
es_query = {
    "_source": False,
    "query": {
        "script_score": {
            "query": {
                "match_all": {}
            },
            "script": {
                "source": "maxSimInvHamming(params.query_vector, 'col_pali_vectors')",
                "params": {
                    "query_vector": query_vector
                }
            }
        }
    },
    "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcaf0c8f999d68701/6a17f46696142a6f46eb1c56/51b989446e4099745971e1eac27d147a78d13e0a-1600x480.png" alt="" /><p>En échange d'un peu de précision, cela nous permet d'utiliser la distance de hamming (<code>maxSimInvHamming(...)</code>), qui est capable de tirer parti d'optimisations telles que les masques de bits, SIMD, etc. Pour en savoir plus sur les <a href="https://www.elastic.co/search-labs/blog/bit-vectors-in-elasticsearch">vecteurs binaires et la distance de hamming, consultez notre blog</a>.</p><p>Sinon, nous ne pouvons pas convertir notre vecteur de requête en vecteurs de bits et rechercher à l'aide du vecteur d'interaction tardive en pleine fidélité :</p>query = "What do companies use for recruiting?"
query_vector = create_col_pali_query_vectors(query)
es_query = {
    "_source": False,
    "query": {
        "script_score": {
            "query": {
                "match_all": {}
            },
            "script": {
                "source": "maxSimDotProduct(params.query_vector, 'col_pali_vectors')",
                "params": {
                    "query_vector": query_vector
                }
            }
        }
    },
    "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2fa6c89fb451b6e0/6a17f468af47b65da9cde0d9/89f3c795c6dc44b2f7d46801320288316b47e1b2-1600x488.png" alt="Résultats de l'utilisation de vecteurs de bits pour optimiser les modèles d'interaction tardive" /><p>Cela comparera nos vecteurs en utilisant une fonction de similarité asymétrique.</p><p></p><p>Considérons une distance de Hamming standard entre deux vecteurs de bits.. Supposons que nous ayons un vecteur de document <em>D :</em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4b6ece1ec4dedac/6a17f4694b055d248143234e/366a70a23e37d403788327b7aefc873fd4482f5e-1235x86.png" alt="" /><p>Et un vecteur de requête <em>Q :</em></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7721df9c8f0d84f1/6a17f46b3e03d77cf54f2dcb/978ec31e0afbc0eaebf548a015b628c0cad84625-1247x84.png" alt="" /><p></p><p>La quantification binaire simple transformera les vecteurs <em>D</em> en <code>10101101</code> et <em>Q</em> en <code>11111011</code>. Pour trouver la distance de Hamming, il faut des calculs directs en bits — c’est extrêmement rapide. Dans ce cas, la distance de hamming est <code>01010110</code>, qui a un nombre de bits de 4. La notation devient donc l'inverse de cette distance de hamming. Rappelez-vous : plus les vecteurs sont similaires, plus la distance de Hamming est petite. L'inverser permet aux vecteurs les plus similaires d'être mieux notés. Plus précisément ici, le score serait 1/4 = <code>0.25</code>.</p><p>Cependant, remarquez comment nous perdons l'ampleur de chaque dimension. Un <code>1</code> est un <code>1</code>. Ainsi, pour <em>Q</em>, la différence entre <code>0.01</code> et <code>0.79</code> disparaît. Puisque nous quantifions simplement selon <code>&gt;0</code>, nous pouvons faire une petite astuce où le vecteur Q n'est pas quantifié. Cela ne permet pas des calculs bit à bit extrêmement rapides, mais cela maintient le coût de stockage bas car D est toujours quantifié.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blted50315518e2d599/6a17f46c414c6463709452d3/508e8498ba969534d2a0131d431c75735fc27cb9-1399x611.png" alt="" /><p>En résumé, cette méthode conserve les informations fournies dans <em>Q</em>, ce qui permet d’améliorer la qualité de l’estimation de la distance tout en limitant la consommation de stockage.</p><p>L'utilisation de vecteurs de bits nous permet d'économiser de manière significative de l'espace disque et de la charge de calcul au moment de la requête. Mais nous pouvons faire davantage.</p><h3>Vecteurs moyens</h3><p>Pour scaler notre recherche sur des centaines de milliers de documents, même les avantages de performance que nous apportent les vecteurs de bits ne seront pas suffisants. Afin de scaler à ces types de charges de travail, nous voudrons exploiter la structure d'index HNSW d'Elasticsearch pour la recherche vectorielle.</p><p>ColPali génère environ un millier de vecteurs par document, ce qui est trop pour les ajouter à notre graphe HNSW. Nous devons donc réduire le nombre de vecteurs. Pour cela, nous pouvons créer une représentation unique de la signification du document en prenant la moyenne de tous les vecteurs de documents produits par ColPali lors de l’intégration de notre image.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc053425fbfcf8de8/6a17f46f148009faf1b488aa/7c3b4dffb70bd95f67deb35f8c01e73c5286c2ab-1476x1102.png" alt="Vecteur moyen sur tous les vecteurs d’interaction tardive" /><p>Pour l’instant, cela n’est pas possible au sein d’Elastic. Nous devrons donc prétraiter les vecteurs avant de les ingérer dans Elasticsearch. </p><p>Nous pouvons le faire avec Logstash ou des pipelines d'ingestion, mais ici nous utiliserons une simple fonction Python :</p>def to_avg_vector(vectors):
    vectors_array = np.array(vectors)
    
    avg_vector = np.mean(vectors_array, axis=0)
    
    norm = np.linalg.norm(avg_vector)
    if norm &gt; 0:
        normalized_avg_vector = avg_vector / norm
    else:
        normalized_avg_vector = avg_vector

    return normalized_avg_vector.tolist()<p>Nous normalisons également le vecteur afin de pouvoir utiliser la similarité par produit scalaire.</p><p>Après avoir transformé tous nos vecteurs ColPali en vecteurs moyens, nous pouvons les indexer dans notre champ dense_vector :</p>mappings = {
    "mappings": {
        "properties": {
            "avg_vector": {
                "type": "dense_vector",
                "dims": 128,
                "index": True,
                "similarity": "dot_product"
            },
            "col_pali_vectors": {
                "type": "rank_vectors",
                "element_type": "bit"
            }
        }
    }
}

es.indices.create(index=INDEX_NAME, body=mappings)<p>Il faut considérer que cela accroîtra l’utilisation globale du disque, car nous sauvegardons des données supplémentaires parallèlement à nos vecteurs d'interaction tardive. En outre, nous utiliserons de la RAM supplémentaire pour contenir le graphe HNSW, ce qui nous permettra de scaler la recherche à des milliards de vecteurs. Pour réduire l’utilisation de la RAM, nous pouvons utiliser notre <a href="https://www.elastic.co/search-labs/blog/optimized-scalar-quantization-elasticsearch">fonctionnalité BBQ</a> populaire. À notre tour, nous obtenons des résultats de recherche rapides sur d'énormes ensembles de données qui ne seraient autrement pas possibles.</p><p>À présent, nous recherchons simplement à l'aide de la requête knn pour trouver nos documents les plus pertinents.</p>query = "What do companies use for recruiting?"
query_vector = to_avg_vector(create_col_pali_query_vectors(query))
es_query = {
    "_source": False,
    "knn": {
        "field": "avg_vector",
        "query_vector": query_vector,
        "k": 10,
        "num_candidates": 100
    },
    "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf4ac6b7fd444f935/6a17f471a29299d76ad02dc0/a500f9907020f032c3b1c7a48ed8c759dc2a1dd4-1600x498.png" alt="" /><p>Le meilleur résultat précédent a malheureusement été relégué au troisième rang.</p><p>Pour résoudre ce problème, nous pouvons effectuer une récupération en plusieurs étapes. Dans notre première étape, nous utilisons la requête knn pour rechercher les meilleurs candidats pour notre requête parmi des millions de documents. Dans la deuxième étape, nous ne classons que les k premiers (ici : 10) avec la plus grande fidélité des vecteurs d'interaction tardive de ColPali. </p>query = "What do companies use for recruiting?"
col_pali_vector = create_col_pali_query_vectors(query)
avg_vector = to_avg_vector(col_pali_vector)
es_query = {
  "_source": False,
  "retriever": {
    "rescorer": {
      "retriever": {
        "knn": {
          "field": "avg_vector",
          "query_vector": avg_vector,
          "k": 10,
          "num_candidates": 100
        }
      },
      "rescore": {
        "window_size": 10,
        "query": {
          "rescore_query": {
            "script_score": {
              "query": {
                "match_all": {}
              },
              "script": {
                "source": "maxSimDotProduct(params.query_vector, 'col_pali_vectors')",
                "params": {
                  "query_vector": col_pali_vector
                }
              }
            }
          }
        }
      }
    }
  },
  "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt265edd5f37be40b7/6a17f4733e9e45583dba15e6/797f490860af87fcf29f34668a5c4511419c6fd0-1600x501.png" alt="Résultats de l'utilisation des vecteurs moyens pour l'optimisation des modèles d'interaction tardive" /><p>Ici, nous utilisons l'outil <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.18/retriever.html#rescorer-retriever">rescore retriever</a> introduit en 8.18 pour reclasser nos résultats. Après un nouveau calcul des scores, nous constatons que notre meilleur match se retrouve à nouveau en première position. </p><p>Remarque : Dans une application de production, nous pouvons utiliser un k bien supérieur à 10 car la fonction Max Sim est toujours relativement performante.</p><h3>Regroupement de jetons</h3><p>Le pooling de tokens réduit la longueur de séquence des plongements multi-vectoriels en regroupant les informations redondantes, comme les zones de fond blanc. Cette technique réduit le nombre de plongements tout en préservant la majeure partie du signal de la page.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3d643e6ac908f640/6a17f4754b055d3783432352/09eae0b768f4b450e555d7e53e57561bd52de2c0-1412x1056.png" alt="Pooling de tokens pour optimiser les modèles d'interaction tardive" /><p>Le pooling de jetons fonctionne en regroupant des embeddings de jetons similaires au sein d'un document en clusters à l'aide d'un algorithme de clustering. Ensuite, la moyenne des vecteurs dans chaque groupe est calculée pour créer une représentation unique et agrégée. Ce vecteur agrégé remplace les tokens originaux dans le groupe, réduisant ainsi le nombre total de vecteurs sans perte significative du signal du document.</p><p>Le document ColPali propose une valeur initiale de facteur de pool de 3 pour la plupart des ensembles de données, ce qui assure la maintenance de 97,8 % des performances originales tout en réduisant le nombre total de vecteurs de 66,7 %. </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d78a15c6944a1ea/6a17f477414c64a2929452d9/343cc9eaf54af8c7a125d4115838f3c7d5659de0-1600x1007.png" alt="Facteur pool pour optimiser les modèles d'interactions tardives" /><p>Mais attention : l’ensemble de données « Shift », qui contient des documents très denses et riches en texte avec peu d’espace blanc, voit ses performances se dégrader rapidement à mesure que les facteurs de regroupement augmentent.</p><p>Pour créer les vecteurs regroupés, nous pouvons utiliser la bibliothèque colpali_engine :</p>from colpali_engine.compression.token_pooling import HierarchicalTokenPooler

pooler = HierarchicalTokenPooler(pool_factor=3) # test on your data for a good pool_factor

def pool_vectors(embedding: list) -&gt; list:
    tensor = torch.tensor(embedding).unsqueeze(0)
    pooled = pooler.pool_embeddings(tensor)
    return pooled.squeeze(0).tolist()<p>Nous avons maintenant un vecteur dont les dimensions ont été réduites d’environ 66,7 %. Nous l'indexons comme d'habitude et nous pouvons rechercher dessus avec notre fonction <code>maxSimDotProduct()</code>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc460c14031814ede/6a17f4794202292bf629f72d/2412c5db7d79a590b96d42fe01f140c42f010612-1600x481.png" alt="Résultats des modèles à interaction tardive" /><p>Nous pouvons obtenir de bons résultats de recherche au détriment d’une légère précision dans les résultats.</p><p>Conseil : avec un pool_factor plus élevé (100-200), vous pouvez également obtenir un juste milieu entre la solution vectorielle moyenne et celle dont nous avons discuté ici. Avec environ 5 à 10 vecteurs par document, il devient possible de les indexer dans un champ imbriqué pour tirer parti de l'index HNSW.</p><h2>Encodeur Coss vs. encodeur à interaction tardive vs. bi-encodeur</h2><p>Compte tenu de ces éléments, quelle est la place des modèles d'interaction tardive (comme ColPali ou ColBERT) vis-à-vis des autres méthodes de récupération de données par IA ?</p><p>Bien que la fonction de simulation maximale soit moins chère que les encodeurs croisés, elle nécessite néanmoins beaucoup plus de comparaisons et de calculs que la recherche vectorielle avec des bi-encodeurs, où nous comparons simplement deux vecteurs pour chaque paire requête-document. </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbfd97f3bba3e5b17/6a17f47be31791b0052d5943/75e1fc9e601aa7a6e88f137565c1919166c7a71c-1480x458.png" alt="Cross-encodeur vs. modèles à interaction tardive vs. bi-encodeur" /><p>Pour cette raison, nous recommandons d'utiliser les modèles d'interaction tardive uniquement pour le reclassement des k premiers résultats de recherche. Nous le reflétons également dans le nom du type de champ : rank_vectors.</p><p>Mais qu'en est-il de l'encodeur croisé ? Les modèles d'interaction tardive sont-ils meilleurs parce qu'ils sont moins chers à exécuter au moment de la requête ? Comme c'est souvent le cas, la réponse est : cela dépend. Les encodeurs croisés produisent généralement des résultats de meilleure qualité, mais ils nécessitent beaucoup de calcul car les paires de documents de requête doivent effectuer un passage complet à travers le modèle transformateur. Ils bénéficient également du fait qu'ils ne nécessitent aucune indexation de vecteurs et peuvent fonctionner de manière apatride. Résultats obtenus :</p><ul><li><p>Moins d'espace disque utilisé</p></li><li><p>Un système plus simple</p></li><li><p>Qualité supérieure des résultats de recherche</p></li><li><p>Latence plus élevée, et donc impossible à repositionner aussi profondément</p></li></ul><p>D'autre part, les modèles d'interaction tardive peuvent décharger une partie de ce calcul sur l'index, ce qui rend la requête moins coûteuse. Le prix à payer est la nécessité d’indexer les vecteurs, ce qui complexifie nos pipelines d’ingestion et nécessite davantage d’espace disque pour sauvegarder ces données.</p><p>Dans le cas particulier de ColPali, l'analyse des informations contenues dans les images est très coûteuse car elles contiennent beaucoup de données. Dans ce cas, le compromis penche en faveur de l'utilisation d'un modèle d'interaction tardive tel que ColPali, car évaluer ces informations au moment de la requête serait trop gourmand en ressources/lent. </p><p>Pour un modèle d’interaction tardif comme ColBERT, qui fonctionne sur des données textuelles comme la plupart des encodeurs croisés (par exemple, elastic-rerank-v1), la décision pourrait plutôt pencher vers l’utilisation du codeur croisé pour bénéficier des économies et de la simplicité du disque.</p><p>Nous vous encourageons à peser le pour et le contre en fonction de votre cas d'utilisation et à expérimenter les différents outils qu'Elasticsearch met à votre disposition pour créer les meilleures applications de recherche.</p><h2>Conclusion</h2><p>Dans ce blog, nous avons exploré diverses techniques pour optimiser les modèles d'interaction tardive comme ColPali pour la recherche vectorielle à grande échelle dans Elasticsearch. Bien que les modèles d'interaction tardive offrent un bon équilibre entre l'efficacité de la récupération et la qualité du classement, ils présentent également des défis liés au stockage et au calcul.</p><p>Pour relever ces défis, nous avons examiné :</p><ul><li><p>Des <strong>vecteurs de bits</strong> pour réduire de manière significative l'espace disque tout en tirant parti de calculs de similarité efficaces, tels que la distance de Hamming ou la similarité maximale asymétrique.</p></li><li><p>Des <strong>vecteurs moyens</strong> pour compresser plusieurs intégrations en une seule représentation dense, permettant ainsi une récupération efficace grâce à l'indexation HNSW.</p></li><li><p>Le <strong>pooling de jetons</strong> permet de fusionner intelligemment les embeddings redondants tout en assurant la maintenance de l’intégrité sémantique, réduisant ainsi la surcharge de calcul au moment de la requête.</p></li></ul><p>Elasticsearch offre une boîte à outils puissante pour personnaliser et optimiser les applications de recherche selon vos besoins. Que vous privilégiez la vitesse de récupération, la qualité de classement ou l'efficacité de stockage, ces outils et techniques vous permettent d'équilibrer les performances et la qualité selon vos besoins pour vos applications du monde réel.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/late-interaction-model-colpali-scale</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/late-interaction-model-colpali-scale</guid>
    <category><![CDATA[Pertinence]]></category>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Peter Straßer,Benjamin Trent]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt97a536033e6b0a56/6a17f47dfbc5f88c86491c0d/c780b78a07573f2df1cfef8b29a7109f839b0ab3-1200x628.png" length="0" type="image/png"/>
    <pubDate>Thu, 20 Mar 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Exploration de la recherche vectorielle accélérée par le GPU dans Elasticsearch avec NVIDIA : Chapitre I]]></title>
    <description><![CDATA[Basée sur NVIDIA cuVS, cette collaboration vise à fournir aux développeurs une accélération GPU pour la recherche vectorielle dans Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>Depuis un certain temps, l'équipe d'Elastic Engineering s'efforce d'optimiser les performances des bases de données vectorielles. Notre mission : faire de Lucene et Elasticsearch la meilleure base de données vectorielle. Grâce à l'accélération matérielle des <a href="https://www.elastic.co/fr/blog/accelerating-vector-search-simd-instructions">instructions SIMD du processeur</a>, à l'introduction de nouvelles innovations en matière de compression de données vectorielles<a href="https://www.elastic.co/fr/search-labs/blog/better-binary-quantization-lucene-elasticsearch">(Better Binary Quantization ou BBQ</a>), puis au dépassement des attentes en mettant à jour l'approche algorithmique de BBQ pour en tirer encore plus d'avantages, et en <a href="https://www.elastic.co/fr/search-labs/blog/filtered-hnsw-knn-search">rendant Filtered HNSW plus rapide.</a> Vous avez compris : nous construisons un système plus rapide, plus performant et plus efficace. base de données vectorielles pour les développeurs qui résolvent ces problèmes de RAG-gedy !</p><p>Dans le cadre de notre mission visant à ne négliger aucune efficacité, nous explorons les possibilités d'accélération avec ces curieuses puces informatiques, dont vous avez peut-être entendu parler - les GPU NVIDIA ! (Sérieusement, vous n'en avez pas entendu parler ?).</p><p>Lorsque nous sommes obsédés par les performances, nous devons explorer plusieurs espaces de problèmes : comment indexer une quantité exponentielle de données, comment en extraire des informations et comment le faire lorsque vos modèles ML sont impliqués. Vous devriez être en mesure d'exploiter tous les avantages disponibles lorsque vous disposez de GPU.</p><p>Dans ce billet, nous nous penchons sur notre collaboration avec l'équipe de recherche vectorielle de NVIDIA pour explorer la recherche vectorielle accélérée par le GPU dans Elasticsearch. Ce travail ouvre la voie à des cas d'utilisation où les développeurs pourraient utiliser un mélange de GPU et de CPU pour des applications réelles basées sur Elasticsearch. Une période passionnante !</p><h2>Elasticsearch GPUs</h2><p>Nous sommes ravis d'annoncer que l'équipe d'ingénieurs d'Elasticsearch participe à l'élaboration de l'API Java cuVS open-source pour les développeurs, qui expose des bindings pour les algorithmes de recherche vectorielle. Ce travail s'appuie sur notre expérience antérieure en matière de FFI au Panama. Elasticsearch et Apache Lucene utilisent l'API NVIDIA cuVS pour construire le graphe pendant l'indexation. D'accord, nous faisons un bond en avant ; revenons un peu en arrière.</p><p><a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS</a>, une bibliothèque C++ open-source, est au cœur de cette collaboration. Il vise à apporter l'accélération GPU à la recherche vectorielle en fournissant un débit plus élevé, une latence plus faible et des temps de construction d'index plus rapides. Mais Elasticsearch et Apache Lucene sont écrits en Java ; comment cela fonctionnera-t-il ?</p><p>C'est là qu'interviennent <a href="https://github.com/SearchScale/lucene-cuvs">lucene-cuvs</a> et la collaboration Elastic-NVIDIA-SearchScale pour l'intégrer à l'écosystème Lucene afin d'explorer la recherche vectorielle accélérée par le GPU dans Elasticsearch. Dans la récente version 25.02 de NVIDIA cuVS, nous avons ajouté une API Java pour cuVS. La nouvelle API est expérimentale et continuera d'évoluer, mais elle est actuellement disponible. La question peut se poser : les appels de fonctions Java à des fonctions natives ne sont-ils pas lents ? Plus maintenant ! Nous utilisons la nouvelle <a href="https://openjdk.org/projects/panama/">interface Panama FFI</a> (Foreign Function Interface) pour les liaisons, ce qui réduit au minimum les frais généraux pour Java par rapport aux appels descendants natifs.</p><p>Nous utilisons <a href="https://www.elastic.co/fr/search-labs/blog/lucene-and-java-moving-forward-together">Panama FFI dans Elasticsearch et Lucene</a> depuis un certain temps déjà. C'est génial ! Mais... il y a toujours un "mais", n'est-ce pas ? FFI est confronté à des problèmes de disponibilité entre les différentes versions de Java. Nous avons surmonté ce problème en compilant l'API cuVS en Java 21 et en encapsulant l'implémentation dans un jar multi-versions ciblant Java 22. Cela permet d'utiliser cuVS Java directement dans Lucene et Elasticsearch.</p><p>Ok, maintenant que nous avons l'API Java cuVS, de quoi d'autre avons-nous besoin ?</p><h2>Deux algorithmes pour l'unité centrale</h2><p>Elasticsearch prend en charge l'<a href="https://arxiv.org/abs/1603.09320">algorithme HNSW</a> pour une recherche KNN approximative évolutive. Cependant, pour tirer le meilleur parti du GPU, nous utilisons un algorithme différent, <a href="https://arxiv.org/pdf/2308.15136">CAGRA [</a><a href="https://arxiv.org/pdf/2308.15136"></a><a href="https://arxiv.org/pdf/2308.15136"><em>CUDA</em></a> <a href="https://arxiv.org/pdf/2308.15136"></a><a href="https://arxiv.org/pdf/2308.15136"><em>ANN</em></a> <a href="https://arxiv.org/pdf/2308.15136"></a><a href="https://arxiv.org/pdf/2308.15136"></a><a href="https://arxiv.org/pdf/2308.15136"></a>GRAph], qui a été spécialement conçu pour les niveaux élevés de parallélisme offerts par le GPU.</p><p>Avant de voir comment nous envisageons d'ajouter la prise en charge de CAGRA, examinons comment Elasticsearch et Lucene accèdent aux données d'index par le biais d'un "format de codec". Il s'agit de</p><ol><li><p>la représentation sur disque,</p></li><li><p>les interfaces de lecture et d'écriture des données,</p></li><li><p>et les mécanismes permettant de gérer l'architecture à base de segments de Lucene.</p></li></ol><p>Nous mettons en œuvre un nouveau <a href="https://lucene.apache.org/core/10_1_0/core/org/apache/lucene/codecs/KnnVectorsFormat.html">format de vecteur</a> KNN (k-nearest neighbors) qui utilise en interne l'API Java cuVS pour l'indexation et la recherche sur le GPU. À partir de là, nous "plongeons" ce type de codec dans les correspondances d'Elasticsearch avec un type de champ dans l'index. Par conséquent, vos requêtes KNN existantes continuent de fonctionner, que l'index de référence utilise un graphique CAGRA ou HNSW. Bien entendu, cela ne tient pas compte de nombreux détails, que nous prévoyons d'aborder dans un prochain blog. Voici l'architecture de haut niveau d'un Elasticsearch accéléré par le GPU.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6197b631a34f8b6/6a170b0da6c2b9e60ce7970b/be6b7356c03df4dee7230625c2c9af3b019f93be-756x510.png" alt="" /><p>Ce nouveau format de codec est par défaut CAGRA. Cependant, il permet également de convertir un graphique CAGRA en un graphique HNSW pour une recherche sur l'unité centrale.</p><h2>Indexation et recherche sur le GPU : Prendre quelques décisions "fondamentales</h2><p>Avec l'<a href="https://www.elastic.co/fr/search-labs/blog/stateless-your-new-state-of-find-with-elasticsearch">architecture</a> sans état d'Elasticsearch Serverless, qui sépare l'indexation et la recherche, les responsabilités sont désormais clairement délimitées. Nous choisissons le meilleur profil de matériel pour remplir chacune de ces responsabilités indépendantes.</p><p>Nous pensons que les utilisateurs envisageront deux stratégies de déploiement principales :</p><ol><li><p>Indexation et recherche sur le GPU : Pendant l'indexation, construire un graphe CAGRA et l'utiliser pendant la recherche - idéal lorsqu'une recherche à très faible latence est requise.</p></li><li><p>Indexation sur GPU et recherche sur CPU : Pendant l'indexation, construire un graphe CAGRA et le convertir en graphe HNSW. Le graphique HNSW est stocké dans l'index, qui peut ensuite être utilisé par l'unité centrale pour effectuer des recherches.</p></li></ol><p>Cette flexibilité permet de proposer différents modèles de déploiement, offrant des compromis entre le coût et la performance. Par exemple, un service d'indexation pourrait utiliser le GPU pour construire et fusionner efficacement des graphes en temps voulu, tout en utilisant un CPU moins puissant pour la recherche.</p><h2>Voici donc le plan pour la recherche vectorielle accélérée par le GPU dans Elasticsearch</h2><p>Nous sommes impatients d'apporter aux utilisateurs des gains de performance et de la flexibilité dans les stratégies de déploiement, en proposant différents boutons pour équilibrer le coût et la performance. <a href="https://www.nvidia.com/gtc/session-catalog/?tab.catalogallsessionstab=16566177511100015Kus&amp;search=Lucene#/">Voici la session de la NVIDIA GTC 2025</a> où ce travail a été présenté en détail.</p><p>Nous tenons à remercier les équipes d'ingénieurs de NVIDIA et de SearchScale pour leur fantastique collaboration. Dans un prochain blog, nous examinerons plus en détail les détails de la mise en œuvre et l'analyse des performances. Accrochez-vous à vos chapeaux de curiosité 🎩!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/gpu-accelerated-vector-search-elasticsearch-nvidia</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/gpu-accelerated-vector-search-elasticsearch-nvidia</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Chris Hegarty,Hemant Malik]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt298e839e708ca11c/6a170b0fb339d560c2769fc2/38bc0377a6adce7eae0099f61902fdbbe644eb4a-1440x960.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 19 Mar 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Une introduction rapide à la recherche vectorielle]]></title>
    <description><![CDATA[Cet article est le premier d'une série de trois qui se pencheront sur les subtilités de la recherche vectorielle, également connue sous le nom de recherche sémantique, et sur la manière dont elle est mise en œuvre dans Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>Cet article est le premier d'une série de trois qui se pencheront sur les subtilités de la recherche vectorielle, également connue sous le nom de recherche sémantique, et sur la manière dont elle est mise en œuvre dans Elasticsearch.</p><p>Cette première partie se concentre sur une introduction générale aux bases de l'intégration des vecteurs et au fonctionnement de la recherche vectorielle.</p><p>Armé de toutes les connaissances acquises dans le premier article, la <a href="https://www.elastic.co/search-labs/blog/vector-search-set-up-elasticsearch">deuxième partie</a> vous guidera dans les méandres de la mise en place de la recherche vectorielle dans Elasticsearch.</p><p>Dans la <a href="https://www.elastic.co/search-labs/blog/hybrid-search-elasticsearch">troisième partie</a>, nous tirerons parti de ce que nous avons appris dans les deux premières parties, nous nous appuierons sur ces connaissances et nous nous pencherons sur la manière de créer de puissantes requêtes de recherche hybrides dans Elasticsearch.</p><p>Avant d'entrer dans le vif du sujet de cet article, remontons dans le temps et passons en revue l'histoire des vecteurs, un concept clé de la recherche sémantique.</p><h2>Les vecteurs ne sont pas nouveaux</h2><p>Tout le monde s'accorde à dire que depuis l'avènement de ChatGPT en novembre 2022, il ne se passe pas un jour sans que l'on entende ou lise parler de "recherche vectorielle". Elle est omniprésente et si répandue que nous avons souvent l'impression qu'il s'agit d'une nouvelle technologie de pointe qui vient de voir le jour, alors que la vérité est que cette technologie existe depuis plus de six décennies ! Les recherches sur le sujet ont commencé au milieu des années 1960 et les premiers documents de recherche ont été publiés en 1978 par Gerard Salton, un spécialiste de la recherche d'informations, et ses collègues de l'université de Cornell. Les travaux de Salton sur les modèles de vecteurs denses et épars constituent la base de la technologie moderne de recherche vectorielle.</p><p>Au cours des 20 dernières années, de nombreux <a href="https://db-engines.com/en/ranking/vector+dbms/all">SGBD vectoriels</a> différents basés sur ses recherches ont été créés et mis sur le marché. Il s'agit notamment d'Elasticsearch alimenté par le projet Apache Lucene, qui a commencé à <a href="https://issues.apache.org/jira/browse/LUCENE-9004">travailler sur la recherche vectorielle</a> en 2019.</p><p>Les vecteurs sont aujourd'hui omniprésents et si répandus qu'il est important de bien comprendre leur théorie sous-jacente et leur fonctionnement interne avant de les utiliser. Avant de nous y plonger, examinons rapidement les différences entre la recherche lexicale et la recherche vectorielle afin de mieux comprendre en quoi elles diffèrent et comment elles peuvent se compléter l'une l'autre.</p><h2>Recherche vectorielle vs. recherche lexicale</h2><p>Une façon simple de présenter la recherche vectorielle est de la comparer à la recherche lexicale plus conventionnelle à laquelle vous êtes probablement habitué. La recherche vectorielle, également connue sous le nom de recherche sémantique, et la recherche lexicale fonctionnent de manière très différente. La recherche lexicale est le type de recherche que nous utilisons tous depuis des années dans Elasticsearch. Pour résumer très brièvement, il n'essaie pas de comprendre le sens réel de ce qui est indexé et interrogé, mais fait un gros effort pour faire correspondre <strong>lexicalement</strong> le littéral des mots ou de leurs variantes (pensez à l'extraction, aux synonymes, etc.) que l'utilisateur tape dans une requête avec tous les littéraux qui ont été précédemment indexés dans la base de données à l'aide d'algorithmes de similarité, tels que le TF-IDF.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfbb8e150865a5ec8/6a17e0c725daab50f408a16e/a4f3eba19599e5330e9b0d29ecdbbeac211cdab0-1600x583.png" alt="Exemple de recherche lexicale" /><p>Comme nous pouvons le voir, les trois documents en haut à gauche sont codés et analysés. Les termes obtenus sont ensuite indexés dans un index inversé, qui associe simplement les termes analysés aux identifiants des documents qui les contiennent. Notez que tous les termes ne sont présents qu'une seule fois et qu'aucun n'est partagé par un document. La recherche de "gentil professeur d'allemand" aboutira à trois documents avec des scores variables, même si aucun d'entre eux ne reflète réellement le sens de la requête.</p><p>Comme le montre la figure 2 ci-dessous, la situation devient encore plus délicate lorsqu'il s'agit de polysémie ou d'homographes, c'est-à-dire de mots qui s'écrivent de la même façon mais qui ont des <strong>sens différents</strong> (droite, paume, bat, signifie, etc.) Prenons le mot "droite", qui peut avoir trois sens différents, et voyons ce qu'il en est.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltea0cc829ff2c8029/6a17e0c92f4a5c8107fa8811/ebb9cc1be03475bbf68269a8dcea5f08bcda0b64-1594x754.png" alt="Recherche d'homographes avec la recherche lexicale" /><p>La recherche de <em>"I'm not right"</em> renvoie à un document dont la signification est exactement opposée à celle du premier résultat obtenu. Si vous recherchez exactement les mêmes termes mais que vous les ordonnez différemment pour produire un sens différent, par exemple <em>"tourner à droite"</em> et <em>"tourner à droite",</em> vous obtiendrez exactement le même résultat (c'est-à-dire le troisième document "Prendre un virage à droite"). Certes, nos requêtes sont trop simplifiées et n'utilisent pas les requêtes plus avancées telles que la correspondance des phrases, mais cela illustre le fait que la recherche lexicale ne comprend pas la véritable signification de ce qui est indexé et de ce qui est recherché. Si cela n'est pas clair, ne vous inquiétez pas, nous reviendrons sur cet exemple dans le troisième article pour voir comment la recherche vectorielle peut être utile dans ce cas.</p><p>Pour rendre justice à la recherche lexicale, lorsque vous avez le contrôle sur la façon dont vous indexez vos données <strong>structurées</strong> (pensez aux mappings, à l'analyse de texte, aux pipelines d'acquisition, etc.) et sur la façon dont vous élaborez vos requêtes (pensez aux requêtes DSL intelligemment conçues, à l'analyse des termes de la requête, etc. Les résultats d'Elasticsearch concernant ses capacités de recherche lexicale sont tout simplement stupéfiants. Ce qu'il a réalisé et à quel point il a popularisé et amélioré le domaine de la recherche lexicale au cours des dernières années est vraiment remarquable.</p><p>Cependant, lorsqu'il s'agit de fournir une assistance pour l'interrogation <a href="https://www.elastic.co/what-is/unstructured-data"><strong>de</strong></a><a href="https://www.elastic.co/what-is/unstructured-data"> données</a> non structurées (images, vidéos, audios, texte brut, etc.) à des utilisateurs qui ont besoin de poser des questions en texte libre, la recherche lexicale n'est pas à la hauteur. De plus, il arrive que la requête ne soit même pas un texte, mais une image, comme nous le verrons bientôt. La principale raison pour laquelle la recherche lexicale est inadéquate dans de telles situations est que les données non structurées ne peuvent être ni indexées ni interrogées de la même manière que les données structurées. Lorsqu'il s'agit de données non structurées, la <strong>sémantique</strong> entre en jeu. Que signifie la sémantique ? Tout simplement, le sens !</p><p>Prenons l'exemple simple d'un moteur de recherche d'images (par exemple, Google Image Search ou Lens). Vous glissez et déposez une image, et le moteur de recherche sémantique de Google trouvera et renverra les images les plus similaires à celle que vous avez interrogée. Dans la figure 3 ci-dessous, on peut voir à gauche la photo d'un berger allemand et à droite toutes les photos similaires qui ont été recherchées, le premier résultat étant la même photo que celle qui a été fournie (c'est-à-dire la plus similaire).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3e0a0fb3c300537a/6a17e0cbe8fbce69d13a185d/c0dff24d4379141abd1038c9c4b5577fd2dca802-1600x657.png" alt="Exemple de recherche sémantique : Recherche d'une image" /><p>Même si cela semble simple et logique pour nous, les humains, c'est une toute autre histoire pour les ordinateurs. C'est ce que la recherche vectorielle permet et contribue à réaliser. Le pouvoir libéré par la recherche vectorielle est énorme, comme le monde l'a récemment constaté. Soulevons maintenant le capot et découvrons ce qui s'y cache.</p><h2>Vecteurs d'intégration</h2><p>Comme nous l'avons vu précédemment, avec les moteurs de recherche lexicaux, les données structurées telles que le texte peuvent facilement être transformées en termes qui peuvent être comparés au moment de la recherche, quelle que soit la signification réelle des termes. Les données non structurées, quant à elles, peuvent prendre différentes formes, telles que des objets binaires de grande taille (images, vidéos, audios, etc.), et ne se prêtent pas du tout au même processus de tokénisation. En outre, l'objectif de la recherche sémantique est d'indexer les données de manière à ce qu'elles puissent être recherchées sur la base de la signification qu'elles représentent. Comment y parvenir ? La réponse tient en deux mots : <strong>Apprentissage automatique</strong>! Ou plus précisément l'apprentissage en profondeur (Deep Learning) !</p><p>L'<strong>apprentissage profond</strong> est un domaine spécifique de l'apprentissage automatique qui repose sur des modèles basés sur des réseaux neuronaux artificiels composés de plusieurs couches de traitement qui peuvent progressivement extraire le véritable sens des données. Le fonctionnement de ces modèles de réseaux neuronaux s'inspire fortement du cerveau humain. La figure 4, ci-dessous, montre à quoi ressemble un réseau neuronal, avec ses couches d'entrée et de sortie ainsi que ses multiples couches cachées :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ca5fd8f0e61d939/6a17e0cd7f6f152a67c09a53/aeea3bab3c29e1c591a9c9082f2e73e7d3990ef1-1600x1116.png" alt="Couches de réseaux neuronaux dans la recherche vectorielle" /><p>La véritable prouesse des réseaux neuronaux est qu'ils sont capables de transformer une simple donnée non structurée en une séquence de valeurs à virgule flottante, connues sous le nom de <strong>vecteurs d'intégration</strong> ou simplement d'<strong>intégration</strong>. En tant qu'êtres humains, nous comprenons assez bien ce que sont les vecteurs à condition de les visualiser dans un espace à deux ou trois dimensions. Chaque composante du vecteur représente une coordonnée dans un plan 2D x-y ou un espace 3D x-y-z.</p><p>Cependant, les vecteurs d'intégration sur lesquels fonctionnent les modèles de réseaux neuronaux peuvent avoir plusieurs centaines, voire des milliers de dimensions et représentent simplement un point dans un espace multidimensionnel. Chaque dimension vectorielle représente une <strong>caractéristique</strong> des données non structurées. Illustrons cela avec un modèle d'apprentissage profond qui transforme les images en vecteurs d'intégration de 2048 dimensions. Ce modèle transformerait l'image du berger allemand que nous avons utilisée dans la figure 3 en un vecteur d'intégration présenté dans le tableau ci-dessous. Notez que nous ne montrons que les trois premiers et derniers éléments, mais qu'il y aurait 2 042 colonnes/dimensions supplémentaires dans le tableau.</p><p></p><p>est_rouge</p><p>est_chien</p><p>ciel bleu</p><p>…</p><p>no_gras</p><p>berger allemand</p><p>est_arbre</p><p>Berger allemand embeddings</p><p>0.0121</p><p>0.9572</p><p>0.8735</p><p>…</p><p>0.1198</p><p>0.9712</p><p>0.0512</p><p>Chaque colonne est une dimension du modèle et représente une caractéristique que le réseau neuronal sous-jacent cherche à modéliser. Chaque entrée donnée au modèle sera caractérisée en fonction de sa similarité avec chacune des 2048 dimensions. Par conséquent, la valeur de chaque élément du vecteur d'intégration indique la <strong>similarité</strong> de cette entrée avec une dimension spécifique. Dans cet exemple, nous pouvons voir que le modèle a détecté une grande similarité entre les chiens et les bergers allemands ainsi que la présence d'un peu de ciel bleu.</p><p>Contrairement à la recherche lexicale, où un terme peut correspondre ou non, la recherche vectorielle permet d'avoir une bien meilleure idée de la <em>similitude</em> d'un élément de données non structurées avec chacune des dimensions prises en charge par le modèle. En tant que tels, les vecteurs d'intégration constituent une formidable représentation sémantique des données non structurées.</p><h2>La sauce secrète</h2><p>Maintenant que nous savons comment les données non structurées sont découpées par les réseaux neuronaux d'apprentissage profond en vecteurs d'intégration qui capturent la similarité des données selon un grand nombre de dimensions, nous devons comprendre comment fonctionne la mise en correspondance de ces vecteurs. Il s'avère que la réponse est assez simple. Les vecteurs d'intégration qui sont <strong>proches</strong> les uns des autres représentent des éléments de données <strong>sémantiquement similaires</strong>. Ainsi, lorsque nous interrogeons une base de données vectorielles, l'entrée de recherche (image, texte, etc.) est d'abord transformée en un vecteur d'intégration à l'aide du même modèle que celui utilisé pour l'indexation de toutes les données non structurées, et l'objectif final est de trouver les <strong>vecteurs voisins les plus proches</strong> de ce vecteur d'interrogation. Par conséquent, tout ce que nous avons à faire est de trouver comment mesurer la "distance" ou la "similarité" entre le vecteur de la requête et tous les vecteurs existants indexés dans la base de données, c'est à peu près tout.</p><h3>Distance et similarité</h3><p>Heureusement pour nous, la mesure de la distance entre deux vecteurs est un problème facile à résoudre grâce à l'arithmétique vectorielle. Examinons donc les fonctions de distance et de similarité les plus populaires qui sont prises en charge par les bases de données de recherche vectorielle modernes, telles qu'Elasticsearch. Attention, mathématiques en perspective !</p><h4>Distance L1</h4><p>La distance L1, également appelée distance de Manhattan, de deux vecteurs x et y est mesurée en additionnant la différence absolue par paire de tous leurs éléments. Il est évident que plus la distance d est petite, plus les deux vecteurs sont proches. La formule est assez simple, comme on peut le voir ci-dessous :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2df2e9bc20883491/6a17e0ce033c8d006a6bb0bf/2b17bcedfbedde61117a3e7970af55bc62318c6c-312x102.png" alt="Formule de distance L1 dans la recherche vectorielle" /><p>Visuellement, la distance L1 peut être illustrée comme le montre la figure 5 ci-dessous :</p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb90a33e6b1cbe00b/6a17e0d0414c64eb76945096/52075441892151536ed216081817a8e852566daa-474x464.png" alt="Visualisation de la distance L1 entre deux vecteurs" /><p>Prenons deux vecteurs x et y, tels que x = (1, 2) et y = (4, 3), la distance L1 des deux vecteurs serait alors | 1 - 4 | + | 2 - 3 | = 4.</p><h4>Distance L2</h4><p>La distance L2, également appelée distance euclidienne, de deux vecteurs x et y est mesurée en additionnant d'abord le carré de la différence par paire de tous leurs éléments, puis en prenant la racine carrée du résultat. Il s'agit du chemin le plus court entre deux points (également appelé hypoténuse). Comme pour L1, plus la distance d est petite, plus les deux vecteurs sont proches :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltae9b63fb593ae225/6a17e0d1af47b68379cddea8/b7675aa41f4f381e954c21dacd23a51a2dde6780-384x112.png" alt="Distance L2 dans la recherche vectorielle" /><p>La distance L2 est indiquée dans la figure 6 ci-dessous :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt32d913b46e07e79d/6a17e0d27f6f1582f7c09a57/bac99d08d6cf8a3a387a8acdc2e8f67357dad235-448x456.png" alt="Visualisation de la distance L2 entre deux vecteurs" /><p>Réutilisons les deux mêmes vecteurs d'échantillonnage x et y que nous avons utilisés pour la distance L1, et nous pouvons maintenant calculer la distance L2 comme  En prenant la racine carrée de 10, on obtient 3,16.</p><p></p><h4>Distance Linf</h4><p>La distance Linf (pour L infini), également appelée distance de Tchebychev ou distance de l'échiquier, de deux vecteurs x et y est simplement définie comme la plus longue distance entre deux de leurs éléments ou la plus longue distance mesurée le long de l'un des axes/dimensions. La formule est très simple et est illustrée ci-dessous :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt863411e15de16fa3/6a17e0d4505ac30939ad8a48/179ea6c6520a59acd97638313a2fb1b105e2ffed-436x82.png" alt="Formule de distance de Linf dans la recherche vectorielle" /><p>Une représentation de la distance Linf est présentée dans la figure 7 ci-dessous :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt863ee7b51fd5fc15/6a17e0d56864a4772db686af/b75540d793cdc0645244d77dc36da9d5734ccbac-548x560.png" alt="Distance Linf entre deux vecteurs" /><p>De nouveau, en prenant les deux mêmes vecteurs d'échantillonnage x et y, nous pouvons calculer la distance à l'infini comme suit : max ( | 1 - 4 | , | 2 - 3 | ) = max (3, 1) = 3.</p><h4>Similitude du cosinus</h4><p>Contrairement à L1, L2 et Linf, la similarité en cosinus ne mesure pas la distance entre deux vecteurs x et y, mais plutôt leur angle relatif, c'est-à-dire s'ils pointent tous deux à peu près dans la même direction. Plus la similarité s est élevée, plus les deux vecteurs sont "proches". La formule est à nouveau très simple et illustrée ci-dessous :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt61d55341a6457ab7/6a17e0d7e9ea872b4ea9c4e2/931b6b90f0ee83e63a66496d06d6b4cef3affddd-304x72.png" alt="Formule de similarité cosinus dans la recherche vectorielle" /><p>La figure 8 ci-dessous présente une façon de représenter la similarité cosinus entre deux vecteurs :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt50eb6399510b5380/6a17e0d83e9e45302cba139a/52092e8b5d366178e50a35f8c954ee8eaca76965-582x586.png" alt="similarité en cosinus entre deux vecteurs" /><p>En outre, comme les valeurs du cosinus sont toujours comprises dans l'intervalle [-1, 1], -1 signifie une similarité opposée (c'est-à-dire un angle de 180° entre les deux vecteurs), 0 signifie une similarité sans rapport (c'est-à-dire un angle de 90°) et 1 signifie une similarité identique (c'est-à-dire un angle de 0°), comme le montre la figure 9 ci-dessous :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt785e195c75a5089e/6a17e0da1d1b8355bc93e398/fd2690a845b89b69ff202bd04192893638538aec-1600x456.png" alt="Le spectre de similarité cosinus dans la recherche vectorielle" /><p>
Réutilisons à nouveau les mêmes vecteurs d'échantillons x et y et calculons la similitude en cosinus à l'aide de la formule ci-dessus. Tout d'abord, nous pouvons calculer le produit en points des deux vecteurs comme suit : . Ensuite, nous multiplions la longueur (également appelée magnitude) des deux vecteurs :  + (4^2 + 3^2)^{1/2} = 11,18034. Enfin, nous divisons le produit en points par la longueur multipliée 10 / 11,18034 = 0,894427 (c'est-à-dire un angle de 26°), qui est assez proche de 1, de sorte que les deux vecteurs peuvent être considérés comme assez similaires.</p><h4>Similitude du produit de points</h4><p>L'un des inconvénients de la similitude en cosinus est qu'elle ne prend en compte que l'angle entre deux vecteurs et non leur magnitude (c'est-à-dire leur longueur), ce qui signifie que si deux vecteurs pointent à peu près dans la même direction mais que l'un est beaucoup plus long que l'autre, les deux seront tout de même considérés comme similaires. La similarité par produit de points, également appelée produit scalaire ou produit intérieur, améliore cette situation en tenant compte à la fois de l'angle et de la magnitude des vecteurs, ce qui permet d'obtenir une mesure de similarité beaucoup plus précise.</p><p>Deux formules équivalentes sont utilisées pour calculer la similitude du produit de points. La première est la même que celle que nous avons vue plus haut dans le numérateur de la similitude du cosinus :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f1c3369f8628457/6a17e0db1d1b832a1893e39c/52e2723926ab27cd96b688e805fae15f607073c8-482x104.png" alt="Formule de similarité du produit de points dans la recherche vectorielle" /><p>La deuxième formule multiplie simplement la longueur des deux vecteurs par le cosinus de l'angle qui les sépare :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt136dd2a749c2398a/6a17e0dc6df73108a80a0e1f/dc9b11fc67dd748d9f1f29b735f4726138cb7d39-452x70.png" alt="Simplification de la formule de similarité du produit de points dans la recherche vectorielle" /><p>La similitude du produit des points est illustrée dans la figure 10 ci-dessous :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb2c095e1c1948752/6a17e0dd6df731e30b0a0e23/1bda38cf82a1e1037f44b6e9657602c9efe1c0a6-558x574.png" alt="similitude du produit de points entre deux vecteurs" /><p>Une dernière fois, nous prenons les vecteurs x et y de l'échantillon et calculons leur produit en points en utilisant la première formule, comme nous l'avons fait pour la similitude du cosinus plus tôt, soit (1 ) + (2 ) = 10.</p><p>En utilisant la deuxième formule, nous multiplions la longueur des deux vecteurs :  + (4^2 + 3^2)^{1/2} = 11,18034 et multiplions ce résultat par le cosinus de l'angle de 26° entre les deux vecteurs, et nous obtenons 11,18034 (26°) = 10.</p><p>Il convient de noter que si tous les vecteurs sont d'abord <strong>normalisés</strong> (c'est-à-dire que leur longueur est égale à 1), la similitude du produit de points devient exactement la même que la similitude du cosinus (parce que |x| |y| = 1), c'est-à-dire le cosinus de l'angle entre les deux vecteurs. Comme nous le verrons plus loin, la normalisation des vecteurs est une bonne pratique à adopter afin de rendre la magnitude du vecteur non pertinente, de sorte que la similitude se concentre simplement sur l'angle. Il accélère également le calcul de la distance au moment de l'indexation et de l'interrogation, ce qui peut s'avérer problématique lorsque l'on travaille sur des milliards de vecteurs.</p><h3>Récapitulatif rapide</h3><p>Nous avons parcouru beaucoup d'informations jusqu'à présent, alors arrêtons-nous un instant et faisons un rapide récapitulatif de notre situation. Nous avons appris que...</p><ul><li><p>...la recherche sémantique est basée sur des modèles de réseaux neuronaux d'apprentissage profond qui excellent dans la transformation de données non structurées en vecteurs d'intégration multidimensionnels.</p></li><li><p>...chaque dimension du modèle représente une caractéristique des données non structurées.</p></li><li><p>...un vecteur d'intégration est une séquence de valeurs de similarité (une pour chaque dimension) qui représente le degré de similarité d'un élément donné de données non structurées par rapport à chaque dimension.</p></li><li><p>...plus deux vecteurs sont "proches" (c'est-à-dire les plus proches voisins), plus ils représentent des concepts sémantiquement similaires.</p></li><li><p>...les fonctions de distance (L1, L2, Linf) nous permettent de mesurer la proximité de deux vecteurs.</p></li><li><p>...les fonctions de similitude (cosinus et produit de points) nous permettent de mesurer à quel point deux vecteurs se dirigent dans la même direction.</p></li></ul><p></p><p>Le dernier élément qu'il nous reste à examiner est le moteur de recherche vectoriel lui-même. Lorsqu'une requête arrive, elle est d'abord vectorisée, puis le moteur de recherche vectorielle trouve les vecteurs voisins les plus proches du vecteur de la requête. L'approche brute consistant à mesurer la distance ou la similarité entre le vecteur de la requête et tous les vecteurs de la base de données peut fonctionner pour de petits ensembles de données, mais s'avère rapidement insuffisante lorsque le nombre de vecteurs augmente. En d'autres termes, comment pouvons-nous indexer des millions, des milliards, voire des trillions de vecteurs et trouver les plus proches voisins du vecteur interrogé dans un délai raisonnable ? C'est là que nous devons faire preuve d'intelligence et trouver des moyens optimaux d'indexer les vecteurs de manière à ce que nous puissions trouver les plus proches voisins aussi rapidement que possible sans trop dégrader la précision.</p><h3>Algorithmes et techniques de recherche vectorielle</h3><p>Au fil des ans, de nombreuses équipes de recherche ont investi beaucoup d'efforts dans le développement d'algorithmes de recherche vectorielle très intelligents. Nous allons ici présenter brièvement les principaux d'entre eux. Selon le cas d'utilisation, certains sont mieux adaptés que d'autres.</p><h4>Recherche linéaire</h4><p>Nous avons brièvement abordé la recherche linéaire, ou indexation plate, lorsque nous avons mentionné l'approche brute consistant à comparer le vecteur de la requête à tous les vecteurs présents dans la base de données. Bien qu'elle puisse donner de bons résultats sur de petits ensembles de données, les performances diminuent rapidement lorsque le nombre de vecteurs et de dimensions augmente (complexité O(n)).</p><p>Heureusement, il existe des approches plus efficaces, appelées <strong>approximation du plus proche voisin</strong> (ANN), dans lesquelles les distances entre les vecteurs d'intégration sont calculées à l'avance et les vecteurs similaires sont stockés et organisés de manière à rester proches les uns des autres, par exemple à l'aide de grappes, d'arbres, de hachages ou de graphes. Ces approches sont dites "approximatives" car elles ne garantissent généralement pas une précision de 100%. L'objectif final est de <strong>réduire l'étendue de la recherche</strong> autant et aussi rapidement que possible afin de se concentrer uniquement sur les zones les plus susceptibles de contenir des vecteurs similaires ou de <strong>réduire la dimensionnalité des vecteurs</strong>.</p><h4>Arbres à K dimensions</h4><p>Un arbre à K dimensions, ou arbre KD, est une généralisation d'un arbre de recherche binaire qui stocke des points dans un espace à k dimensions et fonctionne en divisant continuellement l'espace de recherche en arbres plus petits à gauche et à droite où les vecteurs sont indexés. Au moment de la recherche, l'algorithme doit simplement visiter quelques branches de l'arbre autour du vecteur de la requête (le point rouge dans la figure 11) afin de trouver le plus proche voisin (le point vert dans la figure 11). Si plus de k voisins sont demandés, la zone jaune est étendue jusqu'à ce que l'algorithme trouve d'autres voisins.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt55e01707cd9fca57/6a17e0dfbe60866a90004653/e21af2d613112279089b6fa2166359233b10019d-829x860.png" alt="Algorithme de l'arbre KD dans la recherche vectorielle" /><p>Le principal avantage de l'algorithme de l'arbre KD est qu'il nous permet de nous concentrer rapidement sur certaines branches localisées de l'arbre, éliminant ainsi la plupart des vecteurs. Toutefois, l'efficacité de cet algorithme diminue à mesure que le nombre de dimensions augmente, car il faut visiter beaucoup plus de branches que dans les espaces de dimensions inférieures.</p><h4>Index de fichier inversé</h4><p>L'approche de l'index inversé des fichiers (IVF) est également un algorithme de <strong>partitionnement de l'espace</strong> qui affecte les vecteurs proches les uns des autres à leur centroïde commun. Dans l'espace 2D, le diagramme de Voronoï, comme le montre la figure 12, permet de mieux visualiser ce phénomène :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b7e4fe6deff5ef3/6a17e0e17b54f940eb8b381c/33ddaaa818ab2f2a5fc87982c82c2a34cb849e33-640x640.png" alt="Représentation de Voronoï d'un index de fichier inversé dans l'espace 2D " /><p>Nous pouvons voir que l'espace 2D ci-dessus est divisé en 20 groupes, chacun ayant son centroïde représenté par des points noirs. Tous les vecteurs d'intégration dans l'espace sont affectés au groupe dont le centroïde est le plus proche. Au moment de la recherche, l'algorithme détermine d'abord le groupe sur lequel il doit se concentrer en trouvant le centroïde le plus proche du vecteur de la requête, puis il peut simplement se concentrer sur cette zone, ainsi que sur les zones environnantes si nécessaire, afin de trouver les voisins les plus proches.</p><p>Cet algorithme souffre du même problème que les arbres KD lorsqu'il est utilisé dans des espaces à haute dimension. C'est ce qu'on appelle la malédiction de la dimensionnalité, qui se produit lorsque le volume de l'espace augmente tellement que toutes les données semblent éparses et que la quantité de données nécessaires pour obtenir des résultats plus précis croît de manière exponentielle. Lorsque les données sont éparses, il est plus difficile pour ces algorithmes de partitionnement de l'espace d'organiser les données en grappes. Heureusement pour nous, il existe d'autres algorithmes et techniques qui atténuent ce problème, comme indiqué ci-dessous.</p><h4>Quantification</h4><p>La quantification est une approche <strong>basée sur la compression</strong>qui nous permet de réduire la taille totale de la base de données en diminuant la précision des vecteurs d'intégration. Ceci peut être réalisé en utilisant la <strong>quantification scalaire (SQ)</strong> en convertissant les valeurs vectorielles à virgule flottante en valeurs entières. Cela permet non seulement de réduire la taille de la base de données d'un facteur 8, mais aussi de diminuer la consommation de mémoire et d'accélérer le calcul de la distance entre les vecteurs au moment de la recherche.</p><p>Une autre technique, appelée <strong>quantification par produit (PQ),</strong> divise d'abord l'espace en sous-espaces de dimensions inférieures, puis les vecteurs proches les uns des autres sont regroupés dans chaque sous-espace à l'aide d'un algorithme de regroupement (similaire aux k-moyennes).</p><p>Il convient de noter que la quantification diffère de la <strong>réduction de la dimensionnalité</strong>, où le nombre de dimensions est réduit, c'est-à-dire que les vecteurs deviennent simplement plus courts.</p><h4>Petits mondes navigables hiérarchiques (PMNH)</h4><p>Si le nom semble complexe, ne vous inquiétez pas, ce n'est pas vraiment le cas ! En résumé, Hierarchical Navigable Small Worlds est un algorithme basé sur un graphe multicouche qui est très populaire et efficace. Il est utilisé par de nombreuses bases de données vectorielles, dont Apache Lucene. La figure 13 ci-dessous présente une représentation conceptuelle de HNSW.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f4f4a8e393c8d53/6a17e0e3e8fbcefa193a1861/189ef9a8bec476e379222c454644ba4c1f952085-1400x840.png" alt="Petits mondes navigables hiérarchiques (PMNH)" /><p>Sur la couche supérieure, nous pouvons voir un graphique de très peu de vecteurs qui ont les liens les plus longs entre eux, c'est-à-dire un graphique de vecteurs connectés avec le moins de similarité. Plus nous plongeons dans les couches inférieures, plus nous trouvons de vecteurs et plus le graphique devient dense, avec de plus en plus de vecteurs proches les uns des autres. Dans la couche la plus basse, on trouve tous les vecteurs, les plus similaires étant les plus proches les uns des autres.</p><p>Au moment de la recherche, l'algorithme part de la couche supérieure à un point d'entrée arbitraire et trouve le vecteur le plus proche du vecteur de la requête (représenté par le point gris). Ensuite, il se déplace d'une couche à l'autre et répète le même processus, en partant du même vecteur que celui qu'il a laissé dans la couche précédente, et ainsi de suite, une couche après l'autre, jusqu'à ce qu'il atteigne la couche la plus basse et trouve le voisin le plus proche du vecteur de la requête.</p><h4>Hachage sensible à la localité (LSH)</h4><p>Dans la même veine que toutes les autres approches présentées jusqu'à présent, le hachage sensible à la localité vise à réduire considérablement l'espace de recherche afin d'augmenter la vitesse d'extraction. Avec cette technique, les vecteurs d'intégration sont transformés en valeurs de hachage, tout en préservant les informations de similarité, de sorte que l'espace de recherche devient finalement une simple table de hachage qui peut être consultée au lieu d'un graphe ou d'un arbre qui doit être parcouru. Le principal avantage des méthodes basées sur les hachages est que les vecteurs contenant un nombre arbitraire (important) de dimensions peuvent être mis en correspondance avec des hachages de taille fixe, ce qui accélère considérablement le temps de recherche sans sacrifier trop de précision.</p><p>Il existe de nombreuses façons de hacher les données en général, et d'intégrer des vecteurs en particulier, mais cet article n'entrera pas dans les détails de chacune d'entre elles. Les méthodes de hachage conventionnelles produisent généralement des hachages très différents pour des données qui semblent très similaires. Les vecteurs d'intégration étant composés de valeurs flottantes, prenons deux exemples de valeurs flottantes considérées comme très proches l'une de l'autre dans l'arithmétique vectorielle (par exemple, 0,73 et 0,74) et soumettons-les à quelques fonctions de hachage courantes. En regardant les résultats ci-dessous, il est évident que les fonctions de hachage courantes ne conservent pas la similarité entre les entrées.</p><p>Fonction de hachage</p><p>0.73</p><p>0.74</p><p>MD5</p><p>1342129d04cd2924dd06cead4cf0a3ca</p><p>0aec1b15371bd979cfa66b0a50ebecc5</p><p>SHA1</p><p>49d2c3e0e44bff838e1db571a121be5ea874e8d9</p><p>a534e76482ade9d9fe4bff3035a7f31f2f363d77</p><p>SHA256</p><p>99d03fc3771fe6848d675339fc49eeb1cb8d99a12e6358173336b99a2ec530ea</p><p>5ecbc825ba5c16856edfdaf0abc5c6c41d0d8a9c508e34188239521dc7645663</p><p>Alors que les méthodes de hachage classiques tentent de <em>minimiser les collisions de hachage</em> entre des données similaires, l'objectif principal du hachage sensible à la localité est de faire exactement le contraire, c'est-à-dire de <em>maximiser les collisions de hachage</em> afin que des données similaires tombent dans le même bac avec une forte probabilité. Ainsi, les vecteurs d'intégration qui sont proches les uns des autres dans un espace multidimensionnel seront hachés en une valeur de taille fixe tombant dans le même panier. Comme LSH permet à ces vecteurs hachés de conserver leur proximité, cette technique s'avère très pratique pour le regroupement de données et la recherche du voisin le plus proche.</p><p>Tout le travail se fait au moment de l'indexation, lorsque les hachages doivent être calculés, tandis qu'au moment de la recherche, il suffit de hacher le vecteur de la requête pour rechercher le seau contenant les vecteurs d'intégration les plus proches. Une fois le seau candidat trouvé, un deuxième tour a généralement lieu pour identifier les vecteurs voisins les plus proches du vecteur d'interrogation.</p><h2>Concluons</h2><p>Pour présenter la recherche vectorielle, nous avons dû couvrir un certain nombre de points dans cet article. Après avoir comparé les différences entre la recherche lexicale et la recherche vectorielle, nous avons appris comment les modèles de réseaux neuronaux d'apprentissage profond parviennent à capturer la sémantique des données non structurées et à transcoder leur signification en vecteurs d'intégration à haute dimension, une séquence de nombres à virgule flottante représentant la similarité des données selon chacune des dimensions du modèle. Il convient également de noter que la recherche vectorielle et la recherche lexicale ne sont pas des techniques de recherche d'informations concurrentes mais complémentaires (comme nous le verrons dans la troisième partie de cette série, lorsque nous nous pencherons sur la recherche hybride).</p><p>Nous avons ensuite présenté un élément fondamental de la recherche vectorielle, à savoir les fonctions de distance (et de similarité) qui nous permettent de mesurer la proximité de deux vecteurs et d'évaluer la similarité des concepts qu'ils représentent.</p><p>Enfin, nous avons passé en revue les différentes variantes des algorithmes et techniques de recherche vectorielle les plus populaires, qui peuvent être basés sur des arbres, des graphes, des grappes ou des hachages, et dont l'objectif est de se concentrer rapidement sur une zone spécifique de l'espace multidimensionnel afin de trouver les voisins les plus proches sans avoir à visiter l'ensemble de l'espace comme le ferait une recherche linéaire par force brute.</p><p>Si vous aimez ce que vous lisez, n'hésitez pas à consulter les autres parties de cette série :</p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/vector-search-set-up-elasticsearch">Partie 2 : Comment configurer la recherche vectorielle dans Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/hybrid-search-elasticsearch">Partie 3 : Recherche hybride avec Elasticsearch</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/introduction-to-vector-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/introduction-to-vector-search</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Valentin Crettaz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt70da374b8dc0c490/6a17e0e46864a473aab686b3/63eea8ea95b49e7241e539f65bf5aa3bb8823fff-1200x628.png" length="0" type="image/png"/>
    <pubDate>Thu, 06 Feb 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Comment utiliser le connecteur Elasticsearch Vector Store pour Microsoft Semantic Kernel pour le développement d'agents d'intelligence artificielle ?]]></title>
    <description><![CDATA[Microsoft Semantic Kernel est un kit de développement léger et open-source qui vous permet de créer facilement des agents d'intelligence artificielle et d'intégrer les derniers modèles d'intelligence artificielle dans votre base de code C#, Python ou Java. Avec la sortie du connecteur Semantic Kernel Elasticsearch Vector Store, les développeurs qui utilisent Semantic Kernel pour créer des agents d'intelligence artificielle peuvent désormais utiliser Elasticsearch comme un magasin vectoriel d'entreprise évolutif tout en continuant à utiliser les abstractions de Semantic Kernel.]]></description>
    <content:encoded><![CDATA[<p>En collaboration avec l'équipe <a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/">de Microsoft Semantic Kernel,</a> nous annonçons la disponibilité du <a href="https://github.com/elastic/semantic-kernel-net/">connecteur Semantic Kernel Elasticsearch Vector</a> Store, pour les utilisateurs <a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/">de Microsoft Semantic Kernel (.NET).</a> Semantic Kernel simplifie la création d'agents d'intelligence artificielle de niveau professionnel, notamment en permettant d'améliorer les grands modèles de langage (LLM) grâce à des réponses plus pertinentes et fondées sur des données provenant d'un magasin de vecteurs. Semantic Kernel fournit une couche d'abstraction transparente pour interagir avec les magasins vectoriels tels qu'Elasticsearch, offrant des fonctionnalités essentielles telles que la création, l'énumération et la suppression de collections d'enregistrements et le téléchargement, l'extraction et la suppression d'enregistrements individuels.</p><p>Le <a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/elasticsearch-connector?pivots=programming-language-csharp">connecteur Semantic Kernel Elasticsearch Vector Store, prêt à l'emploi, prend</a> en charge les <a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/?pivots=programming-language-csharp#the-vector-store-abstraction">abstractions du Semantic Kernel</a> vector store, ce qui permet aux développeurs de brancher très facilement Elasticsearch en tant que magasin vectoriel lors de la création d'agents d'intelligence artificielle.</p><p>Elasticsearch est solidement ancré dans la communauté des logiciels libres et a récemment adopté la <a href="https://www.elastic.co/blog/elasticsearch-is-open-source-again">licence AGPL</a>. Associés au noyau sémantique Microsoft open-source, ces outils offrent une solution puissante, prête pour l'entreprise. Vous pouvez commencer localement en démarrant Elasticsearch en quelques minutes en exécutant cette commande <code>curl -fsSL https://elastic.co/start-local | sh </code>(voir <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html">start-local</a> pour plus de détails) et évoluer vers des versions <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;utm_source=semantickernel&amp;utm_content=documentation">hébergées dans le nuage</a> ou <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.16/install-elasticsearch.html">auto-hébergées</a> tout en mettant en production vos agents d'IA.</p><p>Dans ce blog, nous verrons comment utiliser le <a href="https://github.com/elastic/semantic-kernel-net/">connecteur Semantic Kernel Elasticsearch Vector Store</a> lors de l'utilisation de Semantic Kernel. Une version Python du connecteur sera disponible à l'avenir.</p><h2>Scénario de haut niveau : Construire une application RAG avec Semantic Kernel &amp; Elasticsearch</h2><p>Dans la section suivante, nous présentons un exemple. À un niveau élevé, nous construisons une application RAG (Retrieval Augmented Generation) qui prend la question d'un utilisateur en entrée et renvoie une réponse. Nous utiliserons Azure OpenAI<a href="https://devblogs.microsoft.com/semantic-kernel/introducing-new-ollama-connector-for-local-models/">(un LLM local</a> peut également être utilisé) comme LLM, Elasticsearch comme magasin de vecteurs et Semantic Kernel (.net) comme cadre pour relier tous les composants ensemble.</p><p>Si vous n'êtes pas familier avec les architectures RAG, vous pouvez vous familiariser rapidement avec cet article <a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">: https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag</a>.</p><p>La réponse est générée par le LLM qui est alimenté par le contexte, pertinent pour la question, récupéré à partir du vectorstore Elasticsearch. La réponse inclut également la source qui a été utilisée comme contexte par le LLM.</p><h3>Exemple de GCR</h3><p>Dans cet exemple spécifique, nous créons une application qui permet aux utilisateurs de poser des questions sur les hôtels stockés dans une base de données interne. L'utilisateur peut par exemple rechercher un hôtel spécifique, en fonction de différents critères, ou demander une liste d'hôtels.</p><p>Pour la base de données d'exemple, nous avons généré une <a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">liste d'hôtels</a> contenant 100 entrées. La taille de l'échantillon est volontairement réduite pour vous permettre d'essayer la démo du connecteur aussi facilement que possible. Dans une application réelle, le connecteur Elasticsearch montrerait ses avantages par rapport à d'autres options, telles que l'implémentation du magasin vectoriel `InMemory`, en particulier lorsque l'on travaille avec de très grandes quantités de données.</p><p>L'application de démonstration complète se trouve dans le <a href="https://github.com/elastic/semantic-kernel-net/tree/main/Elastic.SemanticKernel.Playground">référentiel du</a> connecteur Elasticsearch vector store.</p><p>Commençons par ajouter les paquets NuGet et les directives nécessaires à notre projet :</p>dotnet add package "Elastic.Clients.Elasticsearch" -v 8.16.2
dotnet add package "Elastic.SemanticKernel.Connectors.Elasticsearch" -v 0.1.2
dotnet add package "Microsoft.Extensions.Hosting" -v 9.0.0
dotnet add package "Microsoft.SemanticKernel.Connectors.AzureOpenAI" -v 1.30.0
dotnet add package "Microsoft.SemanticKernel.PromptTemplates.Handlebars" -v 1.30.0using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Elastic.Clients.Elasticsearch;
using Elastic.Transport;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Data;
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;<p>Nous pouvons maintenant créer notre modèle de données et le doter d'attributs spécifiques au Semantic Kernel pour définir le schéma du modèle de stockage et quelques indications pour la recherche textuelle :</p>/// &lt;summary&gt;
/// Data model for storing a "hotel" with a name, a description, a  description embedding and an optional reference link.
/// &lt;/summary&gt;
public sealed record Hotel
{
	[VectorStoreRecordKey]
	public required string HotelId { get; set; }

	[TextSearchResultName]
	[VectorStoreRecordData(IsFilterable = true)]
	public required string HotelName { get; set; }

	[TextSearchResultValue]
	[VectorStoreRecordData(IsFullTextSearchable = true)]
	public required string Description { get; set; }

	[VectorStoreRecordVector(Dimensions: 1536, DistanceFunction.CosineSimilarity, IndexKind.Hnsw)]
	public ReadOnlyMemory&lt;float&gt;? DescriptionEmbedding { get; set; }

	[TextSearchResultLink]
	[VectorStoreRecordData]
	public string? ReferenceLink { get; set; }
}<p>Les attributs du schéma du modèle de stockage (`VectorStore*`) sont les plus pertinents pour l'utilisation réelle du connecteur Elasticsearch Vector Store, à savoir :</p><p></p><ul><li><p><code>VectorStoreRecordKey</code> pour marquer une propriété d'une classe d'enregistrement comme étant la clé sous laquelle l'enregistrement est stocké dans un magasin vectoriel.</p></li><li><p><code>VectorStoreRecordData</code> pour marquer une propriété d'une classe d'enregistrement comme "data".</p></li><li><p><code>VectorStoreRecordVector</code> pour marquer une propriété d'une classe d'enregistrement en tant que vecteur.</p></li></ul><p>Tous ces attributs acceptent divers paramètres facultatifs qui peuvent être utilisés pour personnaliser davantage le modèle de stockage. Dans le cas de <code>VectorStoreRecordKey </code>, par exemple, il est possible de spécifier une fonction de distance différente ou un type d'indice différent.</p><p>Les attributs de recherche de texte (<code>TextSearch*</code>) seront importants dans la dernière étape de cet exemple. Nous y reviendrons plus tard.</p><p>Dans l'étape suivante, nous initialisons le moteur du noyau sémantique et obtenons des références aux services de base. Dans une application réelle, l' <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection">injection de dépendances</a> devrait être utilisée au lieu d'accéder directement à la collection de services. La même chose s'applique à la configuration et aux secrets codés en dur, qui doivent être lus à l'aide d'un <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration">fournisseur de configuration</a>:</p>var builder = Host.CreateApplicationBuilder(args);

// Register AI services.
var kernelBuilder = builder.Services.AddKernel();

kernelBuilder.AddAzureOpenAIChatCompletion("gpt-4o", "https://my-service.openai.azure.com", "my_token");

kernelBuilder.AddAzureOpenAITextEmbeddingGeneration("ada-002", "https://my-service.openai.azure.com", "my_token");

// Register text search service.
kernelBuilder.AddVectorStoreTextSearch&lt;Hotel&gt;();

// Register Elasticsearch vector store.
var elasticsearchClientSettings = new ElasticsearchClientSettings(new Uri("https://my-elasticsearch-instance.cloud"))
    .Authentication(new BasicAuthentication("elastic", "my_password"));

kernelBuilder.AddElasticsearchVectorStoreRecordCollection&lt;string, Hotel&gt;("skhotels", elasticsearchClientSettings);

// Build the host.
using var host = builder.Build();

// For demo purposes, we access the services directly without using a DI context.

var kernel = host.Services.GetService&lt;Kernel&gt;()!;
var embeddings = host.Services.GetService&lt;ITextEmbeddingGenerationService&gt;()!;
var vectorStoreCollection = host.Services.GetService&lt;IVectorStoreRecordCollection&lt;string, Hotel&gt;&gt;()!;

// Register search plugin.
var textSearch = host.Services.GetService&lt;VectorStoreTextSearch&lt;Hotel&gt;&gt;()!;
kernel.Plugins.Add(textSearch.CreateWithGetTextSearchResults("SearchPlugin"));<p>Le service <code>vectorStoreCollection</code> peut maintenant être utilisé pour créer la collection et ingérer quelques <a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">enregistrements de démonstration :</a></p>await vectorStoreCollection.CreateCollectionIfNotExistsAsync();

// CSV format: ID;Hotel Name;Description;Reference Link
var hotels = (await File.ReadAllLinesAsync("hotels.csv"))
    .Select(x =&gt; x.Split(';'));

foreach (var chunk in hotels.Chunk(25))
{
    var descriptionEmbeddings = await embeddings.GenerateEmbeddingsAsync(chunk.Select(x =&gt; x[2]).ToArray());
    
    for (var i = 0; i &lt; chunk.Length; ++i)
    {
        var hotel = chunk[i];
        await vectorStoreCollection.UpsertAsync(new Hotel
        {
            HotelId = hotel[0],
            HotelName = hotel[1],
            Description = hotel[2],
            DescriptionEmbedding = descriptionEmbeddings[i],
            ReferenceLink = hotel[3]
        });
    }
}<p>Cela montre comment Semantic Kernel réduit l'utilisation d'un magasin de vecteurs, avec toute sa complexité, à quelques simples appels de méthodes.</p><p>Sous le capot, un nouvel index est créé dans Elasticsearch et tous les mappages de propriétés nécessaires sont créés. Notre ensemble de données est ensuite intégré de manière totalement transparente dans le modèle de stockage et finalement stocké dans l'index. Voici à quoi ressemblent les mappings dans Elasticsearch.</p>{
  "mappings": {
    "properties": {
      "descriptionEmbedding": {
        "dims": 1536,
        "index": true,
        "index_options": {
          "type": "hnsw"
        },
        "similarity": "cosine",
        "type": "dense_vector"
      },
      "hotelName": {
        "type": "keyword"
      },
      "description": {
        "type": "text"
      }
    }
  }
}<p>Le site <code>embeddings.GenerateEmbeddingsAsync()</code> appelle de manière transparente le service Azure AI Embeddings Generation configuré.</p><p>La dernière étape de cette démonstration est encore plus magique.</p><p>Avec un seul appel à <code>InvokePromptAsync</code>, toutes les opérations suivantes sont effectuées lorsque l'utilisateur pose une question sur les données :</p><p>1. L'intégration de la question de l'utilisateur est générée.</p><p>2. Le magasin de vecteurs est parcouru à la recherche d'entrées pertinentes</p><p>3. Les résultats de la requête sont insérés dans un modèle d'invite</p><p>4. La requête proprement dite, sous la forme de l'invite finale, est envoyée au service d'achèvement de chat de l'IA.</p>// Invoke the LLM with a template that uses the search plugin to
// 1. get related information to the user query from the vector store
// 2. add the information to the LLM prompt.
var response = await kernel.InvokePromptAsync(
    promptTemplate: """
                    Please use this information to answer the question:
                    {{#with (SearchPlugin-GetTextSearchResults question)}}
                      {{#each this}}
                        Name: {{Name}}
                        Value: {{Value}}
                        Source: {{Link}}
                        -----------------
                      {{/each}}
                    {{/with}}
                    
                    Include the source of relevant information in the response.

                    Question: {{question}}
                    """,
    arguments: new KernelArguments
    {
        { "question", "Please show me all hotels that have a rooftop bar." },
    },
    templateFormat: "handlebars",
    promptTemplateFactory: new HandlebarsPromptTemplateFactory());<p>Vous vous souvenez des attributs <code>TextSearch*</code> que nous avons définis précédemment dans notre modèle de données ? Ces attributs nous permettent d'utiliser les espaces réservés correspondants dans notre modèle d'invite, qui sont automatiquement remplis avec les informations provenant de nos entrées dans le magasin de vecteurs.</p><p>La réponse finale à notre question "Veuillez m'indiquer tous les hôtels qui disposent d'un bar sur le toit." est la suivante :</p>Console.WriteLine(response.ToString());

// &gt; The hotel that has a rooftop bar is Skyline Suites. You can find more information about this hotel [here](https://example.com/yz567).<p>La réponse se réfère correctement à l'entrée suivante dans notre fichier hotels.csv</p>9;
Skyline Suites;
Offering panoramic city views from every suite, this hotel is perfect for those who love the urban landscape. Enjoy luxurious amenities, a rooftop bar, and close proximity to attractions. Luxurious and contemporary.;
https://example.com/yz567<p>Cet exemple montre très bien comment l'utilisation de Microsoft Semantic Kernel permet une réduction significative de la complexité grâce à ses abstractions bien pensées, tout en permettant un très haut niveau de flexibilité. En changeant une seule ligne de code, par exemple, le magasin de vecteurs ou les services d'intelligence artificielle utilisés peuvent être remplacés sans qu'il soit nécessaire de remanier une autre partie du code.</p><p>Dans le même temps, le cadre fournit un ensemble considérable de fonctionnalités de haut niveau, telles que la fonction `InvokePrompt`, ou le système de plugin de modèle ou de recherche.</p><p>L'application de démonstration complète se trouve dans le référentiel du connecteur Elasticsearch vector store.</p><h2>Quelles sont les autres possibilités offertes par Elasticsearch ?</h2><ul><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Nouveau mappage semantic_text d'Elasticsearch : Simplifier la recherche sémantique</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-reranking-with-retrievers">Classement sémantique dans Elasticsearch à l'aide d'extracteurs</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">Techniques avancées de RAG partie 1 : Traitement des données</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">Techniques avancées de RAG, partie 2 : Requêtes et tests</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-rag-with-llama3-opensource-and-elastic">Construire RAG avec Llama 3 open-source et Elastic</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/local-rag-agent-elasticsearch-langgraph-llama3">Un tutoriel sur la construction d'un agent local utilisant LangGraph, LLaMA3 et Elasticsearch vector store à partir de zéro</a></p></li></ul><h2>Elasticsearch &amp; Semantic Kernel : Quelle est la prochaine étape ?</h2><ul><li><p>Nous avons montré comment le magasin vectoriel Elasticsearch peut être facilement intégré au Semantic Kernel lors de la construction d'applications GenAI en .NET. Restez à l'écoute pour une prochaine intégration de Python.</p></li><li><p>Comme Semantic Kernel construit des abstractions pour des fonctions de recherche avancées telles que la <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">recherche hybride</a>, la connexion Elasticsearch permettra aux développeurs .NET de les mettre en œuvre facilement tout en utilisant Semantic Kernel.</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</guid>
    <category><![CDATA[IA]]></category>
    <category><![CDATA[.NET]]></category>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Florian Bernd,Srikanth Manvi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d8725035e86f8a8/6a17fe447f6f1564f8c09d74/0564fe794e4c66d0507317822d7aa71826183d20-1311x762.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 06 Dec 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Comment utiliser la recherche hybride pour un catalogue de produits de commerce électronique ?]]></title>
    <description><![CDATA[Apprenez à utiliser la recherche hybride pour créer un catalogue de produits de commerce électronique, en utilisant les facettes, les promotions, la personnalisation et l'analyse comportementale.]]></description>
    <content:encoded><![CDATA[<p>Dans cet article, nous allons montrer comment mettre en œuvre une recherche hybride qui combine les résultats de la recherche en texte intégral et de la recherche vectorielle. En unifiant ces deux approches, la recherche hybride améliore l'étendue des résultats, en tirant le meilleur des deux stratégies de recherche.</p><p>Outre l'intégration de la recherche hybride, nous vous montrerons comment ajouter des fonctionnalités qui rendront votre solution de recherche encore plus robuste. Il s'agit notamment des facettes et des promotions de produits personnalisées. En outre, nous vous montrerons comment capturer les interactions des utilisateurs et générer des informations précieuses à l'aide de l'outil d'analyse comportementale d'Elastic.</p><p>Dans cette mise en œuvre, vous verrez comment construire à la fois l'interface qui permet aux utilisateurs de visualiser et d'interagir avec les résultats de la recherche et l'API responsable du retour des informations. Pour accéder aux dépôts contenant le code source, les liens sont fournis ci-dessous :</p><ul><li><p><a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search">https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search</a></p></li><li><p><a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/app-product-store">https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/app-product-store</a> </p></li></ul><p>Nous avons divisé ce guide en plusieurs étapes, de la création de l'index à la mise en œuvre de fonctionnalités avancées telles que les facettes et la personnalisation des résultats. À la fin, vous aurez une solution de recherche robuste prête à être utilisée dans un scénario de commerce électronique.</p><h2>Configuration de l'environnement pour la recherche hybride dans le domaine du commerce électronique</h2><p>Avant de commencer la mise en œuvre, nous devons mettre en place l'environnement. Vous pouvez choisir d'utiliser un service sur Elastic Cloud ou une solution conteneurisée pour gérer Elasticsearch. Si vous choisissez la conteneurisation, une configuration via Docker Compose peut être trouvée dans ce dépôt : <a href="https://github.com/andreluiz1987/product-store-search/blob/main/docker/docker-compose.yml">docker-compose.yml</a>.</p><h2>Création d'index et ingestion de catalogues de produits</h2><p>L'index sera créé sur la base d'un catalogue de produits cosmétiques, qui comprend des champs tels que le nom, la description, la photo, la catégorie et les étiquettes. Les champs utilisés pour la recherche en texte intégral, tels que "nom" et "description," seront mappés comme <code>text</code>, tandis que les champs utilisés pour les agrégations, tels que "catégorie" et "marque," seront mappés comme <code>keyword</code> pour permettre les facettes.</p><p>Le champ "description" sera utilisé pour la recherche vectorielle, car il fournit plus de détails sur les produits. Ce champ sera défini comme <code>dense_vector,</code> stockant la représentation vectorielle de la description.</p><p>La correspondance de l'index sera la suivante :</p>{
   "mappings":{
      "properties":{
         "id":{
            "type":"keyword"
         },
         "brand":{
            "type":"text",
            "fields":{
               "keyword":{
                  "type":"keyword"
               }
            }
         },
         "name":{
            "type":"text"
         },
         "price":{
            "type":"float"
         },
         "price_sign":{
            "type":"keyword"
         },
         "currency":{
            "type":"keyword"
         },
         "image_link":{
            "type":"keyword"
         },
         "description":{
            "type":"text"
         },
         "description_embeddings":{
            "type":"dense_vector",
            "dims":384
         },
         "rating":{
            "type":"keyword"
         },
         "category":{
            "type":"keyword"
         },
         "product_type":{
            "type":"keyword"
         },
         "tag_list":{
            "type":"keyword"
         }
      }
   }
}<p>Le script de création de l'index est disponible <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/infra/create_index.py">ici.</a></p><h2>Génération de l'intégration</h2><p>Pour vectoriser les descriptions de produits, nous utilisons le modèle all-MiniLM-L6-v2. Dans ce cas, l'application est responsable de la génération des embeddings avant l'indexation. Une autre option serait d'importer le modèle dans le cluster Elasticsearch, mais pour cet environnement local, nous avons choisi d'effectuer la vectorisation directement dans l'application.</p><p>Nous avons utilisé l'ensemble de données sur les cosmétiques disponible sur <a href="https://www.kaggle.com/datasets/shivd24coder/cosmetic-brand-products-dataset">Kaggle</a> pour alimenter l'index, et pour améliorer l'efficacité de l'ingestion des données, nous avons utilisé le traitement par lots. Au cours de cette même étape d'ingestion, nous générerons les embeddings pour le champ "description" et les indexerons dans le nouveau champ "description_embeddings".</p><p>Le processus complet d'ingestion des données peut être suivi et exécuté directement via le <strong>carnet Jupyter</strong> disponible dans le référentiel. Le carnet de notes fournit un guide étape par étape sur la façon dont les données sont lues, traitées et indexées dans Elasticsearch, ce qui permet de les reproduire et de les expérimenter facilement.</p><p>Vous pouvez accéder au carnet en cliquant sur le lien suivant : <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/ingestion/ingestion.ipynb">Cahier d'ingestion.</a></p><h2>Mise en œuvre de la recherche hybride</h2><p>Mettons maintenant en œuvre la recherche hybride. Pour la recherche par mot-clé, nous utilisons la requête <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html">multi_match</a>, ciblant les champs "nom," " catégorie," et "description." Cela permet de s'assurer que les documents contenant le terme de recherche dans n'importe lequel de ces champs sont retrouvés.</p><p>Pour la recherche vectorielle, nous utilisons la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">requête KNN</a>. Le terme de recherche doit être vectorisé avant d'exécuter la requête, ce qui est fait à l'aide de la méthode qui vectorise le terme d'entrée. Notez que le modèle utilisé lors de l'ingestion est également utilisé pour le terme de recherche.</p><p>La combinaison des deux recherches est réalisée à l'aide de l'algorithme <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">Reciprocal Rank Fusion (RRF) </a>, qui fusionne les résultats des deux requêtes et augmente la précision de la recherche en réduisant le bruit. RRF permet aux recherches par mots-clés et aux recherches vectorielles de fonctionner ensemble, améliorant ainsi la compréhension de la requête de l'utilisateur.</p>query = {
   "retriever": {
       "rrf": {
           "retrievers": [
               {
                   "standard": {
                       "query": organic_query['query']
                   }
               },
               {
                   "knn": {
                       "field": "description_embeddings",
                       "query_vector": vector,
                       "k": 5,
                       "num_candidates": 20
                   }
               }
           ],
           "rank_window_size": 20,
           "rank_constant": 5
       }
   },
   "_source": organic_query['_source']
}<h3>Comparaison des résultats : recherche par mot-clé et recherche hybride</h3><p>Comparons maintenant les résultats d'une recherche traditionnelle par mot-clé avec ceux d'une recherche hybride. En recherchant "foundation for dry skin" à l'aide de mots-clés, nous obtenons les résultats suivants :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcb5405df787bc8f5/6a17023e961e697ca1c4cdb4/52e717aa3c9c1fadfadb639f5fb77cf8e47e3b34-1600x1021.png" alt="Comparaison des résultats : Recherche par mot-clé vs. recherche hybride" /><ol><li><p><strong>Revlon ColorStay Makeup pour peau normale / sècheDescription</strong>: Le maquillage Revlon ColorStay offre une couverture longue durée grâce à une formule légère qui ne s'étale pas, ne s'estompe pas et ne s'efface pas. Grâce à la technologie Time Release, cette formule sans huile à l'équilibre hydrique est spécialement conçue pour les peaux normales ou sèches afin de leur apporter une hydratation continue : Le maquillage est confortable et tient jusqu'à 24 heures. Couvrance moyenne à complète.
</p></li><li><p><strong>Fond de teint mousse Maybelline Dream SmoothDescription</strong>: Pourquoi vous allez l'adorerUn fond de teint crème fouettée unique qui offre 100% une perfection lisse comme un bébé.\N- Peau L'apparence et la sensation d'hydratation durent 14 heures - jamais rugueuse ou sèche. La formule légère offre une couverture parfaitement hydratante. Elle s'intègre parfaitement et donne une sensation de fraîcheur tout au long de la journée. Sans huile, sans parfum, testée par des dermatologues, testée contre les allergies, non comédogène. pour les peaux sensibles.</p></li></ol><p><strong>Analyse</strong>: Lors de la recherche de "foundation for dry skin," les résultats ont été obtenus par la correspondance exacte entre les mots-clés de la recherche et les titres et descriptions des produits. Cependant, cette correspondance ne reflète pas toujours le meilleur choix. Par exemple, le <strong>maquillage Revlon ColorStay pour peau normale/sèche</strong> est une bonne option, car il est spécifiquement formulé pour les peaux sèches. Bien qu'elle soit sans huile, sa formule est conçue pour offrir une hydratation continue. En revanche, nous avons également reçu le <strong>fond de teint Maybelline Dream Smooth Mousse</strong>, qui, bien que sans huile et mentionnant l'hydratation, est généralement plus recommandé pour les peaux grasses ou mixtes, car les produits sans huile ont tendance à se concentrer sur le contrôle du sébum plutôt qu'à fournir l'hydratation supplémentaire nécessaire aux peaux sèches. Cela met en évidence les limites des recherches par mots clés, qui peuvent renvoyer des produits ne répondant pas entièrement aux besoins spécifiques des personnes ayant la peau sèche.</p><p>Maintenant, si l'on effectue la même recherche en utilisant l'approche hybride :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt412e38b327cf6a4f/6a17024066c4f94c52f8beb9/13b562a5120619355ba0861976102e208964a49f-1600x1021.png" alt="Effectuer une recherche à l'aide de l'approche hybride" /><ol><li><p><strong>CoverGirl Outlast Stay Luminous Foundation Creamy Natural (820):Description</strong>: Le fond de teint Outlast Stay Luminous de CoverGirl est parfait pour obtenir un fini rosé et un éclat subtil. Il est sans huile, avec une formule non grasse qui donne à votre peau une luminosité naturelle qui dure toute la journée ! Ce fond de teint, qui dure toute la journée, hydrate la peau tout en offrant une couverture parfaite.

<strong>Analyse : </strong>Ce produit est pertinent car il met l'accent sur l'hydratation, ce qui est essentiel pour les utilisateurs à la peau sèche. Les termes "hydrates skin" et "dewy finish" correspondent à l'intention de l'utilisateur de trouver un fond de teint pour les peaux sèches. La recherche vectorielle a probablement compris le concept d'hydratation et l'a associé à la nécessité d'un fond de teint qui réponde aux besoins des peaux sèches.
</p></li><li><p><strong>Revlon ColorStay Makeup pour peau normale / sèche:Description :</strong> Le maquillage Revlon ColorStay offre une couverture longue durée grâce à une formule légère qui ne s'estompe pas, ne se décolore pas et ne s'efface pas. Grâce à la technologie Time Release, cette formule sans huile et équilibrant l'hydratation est spécialement conçue pour les peaux normales ou sèches afin de leur apporter une hydratation continue.

<strong>Analyse : </strong>Ce produit répond directement aux besoins des utilisateurs ayant la peau sèche, en mentionnant explicitement qu'il est formulé pour les peaux normales ou sèches. La formule d'équilibre hydrique "" et l'hydratation continue conviennent parfaitement aux personnes à la recherche d'un fond de teint pour les peaux sèches. La recherche vectorielle a permis d'obtenir ce résultat, non seulement en raison de la correspondance des mots clés, mais aussi parce que l'accent est mis sur l'hydratation et que la peau sèche est spécifiquement mentionnée comme cible démographique.
</p></li><li><p><strong>Fond de teint sérumDescription : </strong>Les fonds de teint sérum sont des formules légères à couvrance moyenne, disponibles dans une gamme complète de 21 teintes. Ces fonds de teint offrent une couvrance modérée d'aspect naturel avec une sensation de sérum très léger. Ils ont une très faible viscosité et sont distribués avec la pompe fournie ou avec le compte-gouttes en verre optionnel disponible séparément si vous le souhaitez.

<strong>Analyse :</strong> Dans ce cas, la description met l'accent sur un fond de teint sérum léger au toucher naturel, ce qui correspond aux besoins des personnes ayant la peau sèche, qui recherchent souvent des produits doux, hydratants et offrant un fini non gélifié. La recherche vectorielle a probablement pris en compte le contexte plus large de la couverture légère et naturelle et de la texture semblable à celle d'un sérum, qui est associée à la rétention de l'humidité et à une application confortable, ce qui la rend pertinente pour les peaux sèches, même si le terme "dry skin" n'est pas explicitement mentionné.</p></li></ol><h2>Mise en œuvre des facettes</h2><p>Les facettes sont essentielles pour affiner et filtrer efficacement les résultats de la recherche, en offrant aux utilisateurs une navigation plus ciblée, en particulier dans les scénarios comportant une grande variété de produits, tels que le commerce électronique. Ils permettent aux utilisateurs d'ajuster les résultats en fonction d'attributs tels que la catégorie, la marque ou le prix, ce qui rend la recherche plus précise. Pour mettre en œuvre cette fonctionnalité, nous utilisons des <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html">agrégations de termes</a> sur les champs <code>category</code> et <code>brand</code>, qui ont été définis comme <code>keyword</code> lors de la phase de création de l'index.</p>    query = build_query(term, categories, product_types, brands)
    query["aggs"] = {
        "product_types": {"terms": {"field": "product_type"}},
        "categories": {"terms": {"field": "category"}},
        "brands": {"terms": {"field": "brand.keyword"}}
    }<p>Le code complet de la mise en œuvre est disponible <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/api/api.py#L144">ici.</a></p><p>Voir ci-dessous les résultats de la recherche de "foundation for dry skin":</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt31080652a60d8600/6a1702416234e0af7ddb18e7/e8907af359c021eaa38ec7a6a53f10c325ee8ade-1146x1248.png" alt="Résultats de la recherche de &quot;foundation for dry skin&quot;" /><h2>Personnalisation des résultats : requêtes épinglées</h2><p>Dans certains cas, il peut être avantageux de promouvoir certains produits dans les résultats de recherche. Pour ce faire, nous utilisons les <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-pinned-query.html"><strong>requêtes épinglées</strong></a>, qui permettent à des produits spécifiques d'apparaître en tête des résultats. Ci-dessous, nous effectuerons une recherche sur le terme "Foundation" sans promouvoir aucun produit :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt31e75c815262993e/6a170243509168ae42e1b95d/9396c4ed358e7a68eed47f17dd6915d0bad492aa-1600x1157.png" alt="Recherchez le terme &quot;Foundation&quot; sans promouvoir de produits." /><p>Dans notre exemple, nous pouvons promouvoir les produits qui portent l'étiquette "Gluten Free." En utilisant les identifiants des produits, nous nous assurons qu'ils sont prioritaires dans les résultats de recherche. Plus précisément, nous ferons la promotion des produits suivants : <strong>Fond de teint sérum</strong> (ID : 1043), <strong>Fond de teint couvrance</strong> (ID : 1042) et <strong>Poudre fixante invisible Realist</strong> (ID : 1039).</p>{
   "query":{
      "pinned":{
         "ids":[
            "1043",
            "1042",
            "1039"
         ],
         "organic":{
            "bool":{
               "must":[
                  {
                     "multi_match":{
                        "query":"foundation",
                        "fields":[
                           "name",
                           "category",
                           "description"
                        ]
                     }
                  }
               ]
            }
         }
      }
   }
}<p>Nous utilisons des identifiants de produits spécifiques pour nous assurer qu'ils sont prioritaires dans les résultats de la recherche. La structure de la requête comprend une liste d'ID de produits qui doivent être "épinglés" en haut (dans ce cas, les ID 1043, 1042 et 1039), tandis que les autres résultats suivent le flux organique de la recherche, en utilisant une combinaison de conditions telles que le texte de la requête dans les champs "nom", "catégorie", et "description". Il est ainsi possible de promouvoir des éléments de manière contrôlée, en assurant leur visibilité, tout en maintenant le reste de la recherche sur la base de la pertinence habituelle.</p><p>Ci-dessous, vous pouvez voir le résultat de l'exécution de la requête avec les produits promus :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt53a6d5cbef227f16/6a1702458b73cb68f0189ef3/8c1a547bfe31360c405eae0891c666635051a51b-1600x1039.png" alt="Résultat de l'exécution de la requête avec les produits promus" /><p>Le code d'interrogation complet est disponible <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/api/api.py#L112">ici.</a></p><h2>Analyser le comportement de recherche avec l'analyse comportementale</h2><p>Jusqu'à présent, nous avons déjà ajouté des fonctionnalités pour améliorer la pertinence des résultats de recherche et faciliter la découverte des produits. Nous allons maintenant finaliser notre solution de recherche en incluant une fonctionnalité qui nous aidera à analyser le comportement de recherche des utilisateurs, en identifiant des modèles tels que les requêtes avec ou sans résultats et les clics sur les résultats de la recherche.pour cela, nous utiliserons la fonctionnalité <strong>Behavioral Analytics</strong> fournie par Elastic. Grâce à lui, en quelques étapes seulement, nous pouvons surveiller et analyser le comportement de recherche des utilisateurs, en obtenant des informations précieuses pour optimiser l'expérience de recherche.</p><h3>Création de la collection d'analyses comportementales</h3><p>Notre première action sera de créer une collection, qui sera responsable de la réception de tous les événements d'analyse du comportement. Pour créer la collection, accédez à l'interface Kibana dans <strong>Search &gt; Behavioral Analytics</strong>. Dans l'exemple ci-dessous, nous avons créé une collection nommée <code>tracking-search</code>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9b4cb5480ab0bfef/6a1702474a531b189936a7e7/c05e533212a3690c5ea9f2d5226cbcdc901c482a-1600x1009.png" alt="Analyse comportementale - nommer votre collection" /><h3>Intégrer l'analyse comportementale dans l'interface</h3><p>Notre application <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/app-product-store">frontale</a> a été développée en JavaScript, et pour intégrer Behavioral Analytics, nous suivrons les étapes décrites dans la documentation officielle d'Elastic pour installer le <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-start.html#behavioral-analytics-start-ui-integration-js-client"><strong>Behavioral Analytics JavaScript Tracker</strong></a>.</p><h3>Mise en œuvre du tracker JavaScript</h3><p>Nous allons maintenant importer le client tracker dans notre application et utiliser les méthodes <code>trackPageView</code>, <code>trackSearch</code>, et <code>trackSearchClick</code> pour capturer les interactions des utilisateurs.</p><p><strong>Avertissement</strong>: Bien que nous utilisions un outil pour collecter les données d'interaction des utilisateurs, il est essentiel d'assurer la conformité avec le <strong>GDPR</strong>. Cela signifie qu'il faut informer clairement les utilisateurs des données collectées et de la manière dont elles seront utilisées, et leur donner la possibilité de refuser le suivi. En outre, nous devons mettre en œuvre de solides mesures de sécurité pour protéger les informations collectées et respecter les droits des utilisateurs, tels que l'accès aux données et leur suppression, en veillant à ce que toutes les étapes soient conformes aux principes du GDPR.
</p><p><strong>Étape 1 : Création de l'instance de suivi</strong></p><p>Tout d'abord, nous allons créer l'instance de suivi qui surveillera les interactions. Dans cette configuration, nous définissons le point de terminaison cible, le nom de la collection et la clé API :</p>createTracker({
  endpoint: "https://endpoint:443",
  collectionName: "tracking-search",
  apiKey: "api-key"
});<p><strong>Étape 2 : Capturer les pages vues</strong></p><p>Pour suivre les pages vues, nous pouvons configurer l'événement <code>trackPageView</code>:</p>    trackPageView({
      page: {
        title: "home-page"
      },
    });<p>Pour plus de détails sur l'événement <code>trackPageView</code>, vous pouvez consulter cette <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-event-reference.html#behavioral-analytics-event-reference-pageview-fields">documentation</a>.</p><p><strong>Étape 3 : Saisir les requêtes de recherche</strong></p><p>Pour suivre les actions de recherche des utilisateurs, nous utiliserons la méthode <code>trackSearch</code>:</p>      trackSearch({
        search: {
          query: searchTerm,
          results: {
            items: documents,
            total_results: response.data.length,
          },
        },
      });<p>Ici, nous collectons le terme de recherche et les résultats de la recherche.</p><p><strong>Étape 4 : Suivi des clics sur les résultats de recherche</strong></p><p>Enfin, pour capturer les clics sur les résultats de recherche, nous utiliserons la méthode <code>trackSearchClick</code>:</p>trackSearchClick({
      document: { id: product.id, index: "products-catalog"},
      search: {
        query: searchTerm,
        page: {
          current: 1,
          size: products.length,
        },
        results: {
          items: documents,
          total_results: products.length,
        },
        search_application: "app-product-store"
      },
    });<p>Nous recueillons des informations sur l'identifiant du document cliqué, ainsi que sur le terme et les résultats de la recherche.</p><h3>Analyse des données dans Kibana</h3><p>Maintenant que les événements d'interaction avec l'utilisateur sont capturés, nous pouvons obtenir des données précieuses sur les actions de recherche. Kibana utilise l'outil d'analyse comportementale pour visualiser et analyser ces données comportementales. Pour afficher les résultats, il suffit de naviguer vers <strong>Search &gt; Behavioral Analytics &gt; My Collection</strong>, où une vue d'ensemble des événements capturés s'affichera.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdab4f4507c5a4732/6a17024860084b34ca3c4411/e219c4af2e01b0ccc1b9079459a07832835abc75-1600x1155.png" alt="Analyse des données dans Kibana" /><p>Cette vue d'ensemble nous donne un aperçu général des événements capturés pour chaque action intégrée dans notre interface. Ces informations nous permettent d'obtenir des informations précieuses sur le comportement de recherche des utilisateurs. Cependant, si vous souhaitez créer des tableaux de bord personnalisés avec des mesures plus pertinentes pour votre scénario spécifique, Kibana offre des outils puissants pour construire des tableaux de bord, vous permettant de créer diverses visualisations de vos mesures.</p><p>Ci-dessous, j'ai créé quelques visualisations et graphiques pour suivre, par exemple, les termes les plus recherchés au fil du temps, les requêtes qui n'ont donné aucun résultat, un nuage de mots mettant en évidence les termes les plus recherchés et, enfin, une visualisation géographique pour identifier d'où vient l'accès à la recherche.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt04ced4406436f942/6a17024a5091685116e1b961/84a2c39dab77a3f9366c258a3a45d2cbc7df125e-1600x689.png" alt="Visualisations et graphiques pour le suivi" /><h2>Conclusion</h2><p>Dans cet article, nous avons mis en œuvre une solution de recherche hybride qui combine la recherche par mot-clé et la recherche vectorielle, offrant ainsi des résultats plus précis et plus pertinents aux utilisateurs. Nous avons également étudié comment utiliser des fonctionnalités supplémentaires, telles que les facettes et la personnalisation des résultats avec les requêtes épinglées, afin de créer une expérience de recherche plus complète et plus efficace.</p><p>En outre, nous avons intégré l'<strong>analyse comportementale</strong> d'Elastic pour capturer et analyser le comportement des utilisateurs au cours de leurs interactions avec le moteur de recherche. En utilisant des méthodes telles que <code>trackPageView</code>, <code>trackSearch</code>, et <code>trackSearchClick</code>, nous avons pu suivre les requêtes de recherche, les clics sur les résultats de recherche et les pages consultées, ce qui nous a permis d'obtenir des informations précieuses sur le comportement de recherche.</p><h2>Références</h2><p>Ensemble de données</p><p><a href="https://www.kaggle.com/datasets/shivd24coder/cosmetic-brand-products-dataset">https://www.kaggle.com/datasets/shivd24coder/cosmetic-brand-products-dataset</a></p><p>Transformateur</p><p><a href="https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2">https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2</a></p><p>Fusion de rangs réciproques</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html</a></p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/retriever.html#rrf-retriever">https://www.elastic.co/guide/en/elasticsearch/reference/current/retriever.html#rrf-retriever</a></p><p>Requête Knn</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html</a></p><p>Requête épinglée</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-pinned-query.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-pinned-query.html</a></p><p>API d'analyse comportementale</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-apis.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-apis.html</a></p><p>https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-overview.html</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hybrid-search-ecommerce</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hybrid-search-ecommerce</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63c711d1bf9b2501/6a17024c66c4f9c2b7f8bebd/05578fc595a12f6b1ebf88a10a2a31e9971b545e-1200x628.png" length="0" type="image/png"/>
    <pubDate>Tue, 12 Nov 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Création d'une application de recherche avec Blazor et Elasticsearch]]></title>
    <description><![CDATA[Apprenez à construire une application de recherche en utilisant Blazor et Elasticsearch, et à utiliser le client Elasticsearch .NET pour la recherche hybride.]]></description>
    <content:encoded><![CDATA[<p>Dans cet article, vous apprendrez à tirer parti de vos compétences en C# pour créer une application de recherche à l'aide de Blazor et d'Elasticsearch. Nous allons utiliser le client <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/introduction.html">Elasticsearch .NET</a> pour exécuter des requêtes de recherche <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/full-text-queries.html">plein texte</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search.html">sémantique</a> et <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">hybride</a>.</p><p><strong>NOTE</strong> Si vous êtes familier avec l'ancienne version du client Elasticsearch C# <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/nest.html">NEST</a>, lisez cet <a href="https://www.elastic.co/search-labs/blog/net-client-evolution">article de blog</a> sur la dépréciation du client NEST et les nouvelles fonctionnalités. <em>NEST était la génération précédente du client .NET, qui a été remplacée par le client </em><code>Elastic.Clients.Elasticsearch package</code></p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca8d1da68641fac0/6a17f65763173044d4585bfb/18f890286ca122cd97286c09ebf740208b802d0b-650x395.png" alt="Construire une application blazor : esre avec diagramme blazor" /><ul><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#what-is-blazor?">Qu'est-ce que Blazor ?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#what-is-esre?">Qu'est-ce que l'ESRE ?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#configuring-elser">Configuration d'ELSER</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#indexing-data">Indexation des données</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#building-the-app-with-blazor-&amp;-elasticsearch">Demande de permis de construire</a></p></li></ul><h2>Qu'est-ce que Blazor ?</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91278ae422883e28/6a17f6592f4a5c3213fa8a95/622741915d016b68bf94f742332d10d736d60052-707x461.png" alt="Serveur Blazor" /><p><a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor">Blazor</a> est un framework web open source basé sur HTML, CSS et C# créé par Microsoft pour permettre aux développeurs de créer des applications web qui s'exécutent sur le client ou le serveur. Blazor vous permet également de créer des composants réutilisables pour construire des applications plus rapidement ; il permet aux développeurs de construire la vue HTML et les actions en C# dans le même fichier, ce qui aide à maintenir un code lisible et propre. De plus, avec Blazor Hybrid, vous pouvez créer des applications mobiles natives accédant aux capacités de la plateforme native via le code .NET.</p><p>Quelques-unes des caractéristiques qui font de Blazor un framework idéal pour travailler :</p><ul><li><p>Options de rendu côté serveur et côté client</p></li><li><p>Composants réutilisables de l'interface utilisateur</p></li><li><p>Mises à jour en temps réel avec SignalR</p></li><li><p>Gestion intégrée des états</p></li><li><p>Système de routage intégré</p></li><li><p>Vérifications solides du typage et de la compilation</p></li></ul><h3>Pourquoi Blazor ?</h3><p>Blazor offre plusieurs avantages par rapport à d'autres cadres et bibliothèques : il permet aux développeurs d'utiliser le langage C# pour le code client et le code serveur, en fournissant un typage fort et une vérification au moment de la compilation, ce qui améliore la fiabilité. Il s'intègre parfaitement à l'écosystème .NET, permettant la réutilisation de bibliothèques et d'outils .NET, et offre un support de débogage robuste.</p><h2>Qu'est-ce que l'ESRE ?</h2><p><a href="https://www.elastic.co/elasticsearch/elasticsearch-relevance-engine">Elasticsearch Relevance Engine™ (ESRE</a> ) est un ensemble d'<a href="https://www.elastic.co/guide/en/esre/current/learn.html">outils permettant de créer des applications de recherche</a> utilisant l'apprentissage automatique et l'intelligence artificielle au-dessus du puissant moteur de recherche Elasticsearch.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf313ba986de01b93/6a17d851505ac306fcad8975/4c0f2645ed1c27fe3ef61a1a9126adadfd8d5368-721x421.png" alt="esre" /><p>Pour en savoir plus sur l'ESRE, vous pouvez lire notre article de blog <a href="https://www.elastic.co/search-labs/blog/introducing-elasticsearch-relevance-engine-esre">ici.</a></p><h2>Configuration d'ELSER</h2><p>Pour tirer parti des capacités <a href="https://www.elastic.co/elasticsearch/elasticsearch-relevance-engine">ESRE</a> d'Elastic, nous allons utiliser <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">ELSER</a> comme fournisseur de modèle.</p><p><em>Remarque : pour utiliser les modèles ELSER d'Elasticsearch, vous devez disposer d'une licence Platinum ou Enterprise, et d'un nœud de Machine Lerning (ML) dédié d'une taille minimale de 4 Go. Pour en savoir plus,</em> <a href="https://www.elastic.co/guide/en/machine-learning/8.15/ml-nlp-elser.html#elser-req"><em>cliquez ici.</em></a></p><p>Commencez par créer le point d'arrivée de l'inférence :</p>PUT _inference/sparse_embedding/my-elser-model
{
  "service": "elser",
  "service_settings": {
    "num_allocations": 1,
    "num_threads": 1
  }
}<p>Si vous utilisez ELSER pour la première fois, il se peut que vous rencontriez une erreur 502 Bad Gateway lorsque le modèle se charge en arrière-plan. Vous pouvez vérifier l'état du modèle sur <code>Machine Learning &gt; Trained Models</code> dans Kibana. Une fois qu'il est déployé, vous pouvez passer à l'étape suivante.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltee295a3516338302/6a17f65b414c640deb94531d/a7ff94b72d337cde892165d744b9f42fba702a87-1440x649.png" alt="Vérification des modèles formés" /><h2>Indexation des données</h2><p>Vous pouvez télécharger le jeu de données <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/esre-with-blazor/books.zip">ici</a> et importer les données à l'aide de Kibana. Pour ce faire, allez sur la page d'accueil et cliquez sur "Upload data". Ensuite, téléchargez le fichier et cliquez sur <code>Import</code>. Enfin, allez dans l'onglet <code>Advanced</code> et collez les mappings suivants :</p>{
   "properties":{
      "authors":{
         "type":"keyword"
      },
      "categories":{
         "type":"keyword"
      },
      "longDescription":{
         "type":"semantic_text",
         "inference_id":"my-elser-model",
         "model_settings":{
            "task_type":"sparse_embedding"
         }
      },
      "pageCount":{
         "type":"integer"
      },
      "publishedDate":{
         "type":"date"
      },
      "shortDescription":{
         "type":"text"
      },
      "status":{
         "type":"keyword"
      },
      "thumbnailUrl":{
         "type":"keyword"
      },
      "title":{
         "type":"text"
      }
   }
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f07300647edd2ca/6a17f65dfaa913172a93ca0c/1aea0b9c51e275f89339f5d463fcaef799fc3943-1235x1083.png" alt="Importer des données" /><p>Nous allons créer un index capable d'exécuter des requêtes sémantiques et en texte intégral. Le type de champ <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html">semantic_text</a> se chargera du découpage et de l'intégration des données. <em>Notez que nous indexons</em> <em><code>longDescription</code></em> <em>comme</em> <em><code>semantic_text</code></em>,<em> vous pouvez utiliser copy_to si vous voulez indexer un champ comme</em> <em><code>semantic_text</code></em> <em>et text`.</em></p><h2>Construire l'application avec Blazor &amp; Elasticsearch</h2><h3>Clé API</h3><p>La première chose à faire est de créer une clé API pour authentifier nos requêtes à Elasticsearch. La clé API doit être en lecture seule et n'être autorisée qu'à interroger l'index <code>books-blazor</code>.</p>POST /_security/api_key
{
  "name": "books-blazor-key",
  "role_descriptors": {
    "books-blazor-reader": {
      "indices": [
        {
          "names": ["books-blazor"],
          "privileges": ["read"]
        }
      ]
    }
  }
}<p>Vous verrez quelque chose comme ceci :
</p>{
  "id": "XXXXXXXXXXXXXXXXXXXXXXXX",
  "name": "books-blazor-key",
  "api_key": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "encoded": "XXXXXXXXXXXXXXXXXXXXXXXX=="
}<p>Enregistrez la valeur du champ de réponse <code>encoded</code> car vous en aurez besoin ultérieurement. Si vous travaillez sur <a href="https://www.elastic.co/cloud/">Elastic Cloud</a>, vous aurez également besoin de votre Cloud ID. (Vous pouvez le trouver <a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud#finding-your-cloud-id">ici</a>).</p><h4>Création du projet Blazor</h4><p>Commencez par installer Blazor et créez un projet d'exemple en suivant les <a href="https://dotnet.microsoft.com/en-us/learn/aspnet/blazor-tutorial/install">instructions officielles.</a></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5c20b896fe1e98e/6a17f65f6317301566585bff/f5720867d960c91fd1b3c4ae9be174e062e6b2fc-975x830.png" alt="Blazor tutorial - Création du projet Blazor" /><p>Une fois le projet créé, la structure des dossiers et des fichiers doit ressembler à ceci :</p>BlazorApp/
|-- BlazorApp.csproj
|-- BlazorApp.sln
|-- Program.cs
|-- appsettings.Development.json
|-- appsettings.json
|-- Properties/
|   `-- launchSettings.json
|-- Components/
|   |-- App.razor
|   |-- Routes.razor
|   |-- _Imports.razor
|   |-- Layout/
|   |   |-- MainLayout.razor
|   |   |-- MainLayout.razor.css
|   |   |-- NavMenu.razor
|   |   `-- NavMenu.razor.css
|   `-- Pages/
|       |-- Counter.razor
|       |-- Error.razor
|       |-- Home.razor
|       `-- Weather.razor
|-- wwwroot/
|-- bin/
`-- obj/ <p>Le modèle d'application comprend <a href="https://blog.getbootstrap.com/2021/08/04/bootstrap-5-1-0/">Bootstrap v5.1.0</a> pour le stylisme.</p><p>Terminez la configuration du projet en installant le client <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/8.0/installation.html">Elasticsearch .NET :</a></p>dotnet add package Elastic.Clients.Elasticsearch<p>Une fois cette étape terminée, votre page devrait ressembler à ceci :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3b90e394ded7ff56/6a17f660faa913d49f93ca10/ef9d2186c2cf49e78a307c4aa69c51632e84578d-940x529.png" alt="Blazor hello world" /><h3>Structure des dossiers</h3><p>Nous allons maintenant organiser nos dossiers comme suit :</p>BlazorApp/
|-- Components/
|   |-- Pages/
|   |   |-- Search.razor
|   |   `-- Search.razor.css
|   `-- Elasticsearch/
|       |-- SearchBar.razor
|       |-- Results.razor
|       `-- Facet.razor
|-- Models/
|   |-- Book.cs
|   `-- Response.cs
`-- Services/
    `-- ElasticsearchService.cs<p>Les dossiers sont expliqués :</p><ul><li><p>Components/Pages/Search.razor : page principale contenant la barre de recherche, les résultats et les filtres.</p></li><li><p>Composants/Pages/Search.razor.css : les styles de page.</p></li><li><p>Components/Elasticsearch/SearchBar.razor : composant de barre de recherche.</p></li><li><p>Components/Elasticsearch/Results.razor : composant de résultats.</p></li><li><p>Components/Elasticsearch/Facet.razor : composant de filtres.</p></li><li><p>Components/Svg/GlassIcon.razor : icône de recherche.</p></li><li><p>Components/_Imports.razor : ce fichier importera tous les composants.</p></li><li><p>Models/Book.cs : ce fichier contient le schéma du champ livre.</p></li><li><p>Models/Response.cs : ce fichier stocke le schéma de la réponse, y compris les résultats de la recherche, les facettes et le nombre total de résultats.</p></li><li><p>Services/ElasticsearchService.cs : Service Elasticsearch. Il gère la connexion et les requêtes à Elasticsearch.</p></li></ul><h4>Configuration initiale</h4><p>Commençons par un peu de nettoyage.</p><p>Supprimer les fichiers :</p><ul><li><p>Composants/Pages/Compteur.razor</p></li><li><p>Composants/Pages/Météo.razor</p></li><li><p>Composants/Pages/Home.razor</p></li><li><p>Components/Layout/NavMenu.razor</p></li><li><p>Composants/Layout/NavMenu.razor.css</p></li></ul><p>Vérifiez le fichier <code>/Components/_Imports.razor</code>. Vous devriez avoir les importations suivantes :</p>@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorApp
@using BlazorApp.Components<h4>Intégrer Elastic dans le projet</h4><p>Maintenant, importons les composants d'Elasticsearch :</p>@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorApp
@using BlazorApp.Components
@using BlazorApp.Components.Elasticsearch @* &lt;--- Add this line *@<p>Nous allons supprimer la barre latérale par défaut afin de disposer de plus d'espace pour notre application en la supprimant du fichier <code>/Components/Layout/MainLayout.razor</code>:</p>@inherits LayoutComponentBase

&lt;div class="page"&gt;
    &lt;main&gt;
        &lt;article class="content"&gt;
            @Body
        &lt;/article&gt;
    &lt;/main&gt;
&lt;/div&gt;

&lt;div id="blazor-error-ui"&gt;
    An unhandled error has occurred.
    &lt;a href="" class="reload"&gt;Reload&lt;/a&gt;
    &lt;a class="dismiss"&gt;🗙&lt;/a&gt;
&lt;/div&gt;<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt441012c5aeac85f8/6a17f661ec0f8949f15a67ac/55bfff607f9167a532f83ad074b479364a346c6e-816x472.png" alt="barre de navigation supprimée de la mise en page" /><p>Entrons maintenant les informations d'identification Elasticsearch pour les <a href="https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-8.0&amp;tabs=linux#secret-manager">secrets d'utilisateur</a>:</p>dotnet user-secrets init
dotnet user-secrets set ElasticsearchCloudId "your Cloud ID"
dotnet user-secrets set ElasticsearchApiKey "your API Key"<p>Grâce à cette approche, .Net 8 stocke les données sensibles dans un emplacement distinct, en dehors du dossier du projet, et les rend accessibles à l'aide de l'interface <code>IConfiguration</code>. Ces variables seront disponibles pour tout projet .Net qui utilise les mêmes secrets d'utilisateur.</p><p>Ensuite, modifions le fichier <code>Program.cs</code> pour lire les secrets et monter le client Elasticsearch :</p><p>Tout d'abord, il faut importer les bibliothèques nécessaires :</p>using BlazorApp.Services;
using Elastic.Clients.Elasticsearch;
using Elastic.Transport;<ul><li><p>BlazorApp.Services : contient le service Elasticsearch.</p></li><li><p>Elastic.Clients.Elasticsearch : importe la bibliothèque .Net 8 du client Elasticsearch.</p></li><li><p>Elastic.Transport : importe la bibliothèque de transport Elasticsearch, ce qui nous permet d'utiliser la classe ApiKey pour authentifier nos requêtes.</p></li></ul><p>Deuxièmement, insérez le code suivant avant la ligne <code>var app = builder.Build()</code>:</p>// Initialize the Elasticsearch client.
builder.Services.AddScoped(sp =&gt;
{
    // Getting access to the configuration service to read the Elasticsearch credentials.
    var configuration = sp.GetRequiredService&lt;IConfiguration&gt;();
    var cloudId = configuration["ElasticsearchCloudId"];
    var apiKey = configuration["ElasticsearchApiKey"];

    if (string.IsNullOrEmpty(cloudId) || string.IsNullOrEmpty(apiKey))
    {
        throw new InvalidOperationException(
            "Elasticsearch credentials are missing in configuration."
        );
    }

    var settings = new ElasticsearchClientSettings(cloudId, new ApiKey(apiKey)).EnableDebugMode();
    return new ElasticsearchClient(settings);
});<p>Ce code va lire les informations d'identification Elasticsearch à partir des secrets d'utilisateur et créer une instance de client Elasticsearch.</p><p>Après l'initialisation du client ElasticSearch, ajoutez la ligne suivante pour enregistrer le service Elasticsearch :</p>builder.Services.AddScoped&lt;ElasticsearchService&gt;();<p>L'étape suivante consistera à construire la logique de recherche dans le fichier <code>/Services/ElasticsearchService.cs</code>:</p><p>Tout d'abord, il faut importer les bibliothèques et les modèles nécessaires :</p>using BlazorApp.Models;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.QueryDsl;<p>Ensuite, ajoutez la classe <code>ElasticsearchService</code>, le constructeur et les variables :</p>namespace BlazorApp.Services
{
    public class ElasticsearchService
    {
        private readonly ElasticsearchClient _client;

        // The logger is used to log information, warnings and errors about the Elasticsearch service and requests.
        private readonly ILogger&lt;ElasticsearchService&gt; _logger;

        public ElasticsearchService(
            ElasticsearchClient client,
            ILogger&lt;ElasticsearchService&gt; logger
        )
        {
            _client = client ?? throw new ArgumentNullException(nameof(client));
            _logger = logger;
        }
    }
}<h4>Configuration de la recherche</h4><p>Construisons maintenant notre logique de recherche :</p>private static Action&lt;RetrieverDescriptor&lt;BookDoc&gt;&gt; BuildHybridQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    return retrievers =&gt;
        retrievers.Rrf(rrf =&gt;
            rrf.RankWindowSize(50)
                .RankConstant(20)
                .Retrievers(
                    retrievers =&gt;
                        retrievers.Standard(std =&gt;
                            std.Query(q =&gt;
                                q.Bool(b =&gt;
                                    b.Must(m =&gt;
                                            m.MultiMatch(mm =&gt;
                                                mm.Query(searchTerm)
                                                    .Fields(
                                                        new[]
                                                        {
                                                            "title",
                                                            "shortDescription",
                                                        }
                                                    )
                                            )
                                        )
                                        .Filter(filters.ToArray())
                                )
                            )
                        ),
                    retrievers =&gt;
                        retrievers.Standard(std =&gt;
                            std.Query(q =&gt;
                                q.Bool(b =&gt;
                                    b.Must(m =&gt;
                                            m.Semantic(sem =&gt;
                                                sem.Field("longDescription")
                                                    .Query(searchTerm)
                                            )
                                        )
                                        .Filter(filters.ToArray())
                                )
                            )
                        )
                )
        );
}

public static List&lt;Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt;&gt; BuildFilters(
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = new List&lt;Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt;&gt;();

    if (selectedFacets != null)
    {
        foreach (var facet in selectedFacets)
        {
            foreach (var value in facet.Value)
            {
                var field = facet.Key.ToLower();
                if (!string.IsNullOrEmpty(field))
                {
                    filters.Add(m =&gt; m.Term(t =&gt; t.Field(new Field(field)).Value(value)));
                }
            }
        }
    }

    return filters;
}<ul><li><p><code>BuildFilters</code> construira les filtres pour la requête de recherche en utilisant les facettes sélectionnées par l'utilisateur.</p></li><li><p><code>BuildHybridQuery</code> construira une requête de <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">recherche hybride</a> qui combine la recherche plein texte et la recherche sémantique.</p></li></ul><p>Ensuite, ajoutez la méthode de recherche :</p>public async Task&lt;ElasticResponse&gt; SearchBooksAsync(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    try
    {
        _logger.LogInformation($"Performing search for: {searchTerm}");

        // Retrieve the hybrid query with filters applied.
        var retrieverQuery = BuildHybridQuery(searchTerm, selectedFacets);

        var response = await _client.SearchAsync&lt;BookDoc&gt;(s =&gt;
            s.Index("elastic-blazor-books")
                .Retriever(retrieverQuery)
                .Aggregations(aggs =&gt;
                    aggs.Add("Authors", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Authors)))
                        .Add(
                            "Categories",
                            agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Categories))
                        )
                        .Add("Status", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Status)))
                )
        );

        if (response.IsValidResponse)
        {
            _logger.LogInformation($"Found {response.Documents.Count} documents");

            var hits = response.Total;
            var facets =
                response.Aggregations != null
                    ? FormatFacets(response.Aggregations)
                    : new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

            var elasticResponse = new ElasticResponse
            {
                TotalHits = hits,
                Documents = response.Documents.ToList(),
                Facets = facets,
            };

            return elasticResponse;
        }
        else
        {
            _logger.LogWarning($"Invalid response: {response.DebugInformation}");
            return new ElasticResponse();
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error performing search");
        return new ElasticResponse();
    }
}

public static Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; FormatFacets(
    Elastic.Clients.Elasticsearch.Aggregations.AggregateDictionary aggregations
)
{
    var facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

    foreach (var aggregation in aggregations)
    {
        if (
            aggregation.Value
            is Elastic.Clients.Elasticsearch.Aggregations.StringTermsAggregate termsAggregate
        )
        {
            var facetName = aggregation.Key;
            var facetDictionary = ConvertFacetDictionary(
                termsAggregate.Buckets.ToDictionary(b =&gt; b.Key, b =&gt; b.DocCount)
            );
            facets[facetName] = facetDictionary;
        }
    }

    return facets;
}

private static Dictionary&lt;string, long&gt; ConvertFacetDictionary(
    Dictionary&lt;Elastic.Clients.Elasticsearch.FieldValue, long&gt; original
)
{
    var result = new Dictionary&lt;string, long&gt;();
    foreach (var kvp in original)
    {
        result[kvp.Key.ToString()] = kvp.Value;
    }
    return result;
}<ul><li><p><code>SearchBooksAsync</code>: effectuera la recherche en utilisant la requête hybride et renverra les résultats en incluant les agrégations pour construire les facettes.</p></li><li><p><code>FormatFacets</code>: formatera la réponse des agrégations dans un dictionnaire.</p></li><li><p><code>ConvertFacetDictionary</code>: convertit le dictionnaire de facettes dans un format plus lisible.</p></li></ul><p>L'étape suivante consiste à créer les modèles qui représenteront les données renvoyées dans le site <code>hits</code> de la requête Elasticsearch et qui seront imprimées en tant que résultats dans notre page de recherche.</p><p>Nous commençons par créer le fichier <code>/Models/Book.cs</code> et y ajouter ce qui suit :</p>namespace BlazorApp.Models
{
    public class BookDoc
    {
        public string? Title { get; set; }
        public int? PageCount { get; set; }
        public string? PublishedDate { get; set; }
        public string? ThumbnailUrl { get; set; }
        public string? ShortDescription { get; set; }
        public LongDescription? LongDescription { get; set; }
        public string? Status { get; set; }
        public List&lt;string&gt;? Authors { get; set; }
        public List&lt;string&gt;? Categories { get; set; }
    }

    public class LongDescription
    {
        public string? Text { get; set; }
    }
}<p>Ensuite, il faut configurer la réponse Elastic dans le fichier <code>/Models/Response.cs</code> et ajouter ce qui suit :</p>namespace BlazorApp.Models
{
    public class ElasticResponse
    {
        public ElasticResponse()
        {
            Documents = new List&lt;BookDoc&gt;();
            Facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();
        }

        public long TotalHits { get; set; }
        public List&lt;BookDoc&gt; Documents { get; set; }
        public Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; Facets { get; set; }
    }
}<h4>Configuration d'une interface utilisateur de base</h4><p>Ensuite, ajoutez le composant SearchBar. Dans le fichier <code>/Components/Elasticsearch/SearchBar.razor</code>, ajoutez ce qui suit :</p>@using System.Threading.Tasks

&lt;form @onsubmit="SubmitSearch"&gt;
  &lt;div class="input-group mb-3"&gt;
    &lt;input type="text" @bind-value="searchTerm" class="form-control" placeholder="Enter search term..." /&gt;
    &lt;button type="submit" class="btn btn-primary input-btn"&gt;
      &lt;span class="input-group-svg"&gt;
        Search
      &lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/form&gt;

@code {
  [Parameter]
  public EventCallback&lt;string&gt; OnSearch { get; set; }

  private string searchTerm = "";

  private async Task SubmitSearch()
  {
    await OnSearch.InvokeAsync(searchTerm);
  }
}<p>Ce composant contient une barre de recherche et un bouton pour effectuer la recherche.</p><p>Blazor offre une grande flexibilité en permettant de générer du HTML dynamiquement en utilisant du code C# dans le même fichier.</p><p>Ensuite, dans le fichier <code>/Components/Elasticsearch/Results.razor</code>, nous construirons le composant de résultats qui affichera les résultats de la recherche :</p>@using BlazorApp.Models

@if (SearchResults != null &amp;&amp; SearchResults.Any())
{
  &lt;div class="row"&gt;
  @foreach (var result in SearchResults)
    {
      &lt;div class="col-12 mb-3"&gt;
        &lt;div class="card"&gt;
          &lt;div class="row g-0"&gt;
            &lt;div class="col-md-3 image-container"&gt;
              @if (!string.IsNullOrEmpty(result?.ThumbnailUrl))
              {
                &lt;img src="@result?.ThumbnailUrl" class="img-fluid rounded-start" alt="Thumbnail"&gt;
              }
              else
              {
                &lt;div class="placeholder"&gt;
                  @result?.Title
                &lt;/div&gt;
              }
            &lt;/div&gt;

            &lt;div class="col-md-9"&gt; &lt;!-- Adjusted to use the remaining 75% --&gt;
              &lt;div class="card-body"&gt;
                &lt;h4 class="card-title"&gt;
                  @result?.Title
                &lt;/h4&gt;

                &lt;div class="details-container"&gt;
                  &lt;div class=""&gt;

                    @if (result?.Authors?.Any() == true)
                    {
                      &lt;p class="card-text p-first"&gt;
                        Authors: &lt;small class="text-muted"&gt;@string.Join(", ", result.Authors)&lt;/small&gt;
                      &lt;/p&gt;
                    }

                    @if (result?.Categories?.Any() == true)
                    {
                      &lt;p class="card-text p-second"&gt;
                        Categories: &lt;small class="text-muted"&gt;@string.Join(", ", result.Categories)&lt;/small&gt;
                      &lt;/p&gt;
                    }
                  &lt;/div&gt;
                  &lt;div class="numPages-status"&gt;
                    @if (result?.PageCount != null)
                    {
                      &lt;p class="card-text p-first"&gt;
                        Pages: &lt;small class="text-muted"&gt;@result.PageCount&lt;/small&gt;
                      &lt;/p&gt;
                    }

                    @if (result?.Status != null)
                    {
                      &lt;p class="card-text p-second"&gt;
                        Status: &lt;small class="text-muted"&gt;@result.Status&lt;/small&gt;
                      &lt;/p&gt;
                    }
                  &lt;/div&gt;
                &lt;/div&gt;

                &lt;div class="long-text-container"&gt;
                  &lt;p class="card-text"&gt;&lt;small class="text-muted"&gt;@result?.LongDescription?.Text&lt;/small&gt;&lt;/p&gt;
                &lt;/div&gt;
                @if (!string.IsNullOrEmpty(result?.PublishedDate))
                {
                  &lt;div class="date-container"&gt;
                    &lt;p class="card-text"&gt;
                      Published Date: &lt;small class="text-muted small-date"&gt;@FormatDate(result.PublishedDate)&lt;/small&gt;
                    &lt;/p&gt;
                  &lt;/div&gt;
                }
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    }
  &lt;/div&gt;
}
else if (SearchResults != null)
{
  &lt;p&gt;No results found.&lt;/p&gt;
}

@code {
  [Parameter]
  public List&lt;BookDoc&gt; SearchResults { get; set; } = new List&lt;BookDoc&gt;();

  private string FormatDate(string? date)
  {
    if (DateTime.TryParse(date, out DateTime parsedDate))
    {
      return parsedDate.ToString("MMMM dd, yyyy");
    }
    return "";
  }
}<p>Enfin, nous devons créer des facettes pour filtrer les résultats de la recherche.</p><p><em>Remarque : les facettes sont des filtres qui permettent aux utilisateurs d'affiner les résultats de leur recherche en fonction d'attributs ou de catégories spécifiques, tels que le type de produit, la fourchette de prix ou la marque. Ces filtres sont généralement présentés sous forme d'options cliquables, souvent sous forme de cases à cocher, afin d'aider les utilisateurs à affiner leur recherche et à trouver plus facilement des résultats pertinents. Dans le contexte d'Elasticsearch, les facettes sont créées à l'aide d'</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html"><em>agrégations</em></a><em>.</em></p><p>Nous configurons les facettes en plaçant le code suivant dans le fichier <code>/Components/Elasticsearch/Facet.razor</code>:</p>@if (Facets != null)
{
  &lt;div class="facets-container"&gt;
  @foreach (var facet in Facets)
    {
      &lt;h3&gt;@facet.Key&lt;/h3&gt;
      @foreach (var option in facet.Value)
      {
        &lt;div&gt;
          &lt;input type="checkbox" checked="@IsFacetSelected(facet.Key, option.Key)"
            @onclick="() =&gt; ToggleFacet(facet.Key, option.Key)" /&gt;
          @option.Key (@option.Value)
        &lt;/div&gt;
      }
    }
  &lt;/div&gt;
}


@code {
  [Parameter]
  public Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;? Facets { get; set; }

  [Parameter]
  public EventCallback&lt;Dictionary&lt;string, List&lt;string&gt;&gt;&gt; OnFacetChanged { get; set; }

  private Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets = new();

  private void ToggleFacet(string facetName, string facetValue)
  {
    if (!selectedFacets.TryGetValue(facetName, out var facetValues))
    {
      facetValues = selectedFacets[facetName] = new List&lt;string&gt;();
    }

    if (!facetValues.Remove(facetValue))
    {
      facetValues.Add(facetValue);
    }

    OnFacetChanged.InvokeAsync(selectedFacets);
  }

  private bool IsFacetSelected(string facetName, string facetValue)
  {
    return selectedFacets.ContainsKey(facetName) &amp;&amp; selectedFacets[facetName].Contains(facetValue);
  }
}<p>Ce composant lit une agrégation <code>terms</code> sur les champs <code>author</code>, <code>categories</code> et <code>status</code>, puis produit une liste de filtres à renvoyer à Elasticsearch.</p><p>Mettons tout cela bout à bout.</p><p>Dans le fichier <code>/Components/Pages/Search.razor</code>:</p>@page "/"
@rendermode InteractiveServer
@using BlazorApp.Models
@using BlazorApp.Services
@inject ElasticsearchService ElasticsearchService
@inject ILogger&lt;Search&gt; Logger

&lt;PageTitle&gt;Search&lt;/PageTitle&gt;

&lt;div class="top-row px-4 "&gt;

    &lt;div class="searchbar-container"&gt;
        &lt;h4&gt;Semantic Search with Elasticsearch and Blazor&lt;/h4&gt;

        &lt;SearchBar OnSearch="PerformSearch" /&gt;
    &lt;/div&gt;

    &lt;a href="https://www.elastic.co/search-labs/esre-with-blazor" target="_blank"&gt;About&lt;/a&gt;
&lt;/div&gt;

&lt;div class="px-4"&gt;

    &lt;div class="search-details-container"&gt;
        &lt;p role="status"&gt;Current search term: @currentSearchTerm&lt;/p&gt;
        &lt;p role="status"&gt;Total results: @totalResults&lt;/p&gt;
    &lt;/div&gt;

    &lt;div class="results-facet-container"&gt;
        &lt;div class="facets-container"&gt;
            &lt;Facet Facets="facets" OnFacetChanged="OnFacetChanged" /&gt;
        &lt;/div&gt;
        &lt;div class="results-container"&gt;
            &lt;Results SearchResults="searchResults" /&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

@code {
    private string currentSearchTerm = "";
    private long totalResults = 0;
    private List&lt;BookDoc&gt; searchResults = new List&lt;BookDoc&gt;();
    private Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();
    private Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets = new Dictionary&lt;string, List&lt;string&gt;&gt;();

    protected override async Task OnInitializedAsync()
    {
        await PerformSearch();
    }

    private async Task PerformSearch(string searchTerm = "")
    {
        try
        {
            currentSearchTerm = searchTerm;

            var response = await ElasticsearchService.SearchBooksAsync(currentSearchTerm, selectedFacets);
            if (response != null)
            {
                searchResults = response.Documents;
                facets = response.Facets;
                totalResults = response.TotalHits;
            }
            else
            {
                Logger.LogWarning("Search response is null.");
            }

            StateHasChanged();
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Error performing search.");
        }
    }

    private async Task OnFacetChanged(Dictionary&lt;string, List&lt;string&gt;&gt; newSelectedFacets)
    {
        selectedFacets = newSelectedFacets;
        await PerformSearch(currentSearchTerm);
    }
}<p>Notre page fonctionne !</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc750e55fff43de4b/6a17f6636864a4ab08b68931/5f0f25cb029a34df7f577a9f307278226ed07c3f-816x473.png" alt="Exemple de page Blazor" /><p>Comme vous pouvez le constater, la page est fonctionnelle mais manque de style. Ajoutons quelques feuilles de style CSS pour le rendre plus organisé et plus réactif.</p><p>Commençons par remplacer les styles de présentation. Dans le fichier <code>Components/Layout/MainLayout.razor.css</code>:</p>.page {
  position: relative;
  display: flex;
  flex-direction: column;
}

main {
  flex: 1;
}

#blazor-error-ui {
  background: lightyellow;
  bottom: 0;
  box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
  display: none;
  left: 0;
  padding: 0.6rem 1.25rem 0.7rem 1.25rem;
  position: fixed;
  width: 100%;
  z-index: 1000;
}

#blazor-error-ui .dismiss {
  cursor: pointer;
  position: absolute;
  right: 0.75rem;
  top: 0.5rem;
}<p>Ajoutez les styles pour la page de recherche dans le fichier <code>Components/Pages/Search.razor.css</code>:</p>.input-group .input-group-svg {
  background: transparent;
  border: transparent;
  pointer-events: none;
}

.results-facet-container {
  display: flex;
  margin-top: 1rem;
  overflow-x: auto;
}

.search-details-container {
  display: flex;
  justify-content: space-between;
  margin-top: 1rem;
}

.searchbar-container {
  padding-top: 2rem;
  display: flex;
  flex-direction: column; 
  flex-grow: 1;
  height: 100%;
  max-width: 100%; 
}

.searchbar-container h4 {
  margin: 0;
}

.top-row {
  margin-top: -1.1rem;
  position: relative; 
  background-color: hsl(216, 29%, 67%);
  border-bottom: 1px solid #d6d5d5;
  display: flex;
  align-items: center;
  height: 100%;
  padding: 0 1rem;
}

.top-row a {
  margin-left: auto;
  margin-top: -4rem; 
  color: #000000;
  text-decoration: none;
}

.top-row a:hover {
  text-decoration: underline;
}

@media (max-width: 640.98px) {
  .top-row {
    justify-content: space-between;
  }

  .top-row ::deep a,
  .top-row ::deep .btn-link {
    margin-left: 0;
  }
}

@media (min-width: 641px) {
  .top-row.auth ::deep a:first-child {
    flex: 1;
    text-align: right;
    width: 0;
  }

  .top-row,
  article {
    padding-left: 2rem !important;
    padding-right: 1.5rem !important;
  }
}<p>Notre page commence à s'améliorer :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ecc49f404f18591/6a17f6654b055d959b432384/3dadd7ade2dd9070300a4e0514a4da2ae9cc9fb9-817x473.png" alt="Page Blazor après l'ajout de styles pour la page de recherche" /><p>Mettons-y les dernières touches :</p><p>Créez les fichiers suivants :</p><ul><li><p>Composants/Elasticsearch/Facet.razor.css</p></li><li><p>Composants/Elasticsearch/Results.razor.css</p></li></ul><p>Et ajoutez les styles pour <code>Facet.razor.css</code>:</p>.facets-container {
  font-size: 15px;
  margin-right: 4rem;
  overflow-x: auto;
  white-space: nowrap;
  max-width: 300px;
}

.results-facet-container {
  display: flex;
  margin-top: 1rem;
  overflow-x: auto;
}

.results-facet-container &gt; * {
  flex-shrink: 0;
}<p>Pour <code>Results.razor.css</code>:</p>.image-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  padding: 1rem;
  box-sizing: border-box;
}

.image-container img {
  max-width: 100%;
  height: auto;
  border-radius: 0.5rem;
}

.placeholder {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  width: 100%;
  background-color: #f0f0f0;
  border: 1px solid #ccc;
  font-size: 0.9rem;
  color: #888;
  text-align: center;
  padding: 1rem;
  border-radius: 0.5rem;
}

.card-body {
  padding: 1rem;
}

.details-container {
  display: flex;
  justify-content: space-between;
  padding: 1.5rem 0;
}

.date-container {
  margin-top: 1rem;
  display: flex;
  justify-content: flex-end;
}

.date-container .small-date {
  font-weight: bold;
}<p>Résultat final :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt78bbd8e6f9e23988/6a17f6664b055d2ed8432388/3e68ec4a38775fdfbeaa1a990e6a1e11dfb081d8-816x472.png" alt="Résultat final de la construction de la page de l'application blazor" /><p>Pour lancer l'application, vous pouvez utiliser la commande suivante :</p><p><code>dotnet watch</code></p><p>Vous avez réussi ! Vous pouvez désormais rechercher des livres dans votre index Elasticsearch en utilisant la barre de recherche et filtrer les résultats par auteur, catégorie et statut.</p><h3>Effectuer une recherche plein texte et sémantique</h3><p>Par défaut, notre application effectuera une <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">recherche hybride</a> utilisant à la fois le <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html">texte intégral</a> et la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html">recherche sémantique</a>. Vous pouvez modifier la logique de recherche en créant deux méthodes distinctes, l'une pour le texte intégral et l'autre pour la recherche sémantique, puis en sélectionnant une méthode pour élaborer la requête en fonction des données saisies par l'utilisateur.</p><p>Ajoutez les méthodes suivantes à la classe <code>ElasticsearchService</code> dans le fichier <code>/Services/ElasticsearchService.cs</code>:</p>private static Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt; BuildSemanticQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    return query =&gt;
        query.Bool(b =&gt;
            b.Must(m =&gt; m.Semantic(sem =&gt; sem.Field("longDescription").Query(searchTerm)))
                .Filter(filters.ToArray())
        );
}

private static Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt; BuildMultiMatchQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    if (string.IsNullOrEmpty(searchTerm))
    {
        return query =&gt; query.Bool(b =&gt; b.Filter(filters.ToArray()));
    }

    return query =&gt;
        query.Bool(b =&gt;
            b.Should(m =&gt;
                    m.MultiMatch(mm =&gt;
                        mm.Query(searchTerm).Fields(new[] { "title", "shortDescription" })
                    )
                )
                .Filter(filters.ToArray())
        );
}<p>Ces deux méthodes fonctionnent de manière similaire à la méthode <code>BuildHybridQuery</code>, mais elles n'effectuent qu'une recherche plein texte ou sémantique.</p><p>Vous pouvez modifier la méthode <code>SearchBooksAsync</code> pour utiliser la méthode de recherche sélectionnée :</p>public async Task&lt;ElasticResponse&gt; SearchBooksAsync(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    try
    {
        _logger.LogInformation($"Performing search for: {searchTerm}");
        
        // Modify the query builder to use the selected search method.
        var multiMatchQuery = BuildMultiMatchQuery(searchTerm, selectedFacets); // For full text search
        var semanticQuery = BuildSemanticQuery(searchTerm, selectedFacets); // For semantic search

        // In this case we will not use retrievers, but you can add them if you want to use them.
        var response = await _client.SearchAsync&lt;BookDoc&gt;(s =&gt;
            s.Index("elastic-blazor-books")
                .Query(multiMatchQuery) // Change this line to use different search methods, for example: .Query(semanticQuery) for semantic search
                .Aggregations(aggs =&gt;
                    aggs.Add("Authors", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Authors)))
                        .Add(
                            "Categories",
                            agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Categories))
                        )
                        .Add("Status", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Status)))
                )
        );

        if (response.IsValidResponse)
        {
            _logger.LogInformation($"Found {response.Documents.Count} documents");

            var hits = response.Total;
            var facets =
                response.Aggregations != null
                    ? FormatFacets(response.Aggregations)
                    : new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

            var elasticResponse = new ElasticResponse
            {
                TotalHits = hits,
                Documents = response.Documents.ToList(),
                Facets = facets,
            };

            return elasticResponse;
        }
        else
        {
            _logger.LogWarning($"Invalid response: {response.DebugInformation}");
            return new ElasticResponse();
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error performing search");
        return new ElasticResponse();
    }
}<p>Vous pouvez trouver le formulaire complet <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/esre-with-blazor">ici</a></p><h2>Conclusion</h2><p>Blazor est un framework efficace qui vous permet de construire des applications web en utilisant C#. Elasticsearch est un moteur de recherche puissant qui vous permet de créer des applications de recherche. En combinant les deux, vous pouvez facilement créer des applications de recherche robustes, en tirant parti de la puissance d'ESRE pour créer une <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search.html">expérience de recherche sémantique</a> en peu de temps.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[.NET]]></category>
    <dc:creator><![CDATA[Gustavo Llermaly]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte7424ac5f0b223b4/6a17f668414c641971945323/7ba0d6bec908bfcae966b7f38626fabd682c6f3d-1200x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[LangChain4j avec Elasticsearch comme magasin de plongements]]></title>
    <description><![CDATA[LangChain4j (LangChain pour Java) a Elasticsearch comme magasin intégré. Découvrez comment l'utiliser pour construire votre application RAG en Java simple.]]></description>
    <content:encoded><![CDATA[<p>
Dans l'<a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">article précédent</a>, nous avons découvert ce qu'est LangChain4j et comment l'utiliser :</p><ul><li><p>Discutez avec les LLM en mettant en place un <code>ChatLanguageModel</code> et un <code>ChatMemory</code></p></li><li><p>Conserver l'historique du chat en mémoire pour se rappeler le contexte d'une discussion précédente avec un LLM</p></li></ul><p>Cet article de blog traite de la manière de procéder :</p><ul><li><p>Création d'encastrements vectoriels à partir d'exemples de textes</p></li><li><p>Stocker les embeddings vectoriels dans le magasin d'embedding d'Elasticsearch </p></li><li><p>Recherche de vecteurs similaires</p></li></ul><h2>Créer des embeddings</h2><p>Pour créer des embeddings, nous devons définir un <code>EmbeddingModel</code> à utiliser. Par exemple, nous pouvons utiliser le même modèle de mistral que celui utilisé dans le <a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">billet précédent.</a> Il s'agissait de courir avec l'ollama :</p>EmbeddingModel model = OllamaEmbeddingModel.builder()
  .baseUrl(ollama.getEndpoint())
  .modelName(MODEL_NAME)
  .build();<p>Un modèle est capable de générer des vecteurs à partir d'un texte. Nous pouvons ici vérifier le nombre de dimensions générées par le modèle :</p>Logger.info("Embedding model has {} dimensions.", model.dimension());
// This gives: Embedding model has 4096 dimensions.<p>Pour générer des vecteurs à partir d'un texte, nous pouvons utiliser :</p>Response&lt;Embedding&gt; response = model.embed("A text here");<p>Si nous voulons également fournir des métadonnées pour nous permettre de filtrer des éléments tels que le texte, le prix, la date de sortie ou autre, nous pouvons utiliser <code>Metadata.from()</code>. Par exemple, nous ajoutons ici le nom du jeu comme champ de métadonnées :</p>TextSegment game1 = TextSegment.from("""
    The game starts off with the main character Guybrush Threepwood stating "I want to be a pirate!"
    To do so, he must prove himself to three old pirate captains. During the perilous pirate trials, 
    he meets the beautiful governor Elaine Marley, with whom he falls in love, unaware that the ghost pirate 
    LeChuck also has his eyes on her. When Elaine is kidnapped, Guybrush procures crew and ship to track 
    LeChuck down, defeat him and rescue his love.
""", Metadata.from("gameName", "The Secret of Monkey Island"));
Response&lt;Embedding&gt; response1 = model.embed(game1);
TextSegment game2 = TextSegment.from("""
    Out Run is a pseudo-3D driving video game in which the player controls a Ferrari Testarossa 
    convertible from a third-person rear perspective. The camera is placed near the ground, simulating 
    a Ferrari driver's position and limiting the player's view into the distance. The road curves, 
    crests, and dips, which increases the challenge by obscuring upcoming obstacles such as traffic 
    that the player must avoid. The object of the game is to reach the finish line against a timer.
    The game world is divided into multiple stages that each end in a checkpoint, and reaching the end 
    of a stage provides more time. Near the end of each stage, the track forks to give the player a 
    choice of routes leading to five final destinations. The destinations represent different 
    difficulty levels and each conclude with their own ending scene, among them the Ferrari breaking 
    down or being presented a trophy.
""", Metadata.from("gameName", "Out Run"));
Response&lt;Embedding&gt; response2 = model.embed(game2);<p>Si vous souhaitez exécuter ce code, veuillez consulter la classe <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step5EmbedddingsTest.java">Step5EmbedddingsTest.java</a>.</p><h2>Ajouter Elasticsearch pour stocker nos vecteurs</h2><p>LangChain4j fournit un magasin d'intégration en mémoire. Cette fonction est utile pour exécuter des tests simples :</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore = new InMemoryEmbeddingStore&lt;&gt;();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>Mais il est évident que cela ne pourrait pas fonctionner avec un ensemble de données beaucoup plus important car ce datastore stocke tout en mémoire et nous ne disposons pas d'une mémoire infinie sur nos serveurs. Ainsi, nous pourrions plutôt stocker nos enregistrements dans Elasticsearch, qui est par définition "élastique" et peut évoluer avec vos données. Pour cela, ajoutons Elasticsearch à notre projet :</p>&lt;dependency&gt;
  &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
  &lt;artifactId&gt;langchain4j-elasticsearch&lt;/artifactId&gt;
  &lt;version&gt;${langchain4j.version}&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
  &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;
  &lt;artifactId&gt;elasticsearch&lt;/artifactId&gt;
  &lt;version&gt;1.20.1&lt;/version&gt;
  &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;<p>Comme vous l'avez remarqué, nous avons également ajouté le module Elasticsearch TestContainers au projet, afin de pouvoir démarrer une instance Elasticsearch à partir de nos tests :</p>// Create the elasticsearch container
ElasticsearchContainer container =
  new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:8.15.0")
    .withPassword("changeme");

// Start the container. This step might take some time...
container.start();

// As we don't want to make our TestContainers code more complex than
// needed, we will use login / password for authentication.
// But note that you can also use API keys which is preferred.
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "changeme"));

// Create a low level Rest client which connects to the elasticsearch container.
client = RestClient.builder(HttpHost.create("https://" + container.getHttpHostAddress()))
  .setHttpClientConfigCallback(httpClientBuilder -&gt; {
    httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
    httpClientBuilder.setSSLContext(container.createSslContextFromCa());
    return httpClientBuilder;
  })
  .build();

// Check the cluster is running
client.performRequest(new Request("GET", "/"));<p>Pour utiliser Elasticsearch comme magasin d'intégration, il suffit de "" passer du datastore LangChain4j en mémoire au datastore Elasticsearch :</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>Cela permettra de stocker vos vecteurs dans Elasticsearch dans un index <code>default</code>. Vous pouvez également changer le nom de l'index en quelque chose de plus significatif :</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .indexName("games")
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>Si vous souhaitez exécuter ce code, veuillez consulter la classe <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step6ElasticsearchEmbedddingsTest.java">Step6ElasticsearchEmbedddingsTest.java</a>.</p><h2>Recherche de vecteurs similaires</h2><p>Pour rechercher des vecteurs similaires, nous devons d'abord transformer notre question en une représentation vectorielle en utilisant le même modèle que celui utilisé précédemment. Nous l'avons déjà fait, il n'est donc pas difficile de le faire à nouveau. Notez que nous n'avons pas besoin des métadonnées dans ce cas :</p>String question = "I want to pilot a car";
Embedding questionAsVector = model.embed(question).content();<p>Nous pouvons construire une requête de recherche avec cette représentation de notre question et demander au magasin d'intégration de trouver les premiers vecteurs :</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>Nous pouvons maintenant itérer sur les résultats et imprimer certaines informations, comme le nom du jeu qui provient des métadonnées et le score :</p>result.matches().forEach(m -&gt; Logger.info("{} - score [{}]",
  m.embedded().metadata().getString("gameName"), m.score()));<p>Comme on pouvait s'y attendre, cela nous donne "Out Run" comme premier résultat :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ca0dcfdb1a9c94f/6a170291cf4f256938b2d017/140b6a962e5edbb4870419250e30bfb815b0d73e-640x480.gif" alt="Sortie de route" />Out Run - score [0.86672974]
The Secret of Monkey Island - score [0.85569763]<p>Si vous souhaitez exécuter ce code, consultez la classe <a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L110-L129">Step7SearchForVectorsTest.java</a>. </p><h2>L'envers du décor</h2><p>La configuration par défaut du magasin Elasticsearch Embedding utilise la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-knn-query.html">requête kNN approximative</a> en arrière-plan.</p>POST games/_search
{
  "query" : {
    "knn": {
      "field": "vector",
      "query_vector": [-0.019137882, /* ... */, -0.0148779955]
    }
  }
}<p>Mais cela peut être modifié en fournissant une autre configuration (<code>ElasticsearchConfigurationScript</code>) que celle par défaut (<code>ElasticsearchConfigurationKnn</code>) au magasin d'intégration :</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .configuration(ElasticsearchConfigurationScript.builder().build())
    .indexName("games")
    .restClient(client)
    .build();<p>L'implémentation de <code>ElasticsearchConfigurationScript</code> exécute en coulisse une <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html">requête</a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html"><code>script_score</code></a> à l'aide d'une <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html#vector-functions-cosine">fonction</a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html#vector-functions-cosine"><code>cosineSimilarity</code></a>.</p><p>En principe, lors d'un appel :</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>Il s'agit maintenant d'un appel :</p>POST games/_search
{
  "query": {
    "script_score": {
      "script": {
        "source": "(cosineSimilarity(params.query_vector, 'vector') + 1.0) / 2",
        "params": {
          "queryVector": [-0.019137882, /* ... */, -0.0148779955]
        }
      }
    }
  }
}<p>Dans ce cas, le résultat ne change pas en termes d'ordre "" mais le score est simplement ajusté parce que l'appel <code>cosineSimilarity</code> n'utilise pas d'approximation mais calcule le cosinus pour chacun des vecteurs correspondants :</p>Out Run - score [0.871952]
The Secret of Monkey Island - score [0.86380446]<p>Si vous souhaitez exécuter ce code, consultez la classe <a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L132-L155">Step7SearchForVectorsTest.java</a>.</p><h2>Conclusion</h2><p>Nous avons vu comment vous pouvez facilement générer des embeddings à partir de votre texte et comment vous pouvez stocker et rechercher les voisins les plus proches dans Elasticsearch en utilisant deux approches différentes :</p><ul><li><p>Utilisation de la requête approximative et rapide <code>knn</code> avec l'option par défaut <code>ElasticsearchConfigurationKnn</code></p></li><li><p>Utilisation de la requête exacte mais plus lente <code>script_score</code> avec l'option <code>ElasticsearchConfigurationScript</code></p></li></ul><p>La prochaine étape consistera à créer une application RAG complète, sur la base de ce que nous avons appris ici.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store</guid>
    <category><![CDATA[Java]]></category>
    <category><![CDATA[IA]]></category>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[David Pilato]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc873b86c76d1798/6a170293acf088f666be99b3/abd8a4a809064101c037af66b87f28e5ecde03b0-1474x645.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 08 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Techniques avancées de RAG, partie 2 : Requêtes et tests]]></title>
    <description><![CDATA[Discuter et mettre en œuvre des techniques susceptibles d'améliorer les performances du RAG. Partie 2 sur 2, axée sur l'interrogation et le test d'un pipeline RAG avancé.]]></description>
    <content:encoded><![CDATA[<p><em>Tout le code peut être trouvé </em><a href="https://github.com/elastic/elasticsearch-labs/tree/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques"><em>dans le repo de Searchlabs, dans la branche advanced-rag-techniques</em></a><em>.</em></p><p>Bienvenue dans la deuxième partie de notre article sur les techniques avancées de RAG ! Dans la <a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">première partie de cette série</a>, nous avons mis en place, discuté et implémenté les composants de traitement des données du pipeline RAG avancé :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" alt="Pipeline RAG avancé" /><p>Dans cette partie, nous allons procéder à l'interrogation et au test de notre mise en œuvre. Allons droit au but !</p><h3>Table des matières</h3><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#searching-and-retrieving,-generating-answers">Recherche et récupération, génération de réponses</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#enriching-queries-with-synonyms">Enrichir les requêtes avec des synonymes</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hyde-hypothetical-document-embedding">HyDE (Hypothetical Document Embedding)</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hybrid-search">Recherche hybride</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#experiments">Expériences</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#summary-of-results">Synthèse des résultats</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-1-who-audits-elastic">Test 1 : Qui vérifie Elastic ?</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-2--total-revenue-2023">Test 2 : recettes totales en 2023</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-1">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-1">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-3-what-product-does-growth-primarily-depend-on-how-much">Test 3 : De quel produit la croissance dépend-elle principalement ? Combien ?</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-2">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-2">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-4-describe-employee-benefit-plan">Test 4 : Décrire le régime d'avantages sociaux des salariés</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-3">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-3">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-5-which-companies-did-elastic-acquire">Test 5 : Quelles sont les entreprises acquises par Elastic ?</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-4">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-4">SimpleRAG</a></p></li></ul></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#conclusion">Conclusion</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#appendix">Annexe</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#prompts">Prompts</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#rag-question-answering-prompt">Question du RAG - invite à répondre</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#elastic-query-generator-prompt">Générateur de requêtes élastiques</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#potential-questions-generator-prompt">Questions potentielles : invite à la création d'un générateur</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hyde-generator-prompt">Invite du générateur HyDE</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#sample-hybrid-search-query">Exemple de requête de recherche hybride</a></p></li></ul></li></ul><h2>Recherche et récupération, génération de réponses</h2><p>Posons notre première question, idéalement une information trouvée principalement dans le rapport annuel. Que diriez-vous de.. :</p>Who audits Elastic?"
<p>Appliquons maintenant quelques-unes de nos techniques pour améliorer la requête.</p><h3>Enrichir les requêtes avec des synonymes</h3><p>Tout d'abord, améliorons la diversité de la formulation de la requête et transformons-la en une forme qui peut être facilement traitée dans une requête Elasticsearch. Nous ferons appel à GPT-4o pour convertir la requête en une liste de clauses OR. Écrivons ce message :</p>
ELASTIC_SEARCH_QUERY_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating Elasticsearch query strings. Your task is to create the most effective query string for the given user question. This query string will be used to search for relevant documents in an Elasticsearch index.

Guidelines:
1. Analyze the user's question carefully.
2. Generate ONLY a query string suitable for Elasticsearch's match query.
3. Focus on key terms and concepts from the question.
4. Include synonyms or related terms that might be in relevant documents.
5. Use simple Elasticsearch query string syntax if helpful (e.g., OR, AND).
6. Do not use advanced Elasticsearch features or syntax.
7. Do not include any explanations, comments, or additional text.
8. Provide only the query string, nothing else.

For the question "What is Clickthrough Data?", we would expect a response like:
clickthrough data OR click-through data OR click through rate OR CTR OR user clicks OR ad clicks OR search engine results OR web analytics

AND operator is not allowed. Use only OR.

User Question:
[The user's question will be inserted here]

Generate the Elasticsearch query string:
'''
<p>Appliqué à notre requête, GPT-4o génère des synonymes de la requête de base et du vocabulaire connexe.</p>'audits elastic OR 
elasticsearch audits OR 
elastic auditor OR 
elasticsearch auditor OR 
elastic audit firm OR 
elastic audit company OR 
elastic audit organization OR 
elastic audit service'
<p>Dans la classe <code>ESQueryMaker</code>, j'ai défini une fonction pour diviser la requête :</p>def parse_or_query(self, query_text: str) -&gt; List[str]:
    # Split the query by 'OR' and strip whitespace from each term
    # This converts a string like "term1 OR term2 OR term3" into a list ["term1", "term2", "term3"]
    return [term.strip() for term in query_text.split(' OR ')]
<p>Son rôle est de prendre cette chaîne de clauses OR et de les diviser en une liste de termes, ce qui nous permet d'effectuer une correspondance multiple sur les champs clés du document :</p>["original_text", 'keyphrases', 'potential_questions', 'entities']
<p>Finalement, nous avons abouti à cette requête :</p> 'query': {
    'bool': {
        'must': [
            {
                'multi_match': {
                'query': 'audits Elastic Elastic auditing Elastic audit process Elastic compliance Elastic security audit Elasticsearch auditing Elasticsearch compliance Elasticsearch security audit',
                'fields': [
                    'original_text',
                'keyphrases',
                'potential_questions',
                'entities'
                ],
                'type': 'best_fields',
                'operator': 'or'
                }
            }
      ]
<p>Cela permet de couvrir beaucoup plus de bases que la requête initiale, réduisant ainsi le risque de manquer un résultat de recherche en raison de l'oubli d'un synonyme. Mais nous pouvons faire plus.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">Haut de page</a></p><h3>HyDE (Hypothetical Document Embedding)</h3><p>Faisons à nouveau appel à GPT-4o, cette fois pour mettre en œuvre <a href="https://arxiv.org/abs/2212.10496">HyDE</a>.</p><p>Le principe de base de HyDE est de générer un document hypothétique - le type de document qui contiendrait probablement la réponse à la requête initiale. Le caractère factuel ou l'exactitude du document n'est pas en cause. En gardant cela à l'esprit, rédigeons l'invite suivante :</p>HYDE_DOCUMENT_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating hypothetical documents based on user queries. Your task is to create a detailed, factual document that would likely contain the answer to the user's question. This hypothetical document will be used to enhance the retrieval process in a Retrieval-Augmented Generation (RAG) system.

Guidelines:
1. Carefully analyze the user's query to understand the topic and the type of information being sought.
2. Generate a hypothetical document that:
   a. Is directly relevant to the query
   b. Contains factual information that would answer the query
   c. Includes additional context and related information
   d. Uses a formal, informative tone similar to an encyclopedia or textbook entry
3. Structure the document with clear paragraphs, covering different aspects of the topic.
4. Include specific details, examples, or data points that would be relevant to the query.
5. Aim for a document length of 200-300 words.
6. Do not use citations or references, as this is a hypothetical document.
7. Avoid using phrases like "In this document" or "This text discusses" - write as if it's a real, standalone document.
8. Do not mention or refer to the original query in the generated document.
9. Ensure the content is factual and objective, avoiding opinions or speculative information.
10. Output only the generated document, without any additional explanations or meta-text.

User Question:
[The user's question will be inserted here]

Generate a hypothetical document that would likely contain the answer to this query:
'''
<p>La recherche vectorielle s'appuyant généralement sur la similarité vectorielle cosinusoïdale, HyDE part du principe que l'on peut obtenir de meilleurs résultats en faisant correspondre des documents à des documents plutôt que des requêtes à des documents.</p><p>Ce qui nous importe, c'est la structure, le flux et la terminologie. Ce n'est pas tant le cas pour les faits. Le GPT-4o produit un document HyDE comme celui-ci :</p>'Elastic N.V., the parent company of Elastic, the organization known for developing Elasticsearch, is subject to audits to ensure financial accuracy, 
regulatory compliance, and the integrity of its financial statements. The auditing of Elastic N.V. is typically conducted by an external, 
independent auditing firm. This is common practice for publicly traded companies to provide stakeholders with assurance regarding the company\'s 
financial position and operations.\n\nThe primary external auditor for Elastic is the audit firm Ernst &amp; Young LLP (EY). Ernst &amp; Young is one of the 
four largest professional services networks in the world, commonly referred to as the "Big Four" audit firms. These firms handle a substantial number 
of audits for major corporations around the globe, ensuring adherence to generally accepted accounting principles (GAAP) and international financial 
reporting standards (IFRS).\n\nThe audit process conducted by EY involves several steps. Initially, the auditors perform a risk assessment to identify 
areas where misstatements due to error or fraud could occur. They then design audit procedures to test the accuracy and completeness of financial statements,
 which include examining financial transactions, assessing internal controls, and reviewing compliance with relevant laws and regulations. Upon completion of 
 the audit, Ernst &amp; Young issues an audit report, which includes the auditor’s opinion on whether the financial statements are free from material misstatement 
 and are presented fairly in accordance with the applicable financial reporting framework.\n\nIn addition to external audits by firms like Ernst &amp; Young, 
 Elastic may also be subject to internal audits. Internal audits are performed by the company’s own internal auditors to evaluate the effectiveness of internal 
 controls, risk management, and governance processes.\n\nOverall, the auditing process plays a crucial role in maintaining the transparency and reliability of 
 Elastic\'s financial information, providing confidence to investors, regulators, and other stakeholders.'
<p>Il semble assez crédible, comme le candidat idéal pour les types de documents que nous aimerions indexer. Nous allons l'intégrer et l'utiliser pour la recherche hybride.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">Haut de page</a></p><h3>Recherche hybride</h3><p>C'est le cœur de notre logique de recherche. Notre composante de recherche lexicale sera les chaînes de clauses OR générées. Notre composante vectorielle dense sera le document HyDE intégré (également appelé vecteur de recherche). Nous utilisons KNN pour identifier efficacement les documents candidats les plus proches de notre vecteur de recherche. Nous appelons notre composant de recherche lexicale <em>Scoring with TF-IDF and BM25</em> par défaut. Enfin, les scores des vecteurs lexicaux et denses seront combinés en utilisant le ratio 30/70 recommandé par <a href="https://arxiv.org/abs/2407.01219">Wang et al.</a></p>def hybrid_vector_search(self, index_name: str, query_text: str, query_vector: List[float], 
                         text_fields: List[str], vector_field: str, 
                         num_candidates: int = 100, num_results: int = 10) -&gt; Dict:
    """
    Perform a hybrid search combining text-based and vector-based similarity.

    Args:
        index_name (str): The name of the Elasticsearch index to search.
        query_text (str): The text query string, which may contain 'OR' separated terms.
        query_vector (List[float]): The query vector for semantic similarity search.
        text_fields (List[str]): List of text fields to search in the index.
        vector_field (str): The name of the field containing document vectors.
        num_candidates (int): Number of candidates to consider in the initial KNN search.
        num_results (int): Number of final results to return.

    Returns:
        Dict: A tuple containing the Elasticsearch response and the search body used.
    """
    try:
        # Parse the query_text into a list of individual search terms
        # This splits terms separated by 'OR' and removes any leading/trailing whitespace
        query_terms = self.parse_or_query(query_text)

        # Construct the search body for Elasticsearch
        search_body = {
            # KNN search component for vector similarity
            "knn": {
                "field": vector_field,  # The field containing document vectors
                "query_vector": query_vector,  # The query vector to compare against
                "k": num_candidates,  # Number of nearest neighbors to retrieve
                "num_candidates": num_candidates  # Number of candidates to consider in the KNN search
            },
            "query": {
                "bool": {
                    # The 'must' clause ensures that matching documents must satisfy this condition
                    # Documents that don't match this clause are excluded from the results
                    "must": [
                        {
                            # Multi-match query to search across multiple text fields
                            "multi_match": {
                                "query": " ".join(query_terms),  # Join all query terms into a single space-separated string
                                "fields": text_fields,  # List of fields to search in
                                "type": "best_fields",  # Use the best matching field for scoring
                                "operator": "or"  # Match any of the terms (equivalent to the original OR query)
                            }
                        }
                    ],
                    # The 'should' clause boosts relevance but doesn't exclude documents
                    # It's used here to combine vector similarity with text relevance
                    "should": [
                        {
                            # Custom scoring using a script to combine vector and text scores
                            "script_score": {
                                "query": {"match_all": {}},  # Apply this scoring to all documents that matched the 'must' clause
                                "script": {
                                    # Script to combine vector similarity and text relevance
                                    "source": """
                                    # Calculate vector similarity (cosine similarity + 1)
                                    # Adding 1 ensures the score is always positive
                                    double vector_score = cosineSimilarity(params.query_vector, params.vector_field) + 1.0;
                                    # Get the text-based relevance score from the multi_match query
                                    double text_score = _score;
                                    # Combine scores: 70% vector similarity, 30% text relevance
                                    # This weighting can be adjusted based on the importance of semantic vs keyword matching
                                    return 0.7 * vector_score + 0.3 * text_score;
                                    """,
                                    # Parameters passed to the script
                                    "params": {
                                        "query_vector": query_vector,  # Query vector for similarity calculation
                                        "vector_field": vector_field  # Field containing document vectors
                                    }
                                }
                            }
                        }
                    ]
                }
            }
        }

        # Execute the search request against the Elasticsearch index
        response = self.conn.search(index=index_name, body=search_body, size=num_results)
        # Log the successful execution of the search for monitoring and debugging
        logger.info(f"Hybrid search executed on index: {index_name} with text query: {query_text}")
        # Return both the response and the search body (useful for debugging and result analysis)
        return response, search_body
    except Exception as e:
        # Log any errors that occur during the search process
        logger.error(f"Error executing hybrid search on index: {index_name}. Error: {e}")
        # Re-raise the exception for further handling in the calling code
        raise e
<p>Enfin, nous pouvons reconstituer une fonction RAG. Notre RAG, de la question à la réponse, suivra ce flux :</p><ol><li><p>Convertir la requête en clauses OR.</p></li><li><p>Générer un document HyDE et l'intégrer.</p></li><li><p>Transmettre ces deux informations à la recherche hybride.</p></li><li><p>Récupérer les n premiers résultats, les inverser de façon à ce que le score le plus pertinent soit le "plus récent" dans la mémoire contextuelle du LLM (Reverse Packing) Reverse Packing Exemple : Requête : "Techniques d'optimisation des requêtes Elasticsearch" Documents récupérés (ordonnés par pertinence) :  Ordre inversé pour le contexte LLM :  En inversant l'ordre, l'information la plus pertinente (1) apparaît en dernier dans le contexte, recevant potentiellement plus d'attention de la part du LLM pendant la génération de la réponse.</p><ol><li><p>"Utilisez les requêtes bool pour combiner efficacement plusieurs critères de recherche."</p></li><li><p>"Mettre en œuvre des stratégies de mise en cache pour améliorer les temps de réponse des requêtes."</p></li><li><p>"Optimiser les mappages d'index pour accélérer les performances de recherche."</p></li><li><p>"Optimiser les mappages d'index pour accélérer les performances de recherche."</p></li><li><p>"Mettre en œuvre des stratégies de mise en cache pour améliorer les temps de réponse des requêtes."</p></li><li><p>"Utilisez les requêtes bool pour combiner efficacement plusieurs critères de recherche."</p></li></ol></li><li><p>Transmettre le contexte au LLM pour qu'il le génère.</p></li></ol>def get_context(index_name, 
                match_query, 
                text_query, 
                fields, 
                num_candidates=100, 
                num_results=20, 
                text_fields=["original_text", 'keyphrases', 'potential_questions', 'entities'], 
                embedding_field="primary_embedding"):

    embedding=embedder.get_embeddings_from_text(text_query)

    results, search_body = es_query_maker.hybrid_vector_search(
        index_name=index_name,
        query_text=match_query,
        query_vector=embedding[0][0],
        text_fields=text_fields,
        vector_field=embedding_field,
        num_candidates=num_candidates,
        num_results=num_results
    )

    # Concatenates the text in each 'field' key of the search result objects into a single block of text.
    context_docs=['\n\n'.join([field+":\n\n"+j['_source'][field] for field in fields]) for j in results['hits']['hits']]

    # Reverse Packing to ensure that the highest ranking document is seen first by the LLM.
    context_docs.reverse()
    return context_docs, search_body

def retrieval_augmented_generation(query_text):
    match_query= gpt4o.generate_query(query_text)
    fields=['original_text']

    hyde_document=gpt4o.generate_HyDE(query_text)

    context, search_body=get_context(index_name, match_query, hyde_document, fields)

    answer= gpt4o.basic_qa(query=query_text, context=context)
    return answer, match_query, hyde_document, context, search_body

<p>Exécutons notre requête et obtenons notre réponse :</p>According to the context, Elastic N.V. is audited by an independent registered public accounting firm, PricewaterhouseCoopers (PwC). 
This information is found in the section titled "report of independent registered public accounting firm," which states:

"We have audited the accompanying consolidated balance sheets of Elastic N.V. [...] / s / pricewaterhouseco."
<p>C'est une bonne chose. C'est exact.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">Haut de page</a></p><h2>Expériences</h2><p>Il faut maintenant répondre à une question importante. Qu'avons-nous obtenu en investissant tant d'efforts et de complexité supplémentaire dans ces mises en œuvre ?</p><p>Faisons une petite comparaison. Le pipeline RAG que nous avons mis en œuvre par rapport à la recherche hybride de base, sans aucune des améliorations que nous avons apportées. Nous allons effectuer une petite série de tests et voir si nous constatons des différences substantielles. Nous appellerons le RAG que nous venons de mettre en œuvre AdvancedRAG et le pipeline de base SimpleRAG.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf605c8246989df32/6a1711178b73cbc61d18a11d/8da40067835ab8b4dc12fe52a51a6c26858ad32f-1440x1095.jpg" alt="Pipeline RAG simple" /><h4>Synthèse des résultats</h4><p>Ce tableau résume les résultats de cinq tests effectués sur les deux pipelines RAG. J'ai évalué la supériorité relative de chaque méthode sur la base du détail et de la qualité des réponses, mais il s'agit d'un jugement totalement subjectif. Les réponses réelles sont reproduites sous ce tableau pour votre considération. Ceci étant dit, jetons un coup d'œil sur leurs résultats !</p><p>SimpleRAG n'a pas pu répondre aux questions 1 &amp; 5. AdvancedRAG a également répondu de manière beaucoup plus détaillée aux questions 2, 3 et 4. Sur la base de ces détails, j'ai jugé la qualité des réponses d'AdvancedRAG meilleure.</p><p>Test</p><p>Question</p><p>AdvancedRAG Performance</p><p>Performance de SimpleRAG</p><p>AdvancedRAG Latence</p><p>Latence de SimpleRAG</p><p>Gagnant</p><p>1</p><p>Qui audite Elastic ?</p><p>A correctement identifié PwC comme étant l'auditeur.</p><p>L'auditeur n'a pas été identifié.</p><p>11.6s</p><p>4.4s</p><p>AdvancedRAG</p><p>2</p><p>Quel a été le revenu total en 2023 ?</p><p>A fourni le chiffre d'affaires correct. Inclusion d'un contexte supplémentaire avec les recettes des années précédentes.</p><p>A fourni le chiffre d'affaires correct.</p><p>13.3s</p><p>2.8s</p><p>AdvancedRAG</p><p>3</p><p>De quel produit la croissance dépend-elle principalement ? Combien ?</p><p>A correctement identifié l'Elastic Cloud comme étant le facteur clé. Le contexte général des recettes a été inclus &amp; de manière plus détaillée.</p><p>A correctement identifié l'Elastic Cloud comme étant le facteur clé.</p><p>14.1s</p><p>12.8s</p><p>AdvancedRAG</p><p>4</p><p>Décrire le régime d'avantages sociaux des employés</p><p>Description complète des régimes de retraite, des programmes de santé et des autres avantages. Incluait des montants de cotisation spécifiques pour différentes années.</p><p>A donné un bon aperçu des avantages, y compris la rémunération, les plans de retraite, l'environnement de travail et le programme Elastic Cares.</p><p>26.6s</p><p>11.6s</p><p>AdvancedRAG</p><p>5</p><p>Quelles sont les entreprises acquises par Elastic ?</p><p>A corrigé la liste des acquisitions récentes mentionnées dans le rapport (CmdWatch, Build Security, Optimyze). Il a fourni quelques dates d'acquisition et des prix d'achat.</p><p>Échec de la recherche d'informations pertinentes dans le contexte fourni.</p><p>11.9s</p><p>2.7s</p><p>AdvancedRAG</p><h4>Test 1 : Qui vérifie Elastic ?</h4><p>AdvancedRAG</p>According to the context, Elastic N.V. is audited by an independent 
registered public accounting firm, PricewaterhouseCoopers (PwC). 
This information is found in the section titled "report of 
independent registered public accounting firm," which states:
"We have audited the accompanying consolidated balance sheets 
of Elastic N.V. [...] / s / pricewaterhouseco."
<p>SimpleRAG</p>According to the provided context, no specific auditing firm 
or company is mentioned as the auditor for Elastic. 
The context provided talks about various features, strengths, 
deployment options, and other details related to Elastic and 
its operations but does not specify which auditing firm is 
responsible for auditing Elastic's financial statements or 
internal controls.
<p><strong>Résumé</strong>: SimpleRAG n'a pas identifié PWC comme étant l'auditeur.</p><p>D'accord, c'est assez surprenant. Cela ressemble à un échec de recherche de la part de SimpleRAG. Aucun document relatif à l'audit n'a été retrouvé. Réduisons un peu la difficulté avec le test suivant.</p><h4>Test 2 : recettes totales en 2023</h4><p>AdvancedRAG</p>### Total Revenue for 2023

According to the provided context, the total revenue for the 
year ended April 30, 2023, was **$1,068,989,000**. This 
information can be found several times in the provided text. 
Specifically, one part states:

"The following table sets forth our results of operations 
for the periods presented in dollars and as a percentage of 
our total revenue. ... year ended April 30, 2023, ... 
total revenue 1,068,989".

Additionally, it is mentioned here as well:

"For the years ended April 30, 2023, 2022, and 2021, 
representing year-over-year growth of 24% for the year 
ended April 30, 2023... total revenue $1,068,989".

Therefore, the total revenue for 2023 is $1.069 billion.
<p>SimpleRAG</p>According to the context provided, the total revenue for the fiscal year ended April 30, 2023, is:

$1,068,989,000 (or approximately $1.1 billion).
<p><strong>Résumé</strong>: les deux RAG ont obtenu la bonne réponse : 1 068 989 000 dollars de recettes totales en 2023.</p><p>Ils étaient tous les deux ici. Il semble qu'AdvancedRAG ait acquis un plus grand nombre de documents ? La réponse est certainement plus détaillée et intègre des informations des années précédentes. On peut s'y attendre compte tenu des améliorations que nous avons apportées, mais il est encore trop tôt pour se prononcer.</p><p>Augmentons la difficulté.</p><h4>Test 3 : De quel produit la croissance dépend-elle principalement ? Combien ?</h4><p>AdvancedRAG</p>### Product Growth Primarily Depends On:
According to the context, "our revenue is derived primarily from 
subscriptions of search, observability and security built into 
the elastic stack." The document mentions various strategies and 
factors contributing to the company’s growth which include investment 
in "elastic cloud," "elastic stack," and the conversion of free 
users to paid subscribers.

### Quantitative Data:
- **Revenue**: The document states, "Our total revenue was $1.1 billion, 
$862.4 million, and $608.5 million for the years ended April 30, 2023, 
2022, and 2021, respectively."
- **Subscription Revenue**: "Subscriptions accounted for 92%, 93%, 
and 93% of our total revenue for the years ended April 30, 2023, 
2022, and 2021, respectively."
- **Elastic Cloud Contribution**: "For the years ended April 30, 2023, 
2022, and 2021, Elastic Cloud contributed 40%, 35%, and 27% of our 
total revenue, respectively."
- **Customer Growth**: The number of customers increased from 
"over 15,000 as of April 30, 2021," to "approximately 20,200 as of 
April 30, 2023."

### Summary:
Product growth for the company primarily depends on the "Elastic Stack" 
(which includes search, observability, and security solutions) and 
particularly the "Elastic Cloud." The Elastic Cloud's growing contribution 
to revenue highlights its significance, which grew from 27% in 2021 to 40% in 2023.
<p>SimpleRAG</p>According to the provided context, growth primarily depends on **Elastic Cloud**, 
which is described as the company's "family of cloud-based offerings." 
The context also emphasizes the significance of Elastic Cloud in achieving 
the company's long-term growth potential and its role in increasing subscription revenue.

From the context:

- "Increasing adoption of Elastic Cloud... is an important growth opportunity 
for our business. Organizations are increasingly looking for hosted 
deployment alternatives with reduced administrative burdens."
- "We believe that offering Elastic Cloud is important for achieving 
our long-term growth potential, and we expect Elastic Cloud’s contribution 
to our subscription revenue to continue to increase."

In terms of the contribution to total revenue, here are the specific figures provided:

- Elastic Cloud contributed 40% of the total revenue for the year ended April 30, 2023.
- Elastic Cloud contributed 35% of the total revenue for the year ended April 30, 2022.
- Elastic Cloud contributed 27% of the total revenue for the year ended April 30, 2021.

This progression shows a clear upward trend in the importance and 
contribution of Elastic Cloud to the company's overall revenue.
<p><strong>Résumé</strong>: les deux groupes d'experts ont correctement identifié Elastic Cloud comme le principal moteur de croissance. Cependant, AdvancedRAG fournit plus de détails, en tenant compte des revenus d'abonnement et de la croissance de la clientèle, et mentionne explicitement d'autres offres d'Elastic.</p><h4>Test 4 : Décrire le régime d'avantages sociaux des salariés</h4><p>AdvancedRAG</p>### Employee Benefit Plan Overview

The provided context outlines various elements of the employee benefit plan at 
Elastic, often referred to as "Elasticians." Here are the key components 
described in the texts:

1. **401(k) Plan**:
   - **Coverage**: The 401(k) plan is available to substantially all U.S. 
   employees who meet minimum age and service requirements.
   - **Contributions**: Elastic makes contributions to the 401(k) plan up to 
   6% of the participating employee’s W-2 earnings and wages.
   - **Expenses**: For the fiscal years ended April 30, Elastic recorded 
   expenses of $17.9 million (2023), $15.2 million (2022), and $11.4 million (2021) 
   related to the 401(k) plan.
   - **Defined-Contribution Plans in Other Countries**: Elastic has 
   defined-contribution plans in various other countries and recorded respective 
   expenses of $9.4 million (2023), $7.2 million (2022), and $5.1 million (2021).

2. **Stock-Based Compensation**:
   - **Types of Awards**: Stock options, restricted stock units (RSUs), 
   and shares under the Employee Stock Purchase Plan (ESPP).
   - **Fair Value Measurement**: Fair value of these stock awards is 
   measured using models like Black-Scholes.
   - **Employee Stock Purchase Plan (2022 ESPP)**: 
     - Started in 2022, it allows employees to acquire ordinary 
     shares at a discount (85% of the market value at the beginning 
     or end of the offering period).
     - Offering periods are approximately six months long.

3. **Total Rewards Compensation**:
   - **Components**: Includes cash compensation as well as equity awards, 
   reflecting a comprehensive interest in the well-being of the employees.
   - **Programs**: Market competitive medical and dental programs, 
   mental health, and holistic well-being programs.
   - **Paid Time Off**: Features 16 weeks of paid leave for new 
   parents and life-planning benefits.

4. **Retirement and Income Protection Plans**:
   - **401(k) Match**: Elastic provides a dollar-for-dollar 
   match up to 6% of eligible earnings up to a plan-limit 
   maximum for U.S.-based employees.
   - **Similar Plans Outside the U.S.**: Competitive retirement 
   plans available for employees based outside the United States.

5. **Flexible Work Environment**: 
   - **Distributed Workforce**: Elastic operates with a global 
   distributed workforce model, allowing employees to work remotely 
   and providing a broad candidate pool.
   
6. **Learning and Development**:
   - **Programs**: Access to on-demand video-based learning, 
   leadership development programs, and other specific training 
   to develop managers and leaders.

7. **Employee Engagement and Surveys**:
   - **Mechanisms**: Annual employee engagement survey and 
   mid-year pulse survey.
   - **Objectives**: To monitor morale, develop action plans, 
   and maintain high engagement scores.

8. **Onboarding Program**:
   - **X-School**: An extensive new-hire orientation program to 
   help new employees meet peers and learn about the company's 
   products and solutions.

In summary, Elastic's employee benefit plan encompasses a broad 
spectrum of financial, health, well-being, and professional development 
benefits designed to support and enhance the lives of its employees. 
The plan is both competitive and comprehensive, aiming to attract,
retain, and engage its global workforce.
<p>SimpleRAG</p>The employee benefit plan at the Company includes several key 
components aimed at enhancing the overall well-being and 
professional development of employees, referred to as "Elasticians." 

1. **Compensation and Benefits:**
   - The Company provides market-competitive compensation 
   packages which typically include cash compensation and equity awards.
   - Comprehensive medical and dental programs are offered, 
   emphasizing mental health and holistic well-being.
   - Paid time off programs feature:
     - 16 weeks of paid leave for new parents.
     - Life-planning benefits.
     - Travel reimbursements for certain healthcare services.

2. **Retirement and Income Protection Plans:**
   - In the United States, employees are eligible for a 401k 
   plan with a dollar-for-dollar match by the Company up to 6% 
   of eligible earnings, subject to a plan limit.
   - Similar competitive retirement and income protection plans 
   are available for employees outside the United States.

3. **Work Environment:**
   - The Company offers a flexible work environment, allowing most 
   employees to work from anywhere as often as they would like.
   - Physical offices around the world are available for employees 
   who prefer to work on-site or need a space for face-to-face interactions.

4. **Community Involvement:**
   - The Elastic Cares Program supports charitable organizations 
   that matter to employees on both local and global levels.
   - The program includes:
     - Donation matching.
     - Providing the Company's technology for free to certain 
     nonprofit organizations.
     - Volunteer time off initiative.
<p><strong>Résumé</strong>: AdvancedRAG va beaucoup plus en profondeur et en détail, en mentionnant le plan 401K pour les employés basés aux États-Unis, ainsi qu'en définissant les plans de contribution en dehors des États-Unis. Il mentionne également les régimes de santé et de bien-être, mais ne mentionne pas le programme Elastic Cares, que SimpleRAG mentionne.</p><h4>Test 5 : Quelles sont les entreprises acquises par Elastic ?</h4><p>AdvancedRAG</p>Elastic acquired several companies over the years to enhance its 
technology and market presence. According to the provided context, 
the specific acquisitions include:

1. **CmdWatch Security Inc.**: Acquired on September 17, 2021. 
The total purchase consideration was $77.8 million.
2. **Build Security Ltd.**: Acquired on September 2, 2021. The 
combined purchase price for Build Security Ltd. and another acquisition was $57.2 million.
3. **Optimyze Cloud Inc.**: Acquired on November 1, 2021. Part 
of the combined purchase price mentioned above with Build Security Ltd.
4. **Endgame, Inc.**: Although the context mentions an amended 
and restated stock incentive plan, it indicates that Endgame, Inc. was 
associated with Elastic, suggesting it could have been acquired at some point.

These acquisitions are part of Elastic's strategy to 
enhance the technology underlying its security and 
observability offerings.
<p>SimpleRAG</p>The provided context does not specify any companies that Elastic has acquired. 
Therefore, based on the context, there is no information available about the companies acquired by Elastic.
<p><strong>Résumé</strong>: SimpleRAG ne récupère aucune information pertinente sur les acquisitions, ce qui entraîne un échec de la réponse. AdvancedRAG répertorie correctement CmdWatch, Build Security et Optimyze, qui sont les principales acquisitions mentionnées dans le rapport.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">Haut de page</a></p><h2>Conclusion</h2><p>D'après nos tests, nos techniques avancées semblent augmenter l'étendue et la profondeur des informations présentées, ce qui pourrait améliorer la qualité des réponses RAG.</p><p>En outre, il est possible que la fiabilité soit améliorée, car les questions formulées de manière ambiguë, telles que <code>Which companies did Elastic acquire?</code> et <code>Who audits Elastic</code>, ont été correctement répondues par AdvancedRAG, mais pas par SimpleRAG.</p><p>Toutefois, il convient de garder à l'esprit que dans 3 cas sur 5, le pipeline RAG de base, intégrant la recherche hybride mais aucune autre technique, a réussi à produire des réponses qui capturaient la plupart des informations clés.</p><p>Il convient de noter qu'en raison de l'incorporation des LLM dans les phases de préparation des données et d'interrogation, la latence d'AdvancedRAG est généralement de 2 à 5 fois supérieure à celle de SimpleRAG. Il s'agit d'un coût important qui pourrait faire en sorte qu'AdvancedRAG ne convienne qu'aux situations où la qualité de la réponse est prioritaire par rapport à la latence.</p><p>Les coûts de latence importants peuvent être réduits en utilisant un LLM plus petit et moins cher comme Claude Haiku ou GPT-4o-mini à l'étape de préparation des données. Conservez les modèles avancés pour la génération de réponses.</p><p>Cela correspond aux conclusions de Wang et al. Comme le montrent leurs résultats, les améliorations apportées sont relativement progressives. En bref, un simple RAG de base vous permet d'obtenir un produit final décent, tout en étant moins cher et plus rapide. Pour moi, c'est une conclusion intéressante. Pour les cas d'utilisation où la vitesse et l'efficacité sont essentielles, SimpleRAG est un choix judicieux. Pour les cas d'utilisation où chaque goutte de performance doit être extraite, les techniques incorporées dans AdvancedRAG peuvent offrir une solution.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt56b7067a9d41d5a8/6a171119acf0886fb4be9c45/ea811706b6adc4731d90b925a9fefa0ac15901b4-1440x1060.jpg" alt="Pipeline Wang" /><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">Haut de page</a></p><h2>Annexe</h2><h3>Prompts</h3><h4>Question du RAG - invite à répondre</h4><p>Invitation à faire en sorte que le LLM génère des réponses basées sur la requête et le contexte.</p>BASIC_RAG_PROMPT = '''
You are an AI assistant tasked with answering questions based primarily on the provided context, while also drawing on your own knowledge when appropriate. Your role is to accurately and comprehensively respond to queries, prioritizing the information given in the context but supplementing it with your own understanding when beneficial. Follow these guidelines:

1. Carefully read and analyze the entire context provided.
2. Primarily focus on the information present in the context to formulate your answer.
3. If the context doesn't contain sufficient information to fully answer the query, state this clearly and then supplement with your own knowledge if possible.
4. Use your own knowledge to provide additional context, explanations, or examples that enhance the answer.
5. Clearly distinguish between information from the provided context and your own knowledge. Use phrases like "According to the context..." or "The provided information states..." for context-based information, and "Based on my knowledge..." or "Drawing from my understanding..." for your own knowledge.
6. Provide comprehensive answers that address the query specifically, balancing conciseness with thoroughness.
7. When using information from the context, cite or quote relevant parts using quotation marks.
8. Maintain objectivity and clearly identify any opinions or interpretations as such.
9. If the context contains conflicting information, acknowledge this and use your knowledge to provide clarity if possible.
10. Make reasonable inferences based on the context and your knowledge, but clearly identify these as inferences.
11. If asked about the source of information, distinguish between the provided context and your own knowledge base.
12. If the query is ambiguous, ask for clarification before attempting to answer.
13. Use your judgment to determine when additional information from your knowledge base would be helpful or necessary to provide a complete and accurate answer.

Remember, your goal is to provide accurate, context-based responses, supplemented by your own knowledge when it adds value to the answer. Always prioritize the provided context, but don't hesitate to enhance it with your broader understanding when appropriate. Clearly differentiate between the two sources of information in your response.

Context:
[The concatenated documents will be inserted here]

Query:
[The user's question will be inserted here]

Please provide your answer based on the above guidelines, the given context, and your own knowledge where appropriate, clearly distinguishing between the two:
'''
<h4>Générateur de requêtes élastiques</h4><p>Invite à enrichir les requêtes avec des synonymes et à les convertir au format OR.</p>ELASTIC_SEARCH_QUERY_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating Elasticsearch query strings. Your task is to create the most effective query string for the given user question. This query string will be used to search for relevant documents in an Elasticsearch index.

Guidelines:
1. Analyze the user's question carefully.
2. Generate ONLY a query string suitable for Elasticsearch's match query.
3. Focus on key terms and concepts from the question.
4. Include synonyms or related terms that might be in relevant documents.
5. Use simple Elasticsearch query string syntax if helpful (e.g., OR, AND).
6. Do not use advanced Elasticsearch features or syntax.
7. Do not include any explanations, comments, or additional text.
8. Provide only the query string, nothing else.

For the question "What is Clickthrough Data?", we would expect a response like:
clickthrough data OR click-through data OR click through rate OR CTR OR user clicks OR ad clicks OR search engine results OR web analytics

AND operator is not allowed. Use only OR.

User Question:
[The user's question will be inserted here]

Generate the Elasticsearch query string:
'''
<h4>Questions potentielles : invite à la création d'un générateur</h4><p>Invite à générer des questions potentielles, à enrichir les métadonnées des documents.</p>RAG_QUESTION_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating questions for Retrieval-Augmented Generation (RAG) systems. Your task is to analyze a given document and create 10 diverse questions that would effectively test a RAG system's ability to retrieve and synthesize information from this document.

Guidelines:
1. Thoroughly analyze the entire document.
2. Generate exactly 10 questions that cover various aspects and levels of complexity within the document's content.
3. Create questions that specifically target:
   a. Key facts and information
   b. Main concepts and ideas
   c. Relationships between different parts of the content
   d. Potential applications or implications of the information
   e. Comparisons or contrasts within the document
4. Ensure questions require answers of varying lengths and complexity, from simple retrieval to more complex synthesis.
5. Include questions that might require combining information from different parts of the document.
6. Frame questions to test both literal comprehension and inferential understanding.
7. Avoid yes/no questions; focus on open-ended questions that promote comprehensive answers.
8. Consider including questions that might require additional context or knowledge to fully answer, to test the RAG system's ability to combine retrieved information with broader knowledge.
9. Number the questions from 1 to 10.
10. Output only the ten questions, without any additional text, explanations, or answers.

Document:
[The document content will be inserted here]

Generate 10 questions optimized for testing a RAG system based on this document:
'''
<h4>Invite du générateur HyDE</h4><p>Invite à générer des documents hypothétiques à l'aide de HyDE</p>HYDE_DOCUMENT_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating hypothetical documents based on user queries. Your task is to create a detailed, factual document that would likely contain the answer to the user's question. This hypothetical document will be used to enhance the retrieval process in a Retrieval-Augmented Generation (RAG) system.

Guidelines:
1. Carefully analyze the user's query to understand the topic and the type of information being sought.
2. Generate a hypothetical document that:
   a. Is directly relevant to the query
   b. Contains factual information that would answer the query
   c. Includes additional context and related information
   d. Uses a formal, informative tone similar to an encyclopedia or textbook entry
3. Structure the document with clear paragraphs, covering different aspects of the topic.
4. Include specific details, examples, or data points that would be relevant to the query.
5. Aim for a document length of 200-300 words.
6. Do not use citations or references, as this is a hypothetical document.
7. Avoid using phrases like "In this document" or "This text discusses" - write as if it's a real, standalone document.
8. Do not mention or refer to the original query in the generated document.
9. Ensure the content is factual and objective, avoiding opinions or speculative information.
10. Output only the generated document, without any additional explanations or meta-text.

User Question:
[The user's question will be inserted here]

Generate a hypothetical document that would likely contain the answer to this query:
'''
<h3>Exemple de requête de recherche hybride</h3>{'knn': {'field': 'primary_embedding',
  'query_vector': [0.4265527129173279,
   -0.1712949573993683,
   -0.042020395398139954,
   ...],
  'k': 100,
  'num_candidates': 100},
 'query': {'bool': {'must': [{'multi_match': {'query': 'audits Elastic Elastic auditing Elastic audit process Elastic compliance Elastic security audit Elasticsearch auditing Elasticsearch compliance Elasticsearch security audit',
      'fields': ['original_text',
       'keyphrases',
       'potential_questions',
       'entities'],
      'type': 'best_fields',
      'operator': 'or'}}],
   'should': [{'script_score': {'query': {'match_all': {}},
      'script': {'source': '\n                                        double vector_score = cosineSimilarity(params.query_vector, params.vector_field) + 1.0;\n                                        double text_score = _score;\n                                        return 0.7 * vector_score + 0.3 * text_score;\n                                        ',
       'params': {'query_vector': [0.4265527129173279,
         -0.1712949573993683,
         -0.042020395398139954,
        ...],
        'vector_field': 'primary_embedding'}}}}]}},
 'size': 10}
]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[IA]]></category>
    <dc:creator><![CDATA[Han Xiang Choong]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf605c8246989df32/6a1711178b73cbc61d18a11d/8da40067835ab8b4dc12fe52a51a6c26858ad32f-1440x1095.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 15 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Techniques avancées de RAG partie 1 : Traitement des données]]></title>
    <description><![CDATA[Discuter et mettre en œuvre des techniques susceptibles d'améliorer les performances du RAG. Partie 1 sur 2, se concentrant sur le traitement et l'ingestion des données d'un pipeline RAG avancé.]]></description>
    <content:encoded><![CDATA[<p><em>Voici la première partie de notre exploration des techniques avancées de RAG. </em><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2"><em>Cliquez ici pour la deuxième partie !</em></a></p><p>L'article récent intitulé <a href="https://arxiv.org/abs/2407.01219">Searching for Best Practices in Retrieval-Augmented Generation</a> évalue de manière empirique l'efficacité de diverses techniques d'amélioration des RAG, dans le but de converger vers un ensemble de meilleures pratiques pour les RAG.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt671704ff06a4011d/6a170b3ea929cf2d19ae09d8/dafa7250e7c4ead4d9b4aed7c407509131929749-1440x572.png" alt="RAG pipeline recommandé par Wang" /><p>Nous mettrons en œuvre quelques-unes des meilleures pratiques proposées, notamment celles qui visent à améliorer la qualité de la recherche <strong>(Sentence Chunking, HyDE, Reverse Packing)</strong>.</p><p>Par souci de concision, nous omettons les techniques axées sur l'amélioration de l'efficacité <strong>(classification des requêtes et résumé).</strong></p><p>Nous mettrons également en œuvre quelques techniques qui n'ont pas été abordées, mais que je trouve personnellement utiles et intéressantes <strong>(Metadata Inclusion, Composite Multi-Field Embeddings, Query Enrichment).</strong></p><p>Enfin, nous effectuerons un petit test pour voir si la qualité de nos résultats de recherche et des réponses générées s'est améliorée par rapport à la base de référence. C'est parti !</p><h2>Vue d'ensemble du RAG</h2><p>RAG vise à améliorer les LLM en récupérant des informations dans des bases de connaissances externes afin d'enrichir les réponses générées. En fournissant des informations spécifiques au domaine, les LLM peuvent être rapidement adaptés à des cas d'utilisation qui sortent du cadre de leurs données de formation ; cela coûte beaucoup moins cher qu'un réglage fin et il est plus facile de les tenir à jour.</p><p>Les mesures visant à améliorer la qualité du RAG se concentrent généralement sur deux axes :</p><ol><li><p>Améliorer la qualité et la clarté de la base de connaissances.</p></li><li><p>Améliorer la couverture et la spécificité des requêtes de recherche.</p></li></ol><p>Ces deux mesures permettront d'améliorer les chances que le LLM ait accès à des faits et des informations pertinents, et qu'il soit donc moins susceptible d'halluciner ou de s'appuyer sur ses propres connaissances - qui peuvent être dépassées ou non pertinentes.</p><p>La diversité des méthodes est difficile à expliquer en quelques phrases. Pour plus de clarté, passons directement à la mise en œuvre.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" alt="Pipeline RAG avancé" /><h3>Table des matières</h3><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#overview">Aperçu</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Table des matières</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#set-up">Mise en place</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#ingesting-processing-and-embedding-documents">Acquisition, traitement et intégration de documents</a>  </p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#data-ingestion">Ingestion des données</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#sentence-level-token-wise-chunking">Chunking au niveau de la phrase, par jetons</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#metadata-inclusion-and-generation">Inclusion et génération de métadonnées</a> </p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#keyphrases-extracted-by-textrank">Phrases clés extraites par TextRank</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#potential-questions-generated-by-gpt-4o">Questions potentielles générées par le GPT-4o</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#entities-extracted-by-spacy">Entités extraites par Spacy</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#composite-multi-field-embeddings">Enchâssement composite de champs multiples</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#indexing-to-elastic">Indexation vers Elastic</a></p></li></ul></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#cat-break">Pause-catalogues</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#appendix">Annexe</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#definitions">Définitions</a></p></li></ul></li></ul><h2>Mise en place</h2><p><em>Tout le code peut être trouvé </em><a href="https://github.com/elastic/elasticsearch-labs/tree/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques"><em>dans le repo de Searchlabs</em></a><em>.</em></p><p>Commençons par le commencement. Vous aurez besoin des éléments suivants :</p><ol><li><p>Déploiement d'un nuage élastique</p></li><li><p>Une API LLM - Nous utilisons un déploiement GPT-4o sur Azure OpenAI dans ce carnet.</p></li><li><p>Python version 3.12.4 ou ultérieure</p></li></ol><p>Nous allons exécuter tout le code du <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/main.ipynb">cahier main.ipynb</a>.</p><p>Allez-y et clonez git le repo, naviguez vers supporting-blog-content/advanced-rag-techniques, puis exécutez les commandes suivantes :</p># Create a new virtual environment named 'rag_env'
python -m venv rag_env

# Activate the virtual environment (for Unix-based systems)
source rag_env/bin/activate

# (For Windows)
.\rag_env\Scripts\activate

# Install packages listed in requirements.txt
pip install -r requirements.txt
<p>Une fois que c'est fait, créez un fichier <em>.env</em> et remplissez les champs suivants (référencés dans l'<a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/.env.example"><em>exemple .env</em></a>). Remerciements à mon co-auteur, Claude-3.5, pour ses commentaires utiles.</p># Elastic Cloud: Found in the 'Deployment' page of your Elastic Cloud 
# console
ELASTIC_CLOUD_ENDPOINT=""
ELASTIC_CLOUD_ID=""

# Elastic Cloud: Created during deployment setup or in 'Security' 
# settings
ELASTIC_USERNAME=""
ELASTIC_PASSWORD=""

# Elastic Cloud: The name of the index you created in Kibana or via API
ELASTIC_INDEX_NAME=""

# Azure AI Studio: Found in 'Keys and Endpoint' section of your Azure 
# OpenAI resource
AZURE_OPENAI_KEY_1=""
AZURE_OPENAI_KEY_2=""
AZURE_OPENAI_REGION=""
AZURE_OPENAI_ENDPOINT=""

# Azure AI Studio: Found in 'Deployments' section of your Azure OpenAI 
# resource
AZURE_OPENAI_DEPLOYMENT_NAME=""

# Using BAAI/bge-small-en-v1.5 because I think it is a good balance of 
# resource efficiency and performance. 
HUGGINGFACE_EMBEDDING_MODEL="BAAI/bge-small-en-v1.5"
<p>Ensuite, nous allons choisir le document à ingérer et le placer dans le dossier documents. Pour cet article, nous utiliserons le <a href="https://s201.q4cdn.com/217177842/files/doc_downloads/OtherDocuments/2023/AnnualMeeting/Annual-Report-Fiscal-Year-2023.pdf">rapport annuel 2023 d'Elastic N.V.</a> Il s'agit d'un document assez difficile et dense, parfait pour tester nos techniques RAG.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte292dc6030d496cc/6a170b40dc55de9b03e00dfc/e513b9d67adac43da794c25a5969b893127bbbe3-1440x395.jpg" alt="Rapport annuel d'Elastic 2023" /><p>Maintenant que tout est prêt, passons à l'ingestion. Ouvrez <em>main.ipynb</em> et exécutez les deux premières cellules pour importer tous les paquets et initialiser tous les services.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Haut de page</a></p><h2>Acquisition, traitement et intégration de documents</h2><h3>Ingestion des données</h3><ul><li><p><em>Note personnelle : je suis stupéfait par la commodité de LlamaIndex. Avant les LLM et LlamaIndex, l'ingestion de documents de différents formats était un processus pénible de collecte de paquets ésotériques provenant d'un peu partout. Aujourd'hui, il se réduit à un seul appel de fonction. Sauvage.</em></p></li></ul><p>La commande <code>SimpleDirectoryReader</code> chargera chaque document contenu dans les fichiers <code>directory_path.</code> et <code>.pdf</code>. Elle renvoie une liste d'objets document, que je convertis en dictionnaires Python parce que je trouve qu'ils sont plus faciles à manipuler.</p># llamaindex_processor.py
from llama_index.core import SimpleDirectoryReader

class LlamaIndexProcessor:
   def __init__(self):
       pass 
   
   def load_documents(self, directory_path):
       ''' 
       Load all documents in directory
       '''
       reader = SimpleDirectoryReader(input_dir=directory_path)
       return reader.load_data()

# main.ipynb
llamaindex_processor=LlamaIndexProcessor()
documents=llamaindex_processor.load_documents('./documents/')
documents=[dict(doc_obj) for doc_obj in documents]
<p>Chaque dictionnaire contient le contenu clé du champ <code>text</code>. Il contient également des métadonnées utiles telles que le numéro de page, le nom du fichier, sa taille et son type.</p>{
  'id_': '5f76f0b3-22d8-49a8-9942-c2bbab14f63f',
  'metadata': {'page_label': '5',
   'file_name': 'Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf',
   'file_path': '/Users/han/Desktop/Projects/truckasaurus/documents/Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf',
   'file_type': 'application/pdf',
   'file_size': 3724426,
   'creation_date': '2024-07-27',
   'last_modified_date': '2024-07-27'},
   'text': 'Table of Contents\nPage\nPART I\nItem 1. Business 3\n15 Item 1A. Risk Factors\nItem 1B. Unresolved Staff Comments 48\nItem 2. Properties 48\nItem 3. Legal Proceedings 48\nItem 4. Mine Safety Disclosures 48\nPART II\nItem 5. Market for Registrant's Common Equity, Related Stockholder Matters and Issuer Purchases of \nEquity Securities49\nItem 6. [Reserved] 49\nItem 7. Management's Discussion and Analysis of Financial Condition and Results of Operations 50\nItem 7A. Quantitative and Qualitative Disclosures About Market Risk 64\nItem 8. Financial Statements and Supplementary Data 66\nItem 9. Changes in and Disagreements With Accountants on Accounting and Financial Disclosure 100\n100\n101Item 9A. Controls and Procedures\nItem 9B. Other Information\nItem 9C. Disclosure Regarding Foreign Jurisdictions That Prevent Inspections 101\nPART III\n102\n102\n102\n102Item 10. Directors, Executive Officers and Corporate Governance\nItem 11. Executive Compensation\nItem 12. Security Ownership of Certain Beneficial Owners and Management, and Related Stockholder Matters  \nItem 13. Certain Relationships and Related Transactions, and Director Independence\nItem 14. Principal Accountant Fees and Services 102\nPART IV\n103\n105Item 15. Exhibits and Financial Statement Schedules  \nItem 16. Form 10-K Summary\nSignatures 106\ni',
   ...
}
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Haut de page</a></p><h3>Chunking au niveau de la phrase, par jetons</h3><p>La première chose à faire est de réduire nos documents en morceaux d'une longueur standard (pour assurer la cohérence et la maniabilité). Les modèles d'intégration ont des limites de jetons uniques (taille d'entrée maximale qu'ils peuvent traiter). Les jetons sont les unités de base du texte que les modèles traitent. Pour éviter la perte d'informations (tronquage ou omission de contenu), nous devrions fournir un texte qui ne dépasse pas ces limites (en divisant les textes plus longs en segments plus petits).</p><p>Le découpage a un impact significatif sur les performances. Idéalement, chaque morceau devrait représenter un élément d'information autonome, capturant des informations contextuelles sur un seul sujet. Les méthodes de découpage comprennent le découpage au niveau des mots, où les documents sont divisés en fonction du nombre de mots, et le découpage sémantique qui utilise un LLM pour identifier les points de rupture logiques.</p><p>Le découpage au niveau des mots est bon marché, rapide et facile, mais il risque de diviser les phrases et donc de briser le contexte. Le découpage sémantique devient lent et coûteux, surtout s'il s'agit de documents tels que le rapport annuel Elastic de 116 pages.</p><p>Choisissons une approche intermédiaire. Le découpage en phrases est encore simple, mais il permet de préserver le contexte plus efficacement que le découpage en mots, tout en étant nettement moins coûteux et plus rapide. En outre, nous mettrons en œuvre une fenêtre coulissante pour capturer une partie du contexte environnant et atténuer l'impact du découpage des paragraphes.</p># chunker.py 

import uuid
import re


class Chunker: 
    def __init__(self, tokenizer):
        self.tokenizer = tokenizer 
    
    def split_into_sentences(self, text):
        """Split text into sentences."""
        return re.split(r'(?&lt;=[.!?])\s+', text)
 
    def sentence_wise_tokenized_chunk_documents(self, documents, chunk_size=512, overlap=20, min_chunk_size=50):
        '''
        1. Split text into sentences.
        2. Tokenize using the provided tokenizer method.
        3. Build chunks up to the chunk_size limit.
        4. Create an overlap based on tokens - to preserve context.
        5. Only keep chunks that meet the minimum token size requirement.
        '''
        chunked_documents = []

        for doc in documents:
            sentences = self.split_into_sentences(doc['text'])
            tokens = []
            sentence_boundaries = [0]

            # Tokenize all sentences and keep track of sentence boundaries
            for sentence in sentences:
                sentence_tokens = self.tokenizer.encode(sentence, add_special_tokens=True)
                tokens.extend(sentence_tokens)
                sentence_boundaries.append(len(tokens))

            # Create chunks
            chunk_start = 0
            while chunk_start &lt; len(tokens):
                chunk_end = chunk_start + chunk_size

                # Find the last complete sentence that fits in the chunk
                sentence_end = next((i for i in sentence_boundaries if i &gt; chunk_end), len(tokens))
                chunk_end = min(chunk_end, sentence_end)

                # Create the chunk
                chunk_tokens = tokens[chunk_start:chunk_end]

                # Check if the chunk meets the minimum size requirement
                if len(chunk_tokens) &gt;= min_chunk_size:
                    # Create a new document object for this chunk
                    chunk_doc = {
                        'id_': str(uuid.uuid4()),
                        'chunk': chunk_tokens,
                        'original_text': self.tokenizer.decode(chunk_tokens),
                        'chunk_index': len(chunked_documents),
                        'parent_id': doc['id_'],
                        'chunk_token_count': len(chunk_tokens)
                    }

                    # Copy all other fields from the original document
                    for key, value in doc.items():
                        if key != 'text' and key not in chunk_doc:
                            chunk_doc[key] = value

                    chunked_documents.append(chunk_doc)

                # Move to the next chunk start, considering overlap
                chunk_start = max(chunk_start + chunk_size - overlap, chunk_end - overlap)

        return chunked_documents

# main.ipynb 
# Initialize Embedding Model
HUGGINGFACE_EMBEDDING_MODEL = os.environ.get('HUGGINGFACE_EMBEDDING_MODEL')
embedder=EmbeddingModel(model_name=HUGGINGFACE_EMBEDDING_MODEL)

# Initialize Chunker
chunker=Chunker(embedder.tokenizer)
<p>La classe <code>Chunker</code> utilise le tokenizer du modèle d'intégration pour encoder et décoder le texte. Nous allons maintenant construire des blocs de 512 jetons chacun, avec un chevauchement de 20 jetons. Pour ce faire, nous divisons le texte en phrases, nous tokenisons ces phrases, puis nous ajoutons les phrases tokenisées à notre morceau actuel jusqu'à ce que nous ne puissions plus en ajouter sans dépasser notre limite de tokens.</p><p>Enfin, nous décodons les phrases pour les ramener au texte d'origine afin de les intégrer, en les stockant dans un champ appelé <code>original_text</code>. Les morceaux sont stockés dans un champ appelé <code>chunk</code>. Pour réduire le bruit (c'est-à-dire les documents inutiles), nous éliminons tous les documents dont la longueur est inférieure à 50 tokens.</p><p>Passons-le en revue nos documents :</p>chunked_documents=chunker.sentence_wise_tokenized_chunk_documents(documents, chunk_size=512)
<p>Et vous obtenez des morceaux de texte qui ressemblent à ceci :</p>print(chunked_documents[4]['original_text'])

[CLS] the aggregate market value of the ordinary shares held by non - affiliates of the registrant, 
based on the closing price of the shares of ordinary shares on the new york stock exchange on 
october 31, 2022 ( the last business day of the registrant 's second fiscal quarter ), was 
approximately $ 6. 1 billion. [SEP] [CLS] as of may 31, 2023, the registrant had 97, 390, 886 
ordinary shares, par value €0. 01 per share, outstanding. [SEP] [CLS] documents incorporated by 
reference portions of the registrant 's definitive proxy statement relating to the registrant 's 2
023 annual general meeting of shareholders are incorporated by reference into part iii of this annual 
...
...
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Haut de page</a></p><h3>Inclusion et génération de métadonnées</h3><p>Nous avons découpé nos documents en morceaux. Il est maintenant temps d'enrichir les données. Je souhaite générer ou extraire des métadonnées supplémentaires. Ces métadonnées supplémentaires peuvent être utilisées pour influencer et améliorer les performances de recherche.</p><p>Nous allons définir une classe <code>DocumentEnricher</code>, dont le rôle est de recevoir une liste de documents (dictionnaires Python) et une liste de fonctions du processeur. Ces fonctions s'exécutent sur la colonne <code>original_text</code> des documents et stockent leurs résultats dans de nouveaux champs.</p><p>Tout d'abord, nous extrayons les phrases clés à l'aide de <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/nltk_processor.py">TextRank</a>. TextRank est un algorithme basé sur un graphe qui permet d'extraire des phrases et des expressions clés d'un texte en classant leur importance sur la base des relations entre les mots.</p><p>Ensuite, nous allons <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/llm.py">générer des questions potentielles à l'aide de GPT-4o</a>.</p><p>Enfin, nous <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/entity_extractor.py">extrairons les entités</a> à l'aide de <a href="https://spacy.io/">Spacy</a>.</p><p>Le code de chacun d'entre eux étant assez long et complexe, je m'abstiendrai de le reproduire ici. Si vous êtes intéressé, les fichiers sont indiqués dans les exemples de code ci-dessous.</p><p>Lançons l'enrichissement des données :</p># documentenricher.py
from tqdm import tqdm

class DocumentEnricher:

    def __init__(self):
        pass 

    def enrich_document(self, documents, processors, text_col='text'):
        for doc in tqdm(documents, desc="Enriching documents using processors: "+str(processors)): 
            for (processor, field) in processors: 
                metadata=processor(doc[text_col])
                if isinstance(metadata, list):
                    metadata='\n'.join(metadata)
                doc.update({field: metadata})
 
# main.ipynb
# Initialize processor classes 
nltkprocessor=NLTKProcessor() // nltk_processor.py
entity_extractor=EntityExtractor() // entity_extractor.py
gpt4o = LLMProcessor(model='gpt-4o') // llm.py

# Initialize LLM
documentenricher=DocumentEnricher()

# Create new fields in the documents - These are the outputs of the processor functions.
processors=[
    (nltkprocessor.textrank_phrases, "keyphrases"),
    (gpt4o.generate_questions, "potential_questions"),
    (entity_extractor.extract_entities, "entities")
    ]

# .enrich_document() will modify chunked_docs in place. 
# To view the results, we'll print chunked_docs in the next few cells!
documentenricher.enrich_document(chunked_docs, text_col='original_text', processors=processors)
<p>Et regardez les résultats :</p><h4>Phrases clés extraites par TextRank</h4><p>Ces phrases clés sont des substituts des thèmes centraux de la rubrique. Si une requête a trait à la cybersécurité, le score de ce morceau sera augmenté.</p>print(chunked_documents[25]['keyphrases'])

'elastic agent stop', 'agent stop malware', 
'stop malware ransomware', 'malware ransomware environment', 
'ransomware environment wide', 'environment wide visibility', 
'wide visibility threat', 'visibility threat detection', 
'sep cl key', 'cl key feature'
<h4>Questions potentielles générées par le GPT-4o</h4><p>Ces questions potentielles peuvent correspondre directement aux requêtes des utilisateurs, ce qui permet d'améliorer le score. Nous demandons à GPT-4o de générer des questions auxquelles il est possible de répondre en utilisant les informations trouvées dans le morceau actuel.</p>print(chunked_documents[25]['potential_questions'])

1. What are the primary functions that Elastic Agent provides in terms of cybersecurity?
2. Describe how Logstash contributes to data management within an IT environment.
3. List and explain any key features of Logstash mentioned in the document.
4. How does Elastic Agent enhance environment-wide visibility in threat detection?
5. What capabilities does Logstash offer for handling data beyond simple collection?
6. In what ways does the document suggest that Elastic Agent stops malware and ransomware?
7. Can you identify any relationships between the functionalities of Elastic Agent and Logstash in an integrated environment?
8. What implications might the advanced threat detection capabilities of Elastic Agent have for organizational security policies?
9. Compare and contrast the roles of Elastic Agent and Logstash based on their described functions.
10. How might the centralized collection ability of Logstash support the threat detection capabilities of Elastic Agent?
<h4>Entités extraites par Spacy</h4><p>Ces entités ont un objectif similaire à celui des phrases clés, mais elles capturent les noms des organisations et des individus, ce que l'extraction des phrases clés peut ne pas faire.</p>print(chunked_documents[29]['entities'])

'appdynamics', 'apm data', 'azure sentinel', 
'microsoft', 'mcafee', 'broadcom', 'cisco', 
'dynatrace', 'coveo', 'lucidworks'
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Haut de page</a></p><h3>Enchâssement composite de champs multiples</h3><p>Maintenant que nous avons enrichi nos documents avec des métadonnées supplémentaires, nous pouvons exploiter ces informations pour créer des encastrements plus robustes et tenant compte du contexte.</p><p>Faisons le point sur l'état actuel du processus. Nous avons quatre champs d'intérêt dans chaque document.</p>{
    "chunk": "...",
    "keyphrases": "...", 
    "potential_questions": "...", 
    "entities": "..." 
}
<p>Chaque champ représente une perspective différente sur le contexte du document, mettant potentiellement en évidence un domaine clé sur lequel le LLM devrait se concentrer.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt84cb328fce6aae23/6a170b42964cea3e4408bbc4/aea1f513009a0c7c8545a79fad8f072a5bcae24c-1440x1067.jpg" alt="Pipeline d'enrichissement des métadonnées dans RAG" /><p>Il s'agit d'intégrer chacun de ces champs, puis de créer une somme pondérée des intégrations, appelée intégration composite.</p><p>Avec un peu de chance, cette intégration composite permettra au système de mieux tenir compte du contexte, tout en introduisant un autre hyperparamètre réglable pour contrôler le comportement de recherche.</p><p>Tout d'abord, intégrons chaque champ et mettons à jour chaque document en place, en utilisant notre modèle d'intégration défini localement et importé au début du bloc-notes main.ipynb.</p># EmbeddingModel defined in embedding_model.py
embedder=EmbeddingModel(model_name=HUGGINGFACE_EMBEDDING_MODEL)

cols_to_embed=['keyphrases', 'potential_questions', 'entities']

embedding_cols=[]
for col in cols_to_embed:
    # Works on text input
    embedding_col=embedder.embed_documents_text_wise(chunked_documents, text_field=col)
    embedding_cols.append(embedding_col)
# Works on token input
embedding_col=embedder.embed_documents_token_wise(chunked_documents, token_field="chunk")
embedding_cols.append(embedding_col)
<p>Chaque fonction d'intégration renvoie le champ de l'intégration, qui est simplement le champ d'entrée original avec un postfixe <code>_embedding</code>.</p><p>Définissons maintenant les pondérations de notre encastrement composite :</p>embedding_cols=[
                'keyphrases_embedding',
                'potential_questions_embedding',
                'entities_embedding',
                'chunk_embedding']
combination_weights=[
                    0.1,
                    0.15,
                    0.05,
                    0.7
                ]
<p>Les pondérations vous permettent d'attribuer des priorités à chaque composant, en fonction de votre cas d'utilisation et de la qualité de vos données. Intuitivement, la taille de ces pondérations dépend de la valeur sémantique de chaque composant. Étant donné que le morceau de texte lui-même est de loin le plus riche, je lui attribue une pondération de 70%. Les entités étant les plus petites, puisqu'il s'agit simplement d'une liste de noms d'organisations ou de personnes, je leur attribue une pondération de 5%. Le réglage précis de ces valeurs doit être déterminé de manière empirique, au cas par cas.</p><p>Enfin, écrivons une fonction pour appliquer les pondérations et créer notre intégration composite. Pour gagner de la place, nous supprimerons également tous les composants intégrés.</p>from tqdm import tqdm 
def combine_embeddings(objects, embedding_cols, combination_weights, primary_embedding='primary_embedding'):
    # Ensure the number of weights matches the number of embedding columns
    assert len(embedding_cols) == len(combination_weights), "Number of embedding columns must match number of weights"
    
    # Normalize weights to sum to 1
    weights = np.array(combination_weights) / np.sum(combination_weights)
    
    for obj in tqdm(objects, desc="Combining embeddings"):
        # Initialize the combined embedding
        combined = np.zeros_like(obj[embedding_cols[0]])
        
        # Compute the weighted sum
        for col, weight in zip(embedding_cols, weights):
            combined += weight * np.array(obj[col])
        
        # Add the new combined embedding to the object
        obj.update({primary_embedding:combined.tolist()})
        
        # Remove the original embedding columns
        for col in embedding_cols:
            obj.pop(col, None)

combine_embeddings(chunked_documents, embedding_cols, combination_weights)
<p>Nous avons ainsi terminé le traitement des documents. Nous disposons à présent d'une liste d'objets documents qui se présente comme suit :</p>{ 'id_': '7fe71686-5cd0-4831-9e79-998c6dbeae0c', 'chunk': [2312, 14613, ...], 'original_text': 'if an emerging growth company, indicate by check mark if the registrant has elected not to use the extended ...', 'chunk_index': 3, 'chunk_token_count': 399, 'metadata': {'page_label': '3', 'file_name': 'Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf', ... 'keyphrases': 'sep cl unk\ncheck mark registrant\ncl unk indicate\nunk indicate check\nindicate check mark\nprincipal executive office\naccelerate filer unk\ncompany unk emerge\nunk emerge growth\nemerge growth company', 'potential_questions': '1. What are the different types of registrant statuses mentioned in the document?\n2. Under what section of the Sarbanes-Oxley Act must registrants file a report on the effectiveness of their internal ...', 'entities': 'the effe ctiveness of\nsection 13\nSEP\nUNK\nsection 21e\n1934\n1933\nu. s. c.\nsection 404\nsection 12\nal', 'primary_embedding': [-0.3946287803351879, -0.17586839850991964, ...] }
<h4>Indexation vers Elastic</h4><p>Chargeons nos documents en vrac dans Elastic Search. À cette fin, j'ai défini il y a longtemps un ensemble de fonctions Elastic Helper dans <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/elastic_helpers.py"><code>elastic_helpers.py</code></a>. Il s'agit d'un code très long, nous allons donc nous contenter d'examiner les appels de fonction.</p><p><code>es_bulk_indexer.bulk_upload_documents</code> fonctionne avec n'importe quelle liste d'objets dictionnaires, en tirant parti des mappages dynamiques pratiques d'Elasticsearch.</p># Initialize Elasticsearch
ELASTIC_CLOUD_ID = os.environ.get('ELASTIC_CLOUD_ID')
ELASTIC_USERNAME = os.environ.get('ELASTIC_USERNAME')
ELASTIC_PASSWORD = os.environ.get('ELASTIC_PASSWORD')
ELASTIC_CLOUD_AUTH = (ELASTIC_USERNAME, ELASTIC_PASSWORD)
es_bulk_indexer = ESBulkIndexer(cloud_id=ELASTIC_CLOUD_ID, credentials=ELASTIC_CLOUD_AUTH)
es_query_maker = ESQueryMaker(cloud_id=ELASTIC_CLOUD_ID, credentials=ELASTIC_CLOUD_AUTH)

# Define Index Name
index_name=os.environ.get('ELASTIC_INDEX_NAME')


# Create index and bulk upload 
index_exists = es_bulk_indexer.check_index_existence(index_name=index_name)
if not index_exists:
    logger.info(f"Creating new index: {index_name}")
    es_bulk_indexer.create_es_index(es_configuration=BASIC_CONFIG, index_name=index_name)

success_count = es_bulk_indexer.bulk_upload_documents(
    index_name=index_name, 
    documents=chunked_documents, 
    id_col='id_',
    batch_size=32
)
<p>Rendez-vous sur Kibana et vérifiez que tous les documents ont été indexés. Il devrait y en avoir 224. Pas mal pour un document aussi volumineux !</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8efeface6effe01d/6a170b447d8d67652870e72a/1b3b07f6b98ceb65f6594ce4be83c5b0ed7e7cf9-1440x1380.jpg" alt="Index Kibana" /><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Haut de page</a></p><h2>Pause-catalogues</h2><p>Faisons une pause, l'article est un peu lourd, je sais. Jetez un coup d'œil à mon chat :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc1db5595f71c12ff/6a170b450e2e49940241a0fe/baca4eb52b801b21ced97352cc55462f0a12d6b0-969x996.jpg" alt="Pipeline de Han" /><p>Adorable. Le chapeau a disparu et je soupçonne à moitié qu'elle l'a volé et caché quelque part :(</p><p>Félicitations pour avoir réussi à aller aussi loin :)</p><p>Rejoignez-moi dans la <a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">deuxième partie</a> pour tester et évaluer notre pipeline RAG !</p><h2>Annexe</h2><h3>Définitions</h3><p><strong>1. Découpage des phrases</strong></p><ul><li><p>Technique de prétraitement utilisée dans les systèmes RAG pour diviser le texte en unités plus petites et significatives.</p></li><li><p><em>Processus :</em> </p><ol><li><p>Entrée : Grand bloc de texte (par exemple, document, paragraphe)</p></li><li><p>Sortie : Segments de texte plus petits (généralement des phrases ou des petits groupes de phrases)</p></li></ol></li><li><p><em>Objet :</em> </p><ul><li><p>Création de segments de texte granulaires et spécifiques au contexte</p></li><li><p>Permet une indexation et une recherche plus précises</p></li><li><p>Améliore la pertinence des informations recherchées dans les systèmes RAG</p></li></ul></li><li><p><em>Caractéristiques :</em> </p><ul><li><p>Les segments sont sémantiquement significatifs</p></li><li><p>Peuvent être indexés et récupérés de manière indépendante</p></li><li><p>Souvent, le contexte est préservé afin de garantir la compréhensibilité autonome.</p></li></ul></li><li><p><em>Avantages :</em> </p><ul><li><p>Améliore la précision de la recherche</p></li><li><p>Permet une augmentation plus ciblée des pipelines RAG</p></li></ul></li></ul><p><strong>2. HyDE (Hypothetical Document Embedding)</strong></p><ul><li><p>Une technique qui utilise un LLM pour générer un document hypothétique pour l'expansion des requêtes dans les systèmes RAG.</p></li><li><p><em>Processus :</em>  </p><ol><li><p>Requête d'entrée à un LLM</p></li><li><p>LLM génère un document hypothétique répondant à la requête</p></li><li><p>Intégrer le document généré</p></li><li><p>Utiliser l'intégration pour la recherche vectorielle</p></li></ol></li><li><p><em>Différence essentielle :</em> </p><ul><li><p>RAG traditionnel : Correspondance entre la requête et les documents</p></li><li><p>HyDE : fait correspondre des documents à d'autres documents</p></li></ul></li><li><p><em>Objet :</em> </p><ul><li><p>Améliorer les performances de recherche, en particulier pour les requêtes complexes ou ambiguës</p></li><li><p>Saisir un contexte sémantique plus riche qu'une requête courte</p></li></ul></li><li><p><em>Avantages :</em> </p><ul><li><p>Exploite les connaissances du LLM pour élargir les requêtes</p></li><li><p>Peut potentiellement améliorer la pertinence des documents retrouvés</p></li></ul></li><li><p><em>Défis :</em> </p><ul><li><p>Nécessite une inférence LLM supplémentaire, ce qui augmente le temps de latence et le coût.</p></li><li><p>La performance dépend de la qualité du document hypothétique généré</p></li></ul></li></ul><p><strong>3. Emballage inversé</strong></p><ul><li><p>Technique utilisée dans les systèmes RAG pour réorganiser les résultats de la recherche avant de les transmettre au LLM.</p></li><li><p><em>Processus :</em> </p><ol><li><p>Le moteur de recherche (par exemple, Elasticsearch) renvoie les documents par ordre décroissant de pertinence.</p></li><li><p>L'ordre est inversé, le document le plus pertinent étant placé en dernier.</p></li></ol></li><li><p><em>Objet :</em> </p><ul><li><p>Exploite le biais de récence des LLM, qui ont tendance à se concentrer sur les informations les plus récentes dans leur contexte.</p></li><li><p>Veille à ce que les informations les plus pertinentes soient "les plus récentes" dans la fenêtre contextuelle du LLM.</p></li></ul></li><li><p><em>Exemple :</em> Ordre original : [Plus pertinent, Deuxième plus important, Troisième plus important, ...] Ordre inversé : [..., Troisième plus important, Deuxième plus important, Plus important]</p></li></ul><p><strong>4. Classification des requêtes</strong></p><ul><li><p>Technique permettant d'optimiser l'efficacité du système RAG en déterminant si une requête nécessite un RAG ou si elle peut être traitée directement par le LLM.</p></li><li><p><em>Processus :</em> </p><ol><li><p>Développer un ensemble de données personnalisé spécifique au programme d'éducation et de formation tout au long de la vie utilisé</p></li><li><p>Former un modèle de classification spécialisé</p></li><li><p>Utiliser le modèle pour catégoriser les requêtes entrantes</p></li></ol></li><li><p><em>Objet :</em> </p><ul><li><p>Améliorer l'efficacité du système en évitant le traitement inutile des RAG</p></li><li><p>Diriger les demandes vers le mécanisme de réponse le plus approprié</p></li></ul></li><li><p><em>Exigences :</em> </p><ul><li><p>Ensemble de données et modèle spécifiques au LLM</p></li><li><p>Amélioration continue pour maintenir la précision</p></li></ul></li><li><p><em>Avantages :</em> </p><ul><li><p>Réduction de la charge de calcul pour les requêtes simples</p></li><li><p>Amélioration potentielle du temps de réponse pour les requêtes non RAG</p></li></ul></li></ul><p><strong>5. Résumé</strong></p><ul><li><p>Une technique pour condenser les documents récupérés dans les systèmes RAG.</p></li><li><p><em>Processus :</em> </p><ol><li><p>Récupérer les documents pertinents</p></li><li><p>Générer des résumés concis de chaque document</p></li><li><p>Utiliser des résumés plutôt que des documents complets dans le pipeline RAG</p></li></ol></li><li><p><em>Objet :</em> </p><ul><li><p>Améliorer la performance du RAG en se concentrant sur les informations essentielles</p></li><li><p>Réduire le bruit et les interférences provenant de contenus moins pertinents</p></li></ul></li><li><p><em>Avantages :</em> </p><ul><li><p>Amélioration potentielle de la pertinence des réponses au programme d'éducation et de formation tout au long de la vie</p></li><li><p>Permet d'inclure un plus grand nombre de documents dans les limites du contexte</p></li></ul></li><li><p><em>Défis :</em> </p><ul><li><p>Risque de perdre des détails importants dans le résumé</p></li><li><p>Frais de calcul supplémentaires pour la génération du résumé</p></li></ul></li></ul><p><strong>6. Inclusion de métadonnées</strong></p><ul><li><p>Une technique pour enrichir les documents avec des informations contextuelles supplémentaires.</p></li><li><p><em>Types de métadonnées :</em>  </p><ul><li><p>Mots clés</p></li><li><p>Titres</p></li><li><p>Dates</p></li><li><p>Coordonnées de l'auteur</p></li><li><p>Les commentaires</p></li></ul></li><li><p><em>Objet :</em> </p><ul><li><p>Augmenter les informations contextuelles disponibles pour le système RAG</p></li><li><p>Fournir aux gestionnaires du droit d'auteur une meilleure compréhension du contenu et de la pertinence des documents</p></li></ul></li><li><p><em>Avantages :</em> </p><ul><li><p>Amélioration potentielle de la précision de la recherche</p></li><li><p>Améliore la capacité du LLM à évaluer l'utilité des documents</p></li></ul></li><li><p><em>Mise en œuvre :</em> </p><ul><li><p>Peut être effectué lors du prétraitement des documents</p></li><li><p>Peut nécessiter des étapes supplémentaires d'extraction ou de génération de données</p></li></ul></li></ul><p><strong>7. Intégrations composites multi-champs</strong></p><ul><li><p>Une technique d'intégration avancée pour les systèmes RAG qui crée des intégrations distinctes pour les différents composants du document.</p></li><li><p><em>Processus :</em> </p><ol><li><p>Identifier les champs pertinents (par exemple, le titre, les phrases clés, le résumé, le contenu principal)</p></li><li><p>Générer des embeddings distincts pour chaque champ</p></li><li><p>Combiner ou stocker ces encastrements pour les utiliser lors de la recherche.</p></li></ol></li><li><p><em>Différence par rapport à l'approche standard :</em> </p><ul><li><p>Traditionnel : Intégration unique pour l'ensemble du document</p></li><li><p>Composite : Plusieurs encastrements pour différents aspects du document</p></li></ul></li><li><p><em>Objet :</em> </p><ul><li><p>Créer des représentations de documents plus nuancées et tenant compte du contexte</p></li><li><p>Saisir des informations provenant d'une plus grande variété de sources dans un document</p></li></ul></li><li><p><em>Avantages :</em> </p><ul><li><p>Amélioration potentielle des performances sur les requêtes ambiguës ou à multiples facettes</p></li><li><p>Permet une pondération plus souple des différents aspects du document dans la recherche.</p></li></ul></li><li><p><em>Défis :</em> </p><ul><li><p>Complexité accrue de l'intégration des processus de stockage et d'extraction</p></li><li><p>Peut nécessiter des algorithmes d'appariement plus sophistiqués</p></li></ul></li></ul><p><strong>8. Enrichissement des requêtes</strong></p><ul><li><p>Une technique qui consiste à ajouter des termes connexes à la requête initiale afin d'améliorer la couverture de la recherche.</p></li><li><p><em>Processus :</em> </p><ol><li><p>Analyser la requête originale</p></li><li><p>Générer des synonymes et des phrases sémantiquement proches</p></li><li><p>Complétez la requête avec ces termes supplémentaires</p></li></ol></li><li><p><em>Objet :</em> </p><ul><li><p>Augmenter l'éventail des correspondances potentielles dans le corpus de documents</p></li><li><p>Améliorer les performances de recherche pour les requêtes formulées dans un langage spécifique ou technique</p></li></ul></li><li><p><em>Avantages :</em> </p><ul><li><p>Possibilité de retrouver des documents pertinents qui ne correspondent pas exactement aux termes de la requête initiale.</p></li><li><p>Peut aider à surmonter l'inadéquation du vocabulaire entre les requêtes et les documents</p></li></ul></li><li><p><em>Défis :</em> </p><ul><li><p>Risque de dérive des requêtes en l'absence d'une mise en œuvre rigoureuse</p></li><li><p>Peut augmenter la charge de calcul dans le processus de recherche.</p></li></ul></li></ul><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">Haut de page</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[IA]]></category>
    <dc:creator><![CDATA[Han Xiang Choong]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 14 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch et OpenSearch : comparatif de performance pour la recherche vectorielle.]]></title>
    <description><![CDATA[Elasticsearch est d’emblée 2 à 12 fois plus rapide qu’OpenSearch pour la recherche vectorielle]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison#up-to-12x-faster-out-of-the-box">Elasticsearch est jusqu’à 12 fois plus rapide</a> - Chez Elastic, suite aux nombreuses requêtes de notre communauté concernant les écarts de performance entre Elasticsearch et OpenSearch, notamment dans la recherche sémantique et vectorielle, nous avons mené cette série de tests. Le but est d’offrir une comparaison claire et axée sur les données, sans ambiguïté, avec des faits simples pour informer nos utilisateurs. Les résultats montrent qu'<strong>Elasticsearch est jusqu'à 12 fois plus rapide</strong> qu'OpenSearch pour la recherche vectorielle et nécessite donc moins de ressources informatiques. Cela reflète la volonté d’Elastic de se concentrer sur la consolidation de Lucene comme la meilleure base de données vectorielles pour les cas d’utilisation de recherche et de récupération.</p><p>La recherche vectorielle est en train de révolutionner la manière dont nous effectuons les recherches par similarité, en particulier dans des domaines comme l’IA et le Machine Learning. Face à l’adoption de plus en plus répandue des modèles d’intégration de vecteurs, la capacité de rechercher efficacement à travers des millions de vecteurs de haute dimension devient cruciale.</p><p>Elastic et OpenSearch ont choisi des approches très distinctes pour l’exécution des bases de données vectorielles. Pour que ses produits soient le meilleur choix pour les applications de recherche vectorielle, Elastic a investi massivement dans l’optimisation d’Apache Lucene avec Elasticsearch. En revanche, OpenSearch a élargi son champ d’action en intégrant d’autres implémentations de recherche vectorielle et en explorant au-delà de la portée de Lucene. En nous concentrant de manière stratégique sur Lucene, nous pouvons proposer un soutien très intégré dans notre version d’Elasticsearch. Il en résulte un ensemble de fonctionnalités amélioré, où chaque composant complète et amplifie les capacités de l’autre.</p><p>Ce blog offre une comparaison détaillée entre Elasticsearch 8.14 et OpenSearch 2.14, en se basant sur différentes configurations et différents moteurs vectoriels. Dans cette analyse des performances, Elasticsearch s'est avéré être la plateforme supérieure pour les opérations de recherche vectorielle, et les <a href="https://www.elastic.co/search-labs/blog/vector-similarity-computations-ludicrous-speed">fonctionnalités</a> à venir creuseront encore davantage l'<a href="https://www.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains">écart</a>. Comparé à OpenSearch, il a excellé dans tous les domaines de référence — <strong>offrant des performances 2 à 12 fois plus rapides en moyenne</strong>. Cela s’est produit dans des scénarios utilisant des quantités et des dimensions de vecteurs variables, notamment <code>so_vector</code> (2 millions de vecteurs, 768D), <code>openai_vector</code> (2,5 millions de vecteurs, 1536D) et <code>dense_vector</code> (10 millions de vecteurs, 96D), tous disponibles dans <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance">ce référentiel</a> aux côtés des scripts Terraform pour provisionner toute l’infrastructure requise sur Google Cloud et les manifestes Kubernetes pour exécuter les tests.</p><p>Les résultats de ce blog s’ajoutent à ceux d'une étude <a href="https://www.elastic.co/blog/elasticsearch-opensearch-performance-gap">validée par une tierce partie et publiée précédemment</a>. L’étude avait révélé qu’Elasticsearch est plus rapide de 40%–140% qu’OpenSearch en ce qui concerne les opérations d’analyse de recherche les plus courantes : requêtes textuelles, tri, plages, histogramme de dates et filtrage par termes. Maintenant, nous pouvons ajouter un autre facteur de différenciation : la recherche vectorielle. Maintenant, nous pouvons ajouter un autre facteur de différenciation : la recherche vectorielle.</p><h2>Jusqu’à 12 fois plus rapide d’emblée</h2><p>Nos tests d'évaluation ciblés sur les quatre ensembles de données vectorielles impliquaient à la fois des recherches KNN approximatives et KNN exactes, en tenant compte de différentes tailles, dimensions et configurations, totalisant <code>40.189.820</code> demandes de rechercher non mises en cache. Les résultats : <strong>Elasticsearch est jusqu'à 12 fois plus rapide</strong> qu'OpenSearch pour la recherche vectorielle et nécessite donc moins de ressources de calcul.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt34b83c6eba3bcb6e/6a17d727dbb4ff18d6fb54fb/cdb26e91f085b90e9b12aeb8fee53b04d365ecae-1440x1156.webp" alt="p90 moyen" /><p>Figure 1 : Tâches groupées pour ANN et KNN exact dans différentes combinaisons dans Elasticsearch et OpenSearch.</p><p>Les groupes tels que <code>knn-10-100</code> signifient une rechercher KNN avec  et . Dans la recherche vectorielle HNSW,  détermine le nombre de voisins les plus proches à récupérer pour un vecteur de requête. Il définit le nombre de vecteurs similaires qui seront renvoyés en résultat.  définit le nombre de vecteurs candidats à récupérer à chaque segment. Un plus grand nombre de candidats peut renforcer la précision, au prix de ressources de calcul plus importantes.</p><p>Après avoir testé différentes techniques de quantification et tiré parti des optimisations spécifiques à chaque moteur, nous avons obtenu des résultats détaillés pour chaque piste, tâche et moteur vectoriel, que vous trouverez ci-dessous.</p><h2>KNN exact et KNN approximatif</h2><p>Pour des ensembles de données et des cas d’utilisation variés, l’approche appropriée pour la recherche vectorielle variera. Dans ce blog, toutes les tâches indiquées comme <code>knn-*</code> comme <code>knn-10-100</code> utilisent <strong>Approximate KNN</strong> et <code>script-score-*</code> font référence à <strong>Exact KNN</strong>, mais quelle est la différence entre elles et pourquoi sont-elles importantes ?</p><p>Grâce à sa plus grande évolutivité, la méthode de l’algorithme des plus proches voisins approximatifs (ANN) est la solution préférée lorsque vous traitez des ensembles de données plus substantiels. La méthode des plus proches voisins exacts (KNN) est idéale pour les jeux de données plus modestes qui nécessitent parfois un processus de filtrage.</p><p>La méthode du KNN exact emploie une approche par la force brute, qui consiste à calculer la distance entre un vecteur et chaque autre vecteur du jeu de données. Elle classe ensuite ces distances pour trouver les  voisins les plus proches. Même si cette méthode assure une correspondance exacte, elle fait face à des problèmes d’évolutivité pour les jeux de données volumineux et à haute dimension. Toutefois, il y a de nombreuses situations dans lesquelles le recours au KNN exact est requis :</p><ul><li><p><strong>Réévaluation</strong>: dans les cas qui impliquent des recherches lexicales ou sémantiques suivies d’une réévaluation basée sur les vecteurs, le KNN exact est indispensable. Dans un moteur de recherche de produits, par exemple, on peut d’abord filtrer les résultats de recherche initiaux à l’aide de requêtes textuelles (mots-clés, catégories), puis utiliser les vecteurs associés aux éléments filtrés pour une évaluation de similarité plus précise.</p></li><li><p><strong>Personnalisation</strong>: dans le cas d'un grand nombre d'utilisateurs, dont chacun est représenté par un nombre relativement peu élevé (environ 1 million) de vecteurs distincts, le classement de l'index en fonction des métadonnées de l'utilisateur (p. ex., user_id) et le score par force brute avec des vecteurs deviennent efficaces. Cette approche permet de fournir des recommandations personnalisées ou un contenu personnalisé, en fonction de comparaisons de vecteurs précises qui sont spécifiquement conçues pour les préférences de chaque utilisateur.</p></li></ul><p>Le KNN exact permet d’assurer un classement final et des recommandations précis et adaptés aux préférences de l’utilisateur, car ils sont fondés sur la similarité vectorielle.</p><p>D’un autre côté, le KNN approximatif (ANN) utilise des méthodes qui rendent la recherche de données plus rapide et plus efficace que le KNN exact, en particulier dans les jeux de données volumineux et à haute dimension. Contrairement à une approche par force brute qui mesure la distance exacte entre une requête et tous les points (ce qui pose des défis de calcul et de mise à l’échelle), l’ANN recourt à des techniques spécifiques afin de restructurer efficacement les index et les dimensions des vecteurs interrogeables dans l’ensemble de données. Même si cela peut entraîner une légère inexactitude, cela accélère considérablement le processus de recherche, ce qui en fait une solution de rechange efficace pour les ensembles de données volumineux.</p><p>Dans ce blog, toutes les tâches indiquées comme <code>knn-*</code> comme <code>knn-10-100</code> utilisent <strong>KNN approximatif</strong> et <code>script-score-*</code> font référence à <strong>KNN exact</strong>.</p><h2>Méthodologie de test</h2><p>Même si l'API des opérations de recherche BM25 est similaire pour Elasticsearch et OpenSearch (puisque ce dernier est une copie du premier), cela ne s’applique pas à la recherche vectorielle, laquelle a été introduite après la copie. OpenSearch a adopté une approche différente d’Elasticsearch en ce qui concerne les algorithmes, en introduisant deux autres moteurs - <code>nmslib</code> et <code>faiss</code> <code>lucene</code>plus , chacun avec ses configurations et limitations spécifiques (par exemple, <code>nmslib</code> dans OpenSearch n’autorise pas les filtres, une fonctionnalité essentielle pour de nombreux cas d’utilisation).</p><p>Les trois moteurs emploient l’algorithme HNSW, lequel est très performant pour la recherche approximative des plus proches voisins et particulièrement puissant en présence de données de grande dimension. Il est important de noter que <code>faiss</code> prend également en charge un deuxième algorithme, <code>ivf</code>, mais comme il nécessite une formation préalable sur l'ensemble de données, nous allons nous concentrer uniquement sur HNSW. Le concept principal de HNSW est d’organiser les données en couches de graphes connectés, où chaque couche reflète une granularité différente de l’ensemble de données. La recherche s’amorce à la couche supérieure, qui propose la vue la plus grossière. Elle progresse ensuite vers des couches de plus en plus fines jusqu’au niveau de base.</p><p>Les deux moteurs de recherche ont fait l’objet de tests dans un cadre contrôlé, sous des conditions strictement identiques, afin de garantir un terrain d'essai équitable. La méthode utilisée est comparable à <a href="https://www.elastic.co/blog/elasticsearch-opensearch-performance-gap#testing-methodology">la comparaison de performance publiée précédemment</a>, avec des groupes de nœuds dédiés pour Elasticsearch, OpenSearch et Rally. Le <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/main/terraform/main.tf">script terraform</a> est disponible (ainsi que toutes les sources) pour provisionner un cluster Kubernetes avec :</p><ul><li><p>1 pool de nœuds pour Elasticsearch avec 3 machines <code>e2-standard-32</code> (128 Go de RAM et 32 processeurs)</p></li><li><p>1 pool de nœuds pour OpenSearch avec 3 machines <code>e2-standard-32</code> (128 Go de RAM et 32 processeurs)</p></li><li><p>1 pool de nœuds pour Rally avec 2 machines <code>t2a-standard-16</code> (64 Go de RAM et 16 processeurs)</p></li></ul><p>Chaque test (ou piste) a été exécuté à 10 reprises pour chaque configuration, qui incluait différents moteurs, différentes configurations et différents types de vecteurs. Chaque piste se compose de tâches qui sont répétées de 1000 à 10 000 fois, en fonction de la piste. En cas d'échec d’une tâche dans une piste (en raison, par exemple, d’une temporisation de réseau), toutes les tâches sont abandonnées. C’est pourquoi tous les résultats sont issus de pistes qui ont démarré et se sont terminées sans problème. L’ensemble des résultats de test sont validés sur le plan statistique, ce qui assure que les améliorations ne sont pas le résultat d’une coïncidence.</p><h2>Résultats détaillés</h2><p>Pourquoi comparer en utilisant le 99e percentile et non la latence moyenne ? Prenons un exemple hypothétique du prix moyen des logements dans un quartier donné. Le prix moyen peut laisser penser que la zone est onéreuse, mais en y regardant de plus près, il peut se révéler que la plupart des logements sont évalués à un prix beaucoup plus bas, et que seules quelques propriétés de luxe font augmenter la moyenne. Le prix moyen peut ne pas refléter avec précision la gamme complète des valeurs des maisons dans la région, comme l’illustre cet exemple. C’est comparable à l’étude des temps de réponse, où le chiffre moyen peut dissimuler des enjeux critiques.</p><h4>Tâches</h4><ul><li><p>KNN approximatif avec k :10 n :50</p></li><li><p>KNN approximatif avec k : 10 n : 100</p></li><li><p>KNN approximatif avec k : 100 n : 1 000</p></li><li><p>KNN approximatif avec k :10, n :50 et filtres de mots-clés</p></li><li><p>KNN approximatif avec k : 10, n : 100 et filtres de mots-clés</p></li><li><p>KNN approximatif avec k :100, n :1000 et filtres de mots-clés</p></li><li><p>KNN approximatif avec k:10 n:100 en conjonction avec l'indexation</p></li><li><p>KNN exact (score du script)</p></li></ul><h4>Moteurs vectoriels</h4><ul><li><p><code>lucene</code> dans Elasticsearch et OpenSearch, tous deux en version 9.10</p></li><li><p><code>faiss</code> dans OpenSearch</p></li><li><p><code>nmslib</code> dans OpenSearch</p></li></ul><h4>Types de vecteurs</h4><ul><li><p><code>hnsw</code> dans Elasticsearch et OpenSearch</p></li><li><p><code>int8_hnsw</code> dans Elasticsearch (HNSW avec quantification automatique 8 bits–: <a href="https://www.elastic.co/search-labs/blog/evaluating-scalar-quantization">lien</a>)</p></li><li><p><code>sq_fp16 hnsw </code>dans OpenSearch (HNSW avec quantification automatique 16 bits : <a href="https://opensearch.org/docs/2.14/search-plugins/knn/knn-vector-quantization#faiss-16-bit-scalar-quantization">lien</a>)</p></li></ul><h4>Recherche prête à l'emploi et recherche de segments simultanés</h4><p>Lucene est une bibliothèque de moteur de recherche de texte hautement performante, rédigée en Java. Elle constitue la pierre angulaire de nombreuses plateformes de recherche, telles qu’Elasticsearch, OpenSearch et Solr. Le cœur du système de Lucene repose sur l’organisation des données en segments. Ces segments sont des index autonomes qui permettent d’exécuter les recherches plus efficacement. Donc, si vous effectuez une recherche sur un moteur basé sur Lucene, cette recherche sera exécutée dans ces segments, de manière séquentielle ou en parallèle.</p><p>OpenSearch a introduit la recherche de segments simultanés en tant qu’indicateur facultatif et ne l’utilise pas par défaut, vous devez l’activer à l’aide d’un paramètre d’index spécial <code>index.search.concurrent_segment_search.enabled</code> comme détaillé <a href="https://opensearch.org/docs/latest/search-plugins/concurrent-segment-search/">ici</a>, avec certaines <a href="https://opensearch.org/docs/latest/search-plugins/concurrent-segment-search/#other-considerations">limitations</a>.</p><p>En revanche, Elasticsearch effectue des recherches sur les segments en parallèle <a href="https://github.com/elastic/elasticsearch/pull/101230">par défaut</a>. C’est pourquoi les comparaisons que nous effectuons dans cet article de blog tiendront compte, outre des différents moteurs et types de vecteurs, des différentes configurations également :</p><ul><li><p>Elasticsearch ootb : Elasticsearch prêt à l'emploi, avec recherche de segments simultanés ;</p></li><li><p>OpenSearch ootb : sans activation de la recherche par segments simultanés ;</p></li><li><p>OpenSearch css : avec la recherche par segments simultanés activée</p></li></ul><p>À présent, examinons en détail les résultats pour chaque ensemble de données vectorielles qui a été testé :</p><h2>2,5 millions de vecteurs, 1536 dimensions (openai_vector)</h2><p>En commençant par la piste la plus simple, mais aussi la plus grande en termes de dimensions, <a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a> - qui utilise l'<a href="https://huggingface.co/datasets/BeIR/nq">ensemble de données NQ</a> enrichi avec des intégrations générées à l'aide du <a href="https://openai.com/blog/new-and-improved-embedding-model">modèle text-embedding-ada-002</a> d'OpenAI. Il s'agit du plus simple, du fait qu’il ne teste que le KNN approximatif et qu'il ne comporte que 5 tâches. Les tests sont effectués en mode autonome (sans indexation) de même qu’en conjonction avec l’indexation, et avec un seul client ou 8 clients simultanés.</p><h3>Tâches</h3><ul><li><p><strong>standalone-search-knn-10-100-multiple-clients</strong> : recherche sur 2,5 millions de vecteurs avec 8 clients simultanément, k : 10 et n :100</p></li><li><p><strong>standalone-search-knn-100-1000-multiple-clients</strong> : recherche sur 2,5 millions de vecteurs avec 8 clients simultanément, k : 100 et n : 1000</p></li><li><p><strong>standalone-search-knn-10-100-single-client</strong>: recherche sur 2,5 millions de vecteurs avec un seul client, k : 10 et n : 100</p></li><li><p><strong>standalone-search-knn-100-1000-single-client</strong>: recherche sur 2,5 millions de vecteurs avec un seul client, k : 100 et n : 1000</p></li><li><p><strong>parallel-documents-indexing-search-knn-10-100</strong>: indexation sur 2,5 millions de vecteurs tout en recherchant 100 000 documents supplémentaires, k : 10 et n : 100</p></li></ul><p>Les performances moyennes de p99 sont décrites ci-dessous :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5791848eb0c12fb/6a17d72925daab9cae08a09e/eea0b2b49c690baada3e09d6968e513bfffe51a9-1440x318.webp" alt="tableau openai_vector" /><p>Nous avons constaté ici qu’Elasticsearch est <strong>3 à 8 fois plus rapide</strong> qu’OpenSearch lors d’une recherche vectorielle effectuée en parallèle de l'indexation (c’est-à-dire. lecture+écriture) avec :10 et :100 et <strong>2 à 3 fois plus rapide</strong> sans indexation pour les mêmes k et n. Pour :100 et :1000 (<em>standalone-rechercher-knn-100-1000-single-client</em> et <em>standalone-rechercher-knn-100-1000-multiple-clients</em> Elasticsearch est <strong>2 à 7 fois</strong> plus rapide qu'OpenSearch, en moyenne.</p><p>Les résultats détaillés montrent les cas exacts et les moteurs vectoriels comparés :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbe41d3187ced7ec/6a17d72a445de951c44cff4c/a7a761ed631d3e6211beb83d9d93d752d10123c9-1440x1728.webp" alt="openai_vector" /><h4>Rappel</h4><p></p><p>knn-recall-10-100</p><p>knn-recall-100-1000</p><p>Elasticsearch-8.14.0@lucene-hnsw</p><p>0,969485</p><p>0,995138</p><p>Elasticsearch-8.14.0@lucene-int8_hnsw</p><p>0,781445</p><p>0,784817</p><p>OpenSearch-2.14.0@lucene-hnsw</p><p>0,96519</p><p>0,995422</p><p>OpenSearch-2.14.0@faiss</p><p>0,984154</p><p>0,98049</p><p>OpenSearch-2.14.0@faiss-sq_fp16</p><p>0,980012</p><p>0,97721</p><p>OpenSearch-2.14.0@nmslib</p><p>0,982532</p><p>0,99832</p><h2>10 millions de vecteurs, 96 dimensions (dense_vector)</h2><p><a href="https://github.com/elastic/rally-tracks/tree/master/dense_vector">dense_vector</a> avec 10 millions de vecteurs et 96 dimensions. Il est basé sur le jeu de données d'images <a href="https://big-ann-benchmarks.com/">Yandex DEEP1B</a>. Le jeu de données est créé à partir des 10 premiers millions de vecteurs du fichier « données d’échantillon » appelé <code>learn.350M.fbin</code>. Les opérations de recherche font appel à des vecteurs qui proviennent du fichier de « requêtes de données » query.<code>public.10K.fbin</code>.</p><p>Après une <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html">fusion forcée</a>, qui est habituellement effectuée sur des index en lecture seule, Elasticsearch et OpenSearch sont très performants sur cet ensemble de données. Cette opération est similaire à une défragmentation, ce qui permet d’avoir une seule « table » pour la recherche.</p><h3>Tâches</h3><p>Chaque tâche fait l'objet d’un préchauffage de 100 requêtes, et la mesure est ensuite effectuée sur les 1000 requêtes suivantes</p><ul><li><p><strong>knn-search-10-100</strong>: recherche sur 10 millions de vecteurs, k : 10 et n : 100</p></li><li><p><strong>knn-search-100-1000</strong>: recherche sur 10 millions de vecteurs, k : 100 et n : 1000</p></li><li><p><strong>knn-search-10-100-force-merge</strong>: recherche sur 10 millions de vecteurs après une fusion forcée, k : 10 et n : 100</p></li><li><p><strong>knn-search-100-1000-force-merge</strong>: recherche sur 10 millions de vecteurs après une fusion forcée, k : 100 et n :1000</p></li><li><p><strong>knn-search-100-1000-concurrent-with-indexing</strong>: indexation sur 10 millions de vecteurs tout en mettant à jour <a href="https://github.com/elastic/rally-tracks/blob/master/dense_vector/challenges/default.json#L76C36-L76C37">5 % de l'ensemble de données</a>, k : 100 et n : 1000</p></li><li><p><strong>script-score-query</strong>: recherche KNN exacte de <a href="https://github.com/elastic/rally-tracks/blob/master/dense_vector/queries.json">2000 vecteurs spécifiques</a>.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4629d06af85eb96c/6a17d72c6864a423e7b685dc/174995e0a2156d86359cdb7aa446dfaae6312ea4-1440x316.webp" alt="dense_vector" /><p>Elasticsearch et OpenSearch ont tous deux obtenu de bons résultats pour le KNN approximatif. Lorsque l’index est fusionné (c’est-à-dire qu’il n’a qu’un seul segment) dans <em>knn-rechercher-100-1000-force-merge</em> et <em>knn-rechercher-10-100-force-merge</em>, OpenSearch fonctionne mieux que les autres lors de l’utilisation de <code>nmslib</code> et <code>faiss</code>, même s’ils sont tous autour de 15 ms et tous très proches.</p><p>Lorsque l'index a plusieurs segments (ce qui est typique lorsqu'un index reçoit des mises à jour), Elasticsearch maintient la latence autour de ~7ms et ~16ms dans les tests <em>knn-search-10-100</em> et <em>knn-search-100-1000</em>, tandis que tous les autres moteurs OpenSearch sont plus lents.</p><p>Lorsque l'index est interrogé et mis à jour simultanément (<em>knn-search-100-1000-concurrent-with-indexing</em>), Elasticsearch maintient une latence inférieure à 15 ms (13,8 ms). Il est presque <strong>4 fois plus rapide</strong> qu’OpenSearch par défaut (49,3 ms) et reste plus rapide lorsque la recherche concurrente de segments est activée (17,9 ms), bien que la différence ne soit pas significative.</p><p>En ce qui concerne le KNN exact, l’écart est bien plus important : Elasticsearch <strong>est 6 fois plus rapide</strong> qu’OpenSearch (~260 ms contre ~1600 ms).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt254f43bcaa3dbfc2/6a17d72ddbb4ffc780fb54ff/17aec6be31117440bc4d1f99984aed95df1c4f6b-1440x1728.webp" alt="dense_vector" /><h4>Rappel</h4><p></p><p>knn-recall-10-100</p><p>knn-recall-100-1000</p><p>Elasticsearch-8.14.0@lucene-hnsw</p><p>0,969843</p><p>0,996577</p><p>Elasticsearch-8.14.0@lucene-int8_hnsw</p><p>0,775458</p><p>0,840254</p><p>OpenSearch-2.14.0@lucene-hnsw</p><p>0,971333</p><p>0,996747</p><p>OpenSearch-2.14.0@faiss</p><p>0,9704</p><p>0,914755</p><p>OpenSearch-2.14.0@faiss-sq_fp16</p><p>0,968025</p><p>0,913862</p><p>OpenSearch-2.14.0@nmslib</p><p>0,9674</p><p>0,910303</p><h2>2 millions de vecteurs, 768 dimensions (so_vector)</h2><p>Cette <a href="https://github.com/elastic/rally-tracks/tree/master/so_vector">piste</a>, <code>so_vector</code>, est dérivée d’une <a href="https://archive.org/download/stackexchange/stackoverflow.com-Posts.7z">extraction des publications de StackOverflow téléchargée</a> le 21 avril 2022. Seuls les documents de questions y figurent, les documents de réponses ayant tous été supprimés. Chaque titre de question a été encodé dans un vecteur en utilisant le modèle de transformateur de phrase <a href="https://huggingface.co/sentence-transformers/multi-qa-mpnet-base-cos-v1">multi-qa-mpnet-base-cos-v1</a>. Cet ensemble de données contient les 2 premiers millions de questions.</p><p>Contrairement à la piste précédente, chaque document ici contient d'autres champs en plus des vecteurs pour prendre en charge le test de fonctionnalités comme le KNN approximatif avec filtrage et la recherche hybride. <code>nmslib</code> pour OpenSearch est notamment absent dans ce test car <a href="https://opensearch.org/docs/latest/search-plugins/knn/filter-search-knn/#k-nn-search-with-filters">il ne prend pas en charge les filtres</a>.</p><h3>Tâches</h3><p>Chaque tâche fait l'objet d’un préchauffage de 100 requêtes, et la mesure est ensuite effectuée sur les 100 requêtes suivantes. Veuillez noter que les tâches ont été groupées dans un souci de simplicité, le test contenant 16 types de recherche, 2 valeurs k et 3 valeurs n différentes.</p><ul><li><p><strong>KNN-10-50</strong>: recherche sur 2 millions de vecteurs sans filtres, k :10 et n :50</p></li><li><p><strong>knn-10-50-filtered</strong>: recherche sur 2 millions de vecteurs <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">avec des filtres</a>, k :10 et n :50</p></li><li><p><strong>knn-10-50-after-force-merge</strong>: recherche sur 2 millions de vecteurs avec filtres et après une fusion forcée, k :10 et n :50</p></li><li><p><strong>KNN-10-100</strong>: recherche sur 2 millions de vecteurs sans filtres, k :10 et n :100</p></li><li><p><strong>knn-10-100-filtered</strong>: recherche sur 2 millions de vecteurs <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">avec des filtres</a>, k :10 et n :100</p></li><li><p><strong>knn-10-100-after-force-merge</strong>: recherche sur 2 millions de vecteurs avec filtres et après une fusion forcée, k :10 et n :100</p></li><li><p><strong>KNN-100-1000</strong>: Recherche sur 2 millions de vecteurs sans filtres, k :100 et n :1000</p></li><li><p><strong>knn-100-1000-filtered</strong>: recherche sur 2 millions de vecteurs <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">avec des filtres</a>, k :100 et n :1000</p></li><li><p><strong>knn-100-1000-after-force-merge</strong>: recherche sur 2 millions de vecteurs avec filtres et après une fusion forcée, k :100 et n :1000</p></li><li><p><strong>exact-knn</strong>: recherche KNN exacte <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json#L56">avec et sans filtres</a>.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8b44e1306af35877/6a17d72f577262aca11bca3e/d4ed2982d55370cad4b2048b23ce97caa55017c0-1440x316.webp" alt="table so_vector" /><p>Au cours de ce test, Elasticsearch est <strong>toujours plus rapide</strong> qu’OpenSearch par défaut, sauf dans deux cas où la différence n’est pas très grande (<em>knn-10-100</em> et <em>knn-100-1000</em>). Les tâches impliquant <em>knn-10-50</em>, <em>knn-10-100</em> et <em>knn-100-1000</em> en combinaison avec des filtres montrent une différence allant jusqu'à <strong>7x</strong> (112 ms contre 803 ms).</p><p>Les performances des deux solutions semblent s'équilibrer après une « fusion forcée », ce qui est compréhensible, comme en témoignent <em>knn-10-50-after-force-merge</em>, <em>knn-10-100-after-force-merge</em> et <em>knn-100-1000-after-force-merge.</em> Pour ces tâches, <code>faiss</code> est plus rapide.</p><p>La performance pour le KNN exact est à nouveau très différente : Elasticsearch est cette fois <strong>13 fois plus rapide</strong> qu’OpenSearch (~385 ms contre ~5262 ms).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf80ccc7c2df84559/6a17d7314b055de00d43203f/615cb9228eb05ddd2e9512b3a6a5bc88d4088a1a-1440x1440.webp" alt="so_vector" /><h4>Rappel</h4><p></p><p>knn-recall-10-100</p><p>knn-recall-100-1000</p><p>knn-recall-10-50</p><p>Elasticsearch-8.14.0@lucene-hnsw</p><p>1</p><p>1</p><p>1</p><p>Elasticsearch-8.14.0@lucene-int8_hnsw</p><p>1</p><p>0,986667</p><p>1</p><p>OpenSearch-2.14.0@lucene-hnsw</p><p>1</p><p>1</p><p>1</p><p>OpenSearch-2.14.0@faiss</p><p>1</p><p>1</p><p>1</p><p>OpenSearch-2.14.0@faiss-sq_fp16</p><p>1</p><p>1</p><p>1</p><p>OpenSearch-2.14.0@nmslib</p><p>0,9674</p><p>0,910303</p><p>0,976394</p><h2>Elasticsearch et Lucene, les vainqueurs incontestables</h2><p>Chez Elastic, nous innovons sans cesse avec Apache Lucene et Elasticsearch pour pouvoir proposer la meilleure base de données vectorielles pour les cas d'utilisation de recherche et de récupération, y compris la RAG (Génération augmentée de récupération). Nos avancées récentes ont considérablement amélioré les performances, rendant la recherche vectorielle <a href="https://search-labs.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains">plus rapide et plus économe en espace</a> qu'auparavant, en s'appuyant sur les améliorations de Lucene 9.10. Ce blog présente une étude qui montre que lorsque l’on compare les versions les plus récentes, Elasticsearch est jusqu’à 12 fois plus rapide qu’OpenSearch.</p><p>Il convient de noter que les deux produits utilisent la même version de Lucene (<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/release-notes-8.14.0.html">Notes de publication d'Elasticsearch 8.14</a> et <a href="https://github.com/opensearch-project/OpenSearch/blob/2.14/release-notes/opensearch.release-notes-2.14.0.md">Notes de publication d'OpenSearch 2.14</a>).</p><p>Le rythme d'innovation d'Elastic nous permettra d'aller encore plus loin, non seulement pour nos clients sur site et Elastic Cloud, mais aussi pour ceux qui utilisent notre <a href="https://www.elastic.co/search-labs/blog/stateless-your-new-state-of-find-with-elasticsearch">plateforme sans état.</a> Des fonctionnalités comme la prise en charge de la <a href="https://www.elastic.co/search-labs/blog/int4-scalar-quantization-in-lucene">quantification scalaire vers int4</a> seront proposées avec des tests rigoureux, afin que les clients puissent utiliser ces techniques sans une perte significative d'exactitude, de la même manière que <a href="https://www.elastic.co/search-labs/blog/evaluating-scalar-quantization">nos tests pour l’int8</a>.</p><p>La capacité d’une recherche vectorielle à être efficace est un critère non négociable pour les moteurs de recherche modernes, du fait de la prolifération des applications d’IA et de Machine Learning. Elasticsearch est la solution qui s’impose pour les organisations qui ont besoin d’un moteur de recherche puissant capable de gérer la demande en données vectorielles de grand volume et de haute complexité.</p><p>L’intégration d’Elasticsearch pour les besoins de recherche vectorielle, que ce soit pour étendre une plateforme établie ou lancer de nouveaux projets, représente une approche stratégique qui générera des bénéfices tangibles et à long terme. Fort de son avantage de performance avéré, Elasticsearch est bien placé pour sous-tendre la prochaine vague d’innovations dans la recherche.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[Lucene]]></category>
    <dc:creator><![CDATA[Ugo Sangiorgi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5d70b25967c2194e/6a17d732b1e11383f879f0ca/13c3c0053e2968fb835ba2f90f34bec3a011b5c0-880x592.webp" length="0" type="image/webp"/>
    <pubDate>Wed, 26 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Mesures de similarité vectorielle et notation]]></title>
    <description><![CDATA[Explorez les mesures de similarité vectorielle et la notation dans Elasticsearch, y compris la distance L1 &amp; L2, la similarité de cosinus, la similarité de produit de point et la similarité de produit intérieur maximal.]]></description>
    <content:encoded><![CDATA[<p>Lorsque le besoin de rechercher un texte libre se fait sentir et que les touches Ctrl+F / Cmd+F ne suffisent plus, l'utilisation d'un moteur de recherche lexicale est généralement le choix logique qui vient à l'esprit. Les moteurs de recherche lexicale excellent dans l'analyse et la symbolisation du texte à rechercher en termes qui peuvent être mis en correspondance au moment de la recherche, mais ils ne parviennent généralement pas à comprendre et à donner un sens au texte indexé et recherché.</p><p>C'est précisément là que les moteurs de recherche vectoriels brillent. Ils peuvent indexer le même texte de manière à ce qu'il puisse être recherché sur la base du sens qu'il représente et de ses relations avec d'autres concepts ayant un sens similaire ou apparenté.</p><p>Dans ce blog, nous aborderons brièvement la façon dont les vecteurs sont un concept mathématique idéal pour transmettre le sens d'un texte. Nous approfondirons ensuite les différentes techniques de similarité prises en charge par Elasticsearch lorsqu'il s'agit de rechercher des vecteurs voisins, c'est-à-dire de rechercher des vecteurs ayant une signification similaire, et la manière de les classer.</p><h2>Que sont les plongements vectoriels ?</h2><p>Cet article n'aborde pas en profondeur les subtilités des encastrements vectoriels. Si vous souhaitez approfondir ce sujet ou si vous avez besoin d'une introduction avant de poursuivre, nous vous recommandons de consulter le <a href="https://www.elastic.co/fr/what-is/vector-embedding">guide suivant.</a></p><p>En bref, les encastrements vectoriels sont obtenus par un processus d'apprentissage automatique (par ex. réseaux neuronaux d'apprentissage profond) qui transforme tout type de données d'entrée non structurées (texte brut, image, vidéo, son, etc.) en données numériques porteuses de sens et de relations. Les différents types de données non structurées nécessitent différents types de modèles d'apprentissage automatique qui ont été formés pour "comprendre" chaque type de données.</p><p>Chaque vecteur localise un élément spécifique des données en tant que point dans un espace multidimensionnel et cette localisation représente un ensemble de caractéristiques que le modèle utilise pour caractériser les données. Le nombre de dimensions dépend du modèle d'apprentissage automatique, mais il varie généralement de quelques centaines à quelques milliers. Par exemple, les <a href="https://platform.openai.com/docs/guides/embeddings">modèles OpenAI Embeddings</a> peuvent se vanter d'avoir 1536 dimensions, tandis que <a href="https://docs.cohere.com/reference/embed">les modèles Cohere Embeddings</a> peuvent avoir entre 382 et 4096 dimensions. Le type de champ Elasticsearch dense_vector prend en charge jusqu'à 4096 dimensions depuis la dernière version.</p><p>La véritable prouesse des encastrements vectoriels réside dans le fait que les points de données qui partagent une signification similaire sont proches les uns des autres dans l'espace. Un autre aspect intéressant est que l'intégration vectorielle permet également de saisir les relations entre les points de données.</p><h2>Comment comparer les vecteurs ?</h2><p>Sachant que les données non structurées sont découpées en tranches par des modèles d'apprentissage automatique sous forme de vecteurs qui capturent la similarité des données selon un grand nombre de dimensions, nous devons maintenant comprendre comment fonctionne la mise en correspondance de ces vecteurs. Il s'avère que la réponse est assez simple.</p><p>Les vecteurs intégrés qui sont <strong>proches</strong> les uns des autres représentent des éléments de données <strong>sémantiquement similaires</strong>. Ainsi, lorsque nous interrogeons une base de données vectorielle, l'entrée de recherche (image, texte, etc.) est d'abord transformée en un vecteur intégré à l'aide du même modèle d'apprentissage automatique que celui utilisé pour l'indexation de toutes les données non structurées, et l'objectif final est de trouver les <strong>vecteurs voisins les plus proches</strong> de ce vecteur d'interrogation. Par conséquent, tout ce que nous avons à faire est de trouver comment mesurer la distance "" ou la similarité "" entre le vecteur de la requête et tous les vecteurs existants indexés dans la base de données - c'est aussi simple que cela.</p><h2>Distance, similarité et notation</h2><p>Heureusement pour nous, la mesure de la distance ou de la similarité entre deux vecteurs est un problème facile à résoudre grâce à l'arithmétique vectorielle. Examinons donc les fonctions de distance et de similarité les plus populaires prises en charge par Elasticsearch. Attention, mathématiques en perspective !</p><p>Avant d'entrer dans le vif du sujet, jetons un coup d'œil rapide au système de notation. En réalité, Lucene n'autorise que les scores positifs. Toutes les fonctions de distance et de similarité que nous présenterons prochainement donnent une mesure de la proximité ou de la similarité de deux vecteurs, mais ces chiffres bruts sont rarement adaptés à une utilisation en tant que score car ils peuvent être négatifs. C'est pourquoi la note finale doit être dérivée de la distance ou de la valeur de similarité de manière à ce que la note soit positive et qu'une note plus élevée corresponde à un meilleur classement (c'est-à-dire à des vecteurs plus proches).</p><h3>Distance L1</h3><p>La distance L1, également appelée distance de Manhattan, de deux vecteurs  et  est mesurée en additionnant la différence absolue par paire de tous leurs éléments. Il est évident que plus la distance  est faible, plus les deux vecteurs sont proches. La formule de la distance L1 (1) est assez simple, comme on peut le voir ci-dessous :</p><p>Visuellement, la distance L1 peut être illustrée comme le montre l'image ci-dessous (en rouge) :</p><p>Le calcul de la distance L1 des deux vecteurs suivants : \vec   .</p><p><strong>Important :</strong> il convient de noter que la fonction de distance L1 n'est prise en charge que pour <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/current/query-dsl-script-score-query.html#vector-functions-l1">la recherche vectorielle exacte</a> (également appelée recherche par force brute) à l'aide de la <code>script_score</code> requête DSL, mais pas pour <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/8.11/dense-vector.html#dense-vector-params"></a> la <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/8.11/knn-search.html#approximate-knn"><code>knn</code></a><a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/8.11/knn-search.html#approximate-knn"> recherche approximative kNN à l'aide de l' option</a> de recherche ou de la <a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/current/query-dsl-knn-query.html"><code>knn</code></a><a href="https://www.elastic.co/fr/guide/en/elasticsearch/reference/current/query-dsl-knn-query.html"> requête</a> DSL.</p><h3>Distance L2</h3><p>La distance L2, également appelée distance euclidienne, de deux vecteurs  et  est mesurée en additionnant d'abord le carré de la différence par paire de tous leurs éléments, puis en prenant la racine carrée du résultat. Il s'agit en fait du chemin le plus court entre deux points. Comme pour L1, plus la distance  est faible, plus les deux vecteurs sont proches :</p><p>La distance L2 est indiquée en rouge dans l'image ci-dessous :</p><p>Réutilisons les deux mêmes vecteurs d'échantillonnage  et  que nous avons utilisés pour la distance , et nous pouvons maintenant calculer la distance  comme \sqrt \approxeq  1,803.</p><p>En ce qui concerne la notation, plus la distance entre deux vecteurs est faible, plus ils sont proches (c'est-à-dire plus ils se ressemblent). Pour obtenir un score, nous devons donc inverser la mesure de la distance, de sorte que la distance la plus faible donne le score le plus élevé. La façon dont le score est calculé en utilisant la distance L2 est présentée dans la formule (3) ci-dessous :</p><p>En réutilisant les vecteurs d'échantillonnage de l'exemple précédent, leur score serait \frac   0,2352. Deux vecteurs très proches l'un de l'autre auront un score proche de 1, tandis que le score de deux vecteurs très éloignés l'un de l'autre tendra vers 0.</p><p>Pour conclure sur les fonctions de distance L1 et L2, une bonne analogie pour les comparer est de considérer A et B comme deux bâtiments à Manhattan, NYC. Un taxi se rendant de A à B devrait emprunter le chemin L1 (rues et avenues), tandis qu'un oiseau utiliserait probablement le chemin L2 (ligne droite).</p><h3>Similitude du cosinus</h3><p>Contrairement à L1 et L2, la similarité en cosinus ne mesure pas la distance entre deux vecteurs  et , mais plutôt leur angle relatif, c'est-à-dire s'ils pointent tous deux à peu près dans la même direction. Plus la similarité  est élevée, plus l'angle  entre les deux vecteurs est faible et, par conséquent, plus "" ils sont proches et plus "" leur signification est similaire.</p><p>Pour illustrer cela, imaginons deux personnes dans la nature qui regardent dans des directions différentes. Dans la figure ci-dessous, la personne en bleu regarde dans la direction symbolisée par le vecteur  et la personne en rouge dans la direction du vecteur . Plus ils orienteront leur regard dans la même direction (c'est-à-dire plus leurs vecteurs se rapprochent), plus leur champ de vision symbolisé par les zones bleues et rouges se chevauchera. Le degré de chevauchement de leur champ de vision correspond à leur similarité cosinusoïdale. Notez toutefois que la personne B regarde plus loin que la personne A (le vecteur  est plus long). La personne B peut regarder une montagne au loin à l'horizon, tandis que la personne A peut regarder un arbre à proximité. Pour la similitude du cosinus, cela ne joue aucun rôle puisqu'il s'agit uniquement de l'angle.</p><p>Calculons maintenant la similitude en cosinus. La formule (4) est assez simple : le numérateur est le produit de points des deux vecteurs et le dénominateur est le produit de leur magnitude (c'est-à-dire de leur longueur) :</p><p>La similarité en cosinus entre  et  est représentée dans l'image ci-dessous comme une mesure de l'angle qui les sépare (en rouge) :</p><p>Faisons un petit détour pour expliquer ce que signifient concrètement ces valeurs de similitude cosinusoïdale. Comme le montre l'image ci-dessous représentant la fonction cosinus, les valeurs oscillent toujours dans l'intervalle </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc0e4eb98ebb64cf3/6a17da287b54f983818b3783/e31145284b8c27d0bd9e3d2831f811388134fd83-1188x272.png" alt="fonction cos" /><p>Rappelons que pour que deux vecteurs soient considérés comme similaires, leur angle doit être le plus aigu possible, idéalement proche de , ce qui revient à une similitude parfaite de  En d'autres termes, lorsque les vecteurs sont...</p><ol><li><p>..<strong>.proches</strong> l'un de l'autre, le cosinus de leur angle est proche de  (c'est-à-dire proche de )</p></li></ol><ol><li><p>..<strong>.sans rapport</strong>, le cosinus de leur angle est proche de  (c'est-à-dire proche de )</p></li></ol><ol><li><p>..<strong>.opposé</strong>, le cosinus de leur angle est proche de  (c'est-à-dire proche de )</p></li></ol><p>Maintenant que nous savons comment calculer la similitude en cosinus entre deux vecteurs et que nous avons une bonne idée de la manière d'interpréter la valeur obtenue, nous pouvons réutiliser les mêmes vecteurs échantillons  et  et calculer leur similitude en cosinus à l'aide de la formule (4) que nous avons vue précédemment.</p><p>Nous obtenons un cosinus similaire de , qui est plus proche de  que de . Cela signifie que les deux vecteurs sont <strong>quelque peu similaires</strong>, c'est-à-dire qu'ils ne sont pas parfaitement similaires, mais qu'ils ne sont pas non plus complètement sans rapport, et qu'ils n'ont certainement pas de signification opposée.</p><p>Pour obtenir un score positif à partir de n'importe quelle valeur de similarité en cosinus, nous devons utiliser la formule suivante (5), qui transforme les valeurs de similarité en cosinus oscillant dans l'intervalle  en scores dans l'intervalle </p><p>Le score pour les vecteurs échantillons  et  serait donc : \frac   0.8253.</p><h3>Similitude du produit de points</h3><p>L'un des inconvénients de la similitude en cosinus est qu'elle ne prend en compte que l'angle entre deux vecteurs et non leur magnitude, ce qui signifie que si deux vecteurs pointent à peu près dans la même direction mais que l'un est beaucoup plus long que l'autre, les deux seront tout de même considérés comme similaires. La similarité du produit de points, également appelée similarité scalaire ou produit intérieur, améliore cette situation en tenant compte à la fois de l'angle et de la magnitude des vecteurs, ce qui permet d'obtenir une mesure de similarité plus précise. Pour que la magnitude des vecteurs n'ait pas d'importance, la similitude du produit de points exige que les vecteurs soient d'abord normalisés, de sorte que nous ne comparons finalement que des vecteurs de longueur unitaire 1.</p><p>Essayons à nouveau d'illustrer cela avec les deux mêmes personnes que précédemment, mais cette fois-ci, nous les plaçons au milieu d'une pièce circulaire, de sorte que leur portée visuelle soit exactement la même (c.-à-d. le rayon de la pièce). De la même manière que pour la similarité cosinus, plus ils tournent dans la même direction (c'est-à-dire plus leurs vecteurs se rapprochent), plus leur champ de vision se chevauche. Cependant, contrairement à la similitude de cosinus, les deux vecteurs ont la même longueur et les deux aires ont la même surface, ce qui signifie que les deux personnes regardent exactement la même image située à la même distance. Le degré de chevauchement de ces deux zones correspond à la similitude de leur produit de point.</p><p>Avant d'introduire la formule de similarité du produit de points, voyons rapidement comment un vecteur peut être normalisé. C'est assez simple et peut être réalisé en deux étapes triviales :</p><ol><li><p>calculer la magnitude du vecteur</p></li><li><p>diviser chaque composante par la valeur obtenue en 1.</p></li></ol><p>Prenons par exemple le vecteur \vec  Nous pouvons calculer sa magnitude \Vert  \Vert comme nous l'avons vu précédemment lors de l'examen de la similitude du cosinus, c'est-à-dire \sqrt . Ensuite, en divisant chaque composante du vecteur par sa magnitude, on obtient le vecteur normalisé suivant :</p><p>En procédant de la même manière pour le deuxième vecteur \vec  :</p><p>Afin de dériver la formule de similarité du produit en points, nous pouvons calculer la similarité en cosinus entre nos vecteurs normalisés  et  en utilisant la formule (4), comme indiqué ci-dessous :</p><p>Et comme la magnitude des deux vecteurs normalisés est maintenant de , la formule de similitude du produit par points (6) devient simplement... vous l'avez deviné, un produit par points des deux vecteurs normalisés :</p><p>Dans l'image ci-dessous, nous montrons les vecteurs normalisés  et  et nous pouvons illustrer la similarité de leur produit de point comme la projection d'un vecteur sur l'autre (en rouge).</p><p>En utilisant notre nouvelle formule (6), nous pouvons calculer la similarité du produit de points de nos deux vecteurs normalisés, ce qui, sans surprise, donne exactement la même valeur de similarité que celle du cosinus :</p><p>Lorsque l'on utilise la similarité du produit de points, le score est calculé différemment selon que les vecteurs contiennent des valeurs flottantes ou des valeurs d'octets. Dans le premier cas, le score est calculé de la même manière que pour la similarité en cosinus en utilisant la formule (7) ci-dessous :</p><p>Toutefois, lorsque le vecteur est composé de valeurs d'octets, la notation est calculée un peu différemment, comme le montre la formule (8) ci-dessous, où  est le nombre de dimensions du vecteur :</p><p>En outre, pour obtenir des résultats précis, il faut que tous les vecteurs, y compris le vecteur d'interrogation, aient la même longueur, mais pas nécessairement la même longueur (1).</p><h3>Similitude maximale du produit intérieur</h3><p>Depuis la version 8.11, il existe une nouvelle fonction de similarité qui est moins contraignante que la similarité par produit de points, dans la mesure où les vecteurs n'ont pas besoin d'être normalisés. La raison principale est expliquée en détail dans l'<a href="https://www.elastic.co/fr/search-labs/blog/lucene-bringing-maximum-inner-product-to-lucene">article suivant</a>, mais pour résumer très brièvement, certains ensembles de données ne sont pas très bien adaptés à la normalisation de leurs vecteurs (par exemple, <a href="https://www.elastic.co/fr/search-labs/blog/elasticsearch-cohere-embeddings-support">Cohere embeddings</a>) et cette normalisation peut entraîner des problèmes de pertinence.</p><p>La formule de calcul de la similarité maximale du produit intérieur est exactement la même que celle du produit de points (6). Ce qui change, c'est la manière dont le score est calculé en mettant à l'échelle la similarité maximale du produit intérieur à l'aide d'une fonction par morceaux dont la formule dépend du fait que la similarité est positive ou négative, comme le montre la formule (9) ci-dessous :</p><p>Cette fonction par morceaux met à l'échelle toutes les valeurs négatives du produit intérieur maximal dans l'intervalle  et toutes les valeurs positives dans l'intervalle </p><h2>En résumé</h2><p>C'était un sacré parcours, mathématiquement parlant, mais voici quelques enseignements qui pourraient vous être utiles.</p><p>La fonction de similarité que vous pouvez utiliser dépend en fin de compte de la normalisation ou non de vos encastrements vectoriels. Si vos vecteurs sont déjà normalisés ou si votre ensemble de données ne dépend pas de la normalisation des vecteurs (c'est-à-dire que la pertinence n'en souffrira pas), vous pouvez aller de l'avant et normaliser vos vecteurs et utiliser la similarité par produit de points, car elle est beaucoup plus rapide à calculer que celle par cosinus, puisqu'il n'est pas nécessaire de calculer la longueur de chaque vecteur. Lorsque l'on compare des millions de vecteurs, ces calculs peuvent s'avérer très lourds.</p><p>Si vos vecteurs ne sont pas normalisés, vous avez deux possibilités :</p><ol><li><p>utiliser la similarité en cosinus si la normalisation des vecteurs n'est pas possible</p></li><li><p>utiliser le nouveau produit intérieur maximal de similarité si vous souhaitez que la magnitude de vos vecteurs contribue à la notation parce qu'elle est porteuse de sens (par exemple, Cohere embeddings).</p></li></ol><p>À ce stade, le calcul de la distance ou de la similarité entre les intégrations vectorielles et la manière de dériver leurs scores devraient vous sembler évidents. Nous espérons que cet article vous a été utile.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-similarity-measures-and-scoring</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-similarity-measures-and-scoring</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Valentin Crettaz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5a3e9d39849d3ed/6a17da31a2929960a3d02b3f/4d9e89678798b9de68357b5cc06dbbd8b9c6e5e8-1440x823.webp" length="0" type="image/webp"/>
    <pubDate>Mon, 13 May 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Plagiat par l'IA : Détection de plagiat avec Elasticsearch]]></title>
    <description><![CDATA[Voici comment vérifier le plagiat de l'IA à l'aide d'Elasticsearch, en se concentrant sur les cas d'utilisation avec les modèles NLP et Vector Search.]]></description>
    <content:encoded><![CDATA[<p>Le plagiat peut être <strong>direct</strong>, c'est-à-dire qu'il consiste à copier des parties ou la totalité du contenu, ou <strong>paraphrasé</strong>, c'est-à-dire qu'il consiste à reformuler le travail de l'auteur en changeant certains mots ou certaines phrases.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6e139760e56ec98/6a171147dc55de0ad2e00edf/5d7073187fda829438aeec8d3a1194a5bea2ba57-1440x347.png" alt="" /><p>Il existe une distinction entre l'inspiration et la paraphrase. Il est possible de lire un contenu, de s'en inspirer, puis d'explorer l'idée avec ses propres mots, même si l'on arrive à une conclusion similaire.</p><p>Si le plagiat est un sujet de discussion depuis longtemps, l'accélération de la production et de la publication de contenus lui a donné toute sa pertinence et constitue un défi permanent.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc2af1678cb04506b/6a171149cf4f251c2ab2d257/a0b9a98d729db09dae0a79315c001e6763c12704-1400x1016.png" alt="" /><p>Ce défi ne se limite pas aux livres, à la recherche universitaire ou aux documents judiciaires, pour lesquels des contrôles de plagiat sont fréquemment effectués. Elle peut également s'étendre aux journaux et même aux médias sociaux.</p><p>Avec l'abondance d'informations et l'accès facile à la publication, comment le plagiat peut-il être contrôlé de manière efficace et à grande échelle ?</p><p>Les universités, les organismes publics et les entreprises utilisent divers outils, mais si une simple <a href="https://www.elastic.co/search-labs/lexical-and-semantic-search-with-elasticsearch">recherche lexicale</a> permet de détecter efficacement le plagiat direct, la principale difficulté réside dans l'identification du <strong>contenu paraphrasé</strong>.</p><h2>Détection du plagiat avec l'IA générative</h2><p>L'IA générative pose un nouveau défi. Le contenu généré par l'IA est-il considéré comme du plagiat lorsqu'il est copié ?</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a1ed1b6fa3f1a56/6a17114b4a531bc40736aa69/9345b28d6d27c37469bc38e823c41780b4eabfe5-1440x875.png" alt="" /><p>Les <a href="https://openai.com/"></a> <a href="https://openai.com/policies/terms-of-use">conditions d'utilisation</a> de l'OpenAI, par exemple, précisent que l'OpenAI ne revendiquera pas de droits d'auteur sur le contenu généré par l'API pour les utilisateurs. Dans ce cas, les personnes utilisant leur IA générative peuvent utiliser le contenu généré comme elles le souhaitent, sans le citer.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc2e08ab5b808e38/6a17114dab7f086cbadb9f93/e1f415f69a247666f02ddc81468944920c874cd7-968x814.png" alt="" /><p>Toutefois, l'acceptation de l'utilisation de l'IA générative pour améliorer l'efficacité reste un sujet de discussion.</p><p>Afin de contribuer à la détection du plagiat, OpenAI a développé un <a href="https://huggingface.co/roberta-base-openai-detector">modèle de détection</a>, mais a reconnu par la suite que sa précision n'était pas suffisamment élevée.</p><p><em>"Nous pensons que cette précision n'est pas suffisante pour une détection autonome et qu'elle doit être associée à des approches basées sur les métadonnées, au jugement humain et à l'éducation du public pour être plus efficace."</em></p><p>Le défi persiste ; cependant, grâce à la disponibilité d'outils supplémentaires, il existe désormais davantage d'options pour détecter le plagiat, même dans les cas de paraphrases et de contenu d'IA.</p><h2>Détecter le plagiat avec Elasticsearch</h2><p>C'est pourquoi, dans ce blog, nous explorons un autre cas d'utilisation des modèles de traitement du langage naturel (NLP) et de la recherche vectorielle, la détection du plagiat, au-delà de la recherche de métadonnées.</p><p>Nous le démontrons à l'aide d'<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/plagiarism-detection-with-elasticsearch/plagiarism_detection_es.ipynb">exemples en Python</a>, où nous utilisons un <a href="https://sbert.net/datasets/emnlp2016-2018.json">ensemble de données</a> de <a href="https://www.sbert.net/">SentenceTransformers</a> contenant des articles liés au TAL. Nous vérifions que les résumés ne sont pas plagiés en effectuant une "similarité textuelle sémantique" en tenant compte de l'intégration des "résumés" générée à l'aide d'un <a href="https://huggingface.co/sentence-transformers/all-mpnet-base-v2">modèle d'intégration de texte</a> importé au préalable dans Elasticsearch. En outre, pour identifier le contenu généré par l'IA - le plagiat par l'IA, un <a href="https://huggingface.co/roberta-base-openai-detector">modèle NLP</a> développé par OpenAI a également été importé dans Elasticsearch.</p><p>L'image suivante illustre le flux de données :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0464f88d3ed12070/6a17114fab7f084905db9f97/1ad89c98a2f42a497548ca3947749bad54ec1172-1440x880.png" alt="" /><p>Au cours du processus d <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html">'ingestion</a> avec un <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/inference-processor.html">processeur d'inférence</a>, le paragraphe "abstrait" est mis en correspondance avec un vecteur à 768 dimensions, le "vecteur_abstrait.valeur_prédite".</p><p>Cartographie :</p>"abstract_vector.predicted_value": { # Inference results field
"type": "dense_vector", 
"dims": 768, # model embedding_size
"index": "true", 
"similarity": "dot_product" # When indexing vectors for approximate kNN search, you need to specify the similarity function for comparing the vectors.
<p>La similarité entre les représentations vectorielles est mesurée à l'aide d'une métrique de similarité vectorielle, définie à l'aide du <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html#dense-vector-params">paramètre</a>"similarité".</p><p><a href="https://en.wikipedia.org/wiki/Cosine_similarity">Cosinus</a> est la métrique de similarité par défaut, calculée comme '(1 + cosinus(query, vector)) / 2'. À moins que vous ne deviez préserver les vecteurs originaux et que vous ne puissiez pas les normaliser à l'avance, la façon la plus efficace d'effectuer une similarité en cosinus est de normaliser tous les vecteurs à une longueur unitaire. Cela permet d'éviter d'effectuer des calculs supplémentaires sur la longueur du vecteur pendant la recherche, et d'utiliser plutôt 'dot_product'.</p><p>Dans ce même pipeline, un autre processeur d'inférence contenant le <a href="https://huggingface.co/roberta-base-openai-detector">modèle de classification de texte</a> détecte si le contenu est "vrai", probablement écrit par des humains, ou "faux", probablement écrit par une IA, en ajoutant la valeur "openai-detector.predicted_value" à chaque document.</p><p>Pipeline d'ingestion :</p>client.ingest.put_pipeline( 
    id="plagiarism-checker-pipeline",
    processors = [
    {
      "inference": { #for ml models - to infer against the data that is being ingested in the pipeline
        "model_id": "roberta-base-openai-detector", #text classification model id
        "target_field": "openai-detector", # Target field for the inference results
        "field_map": { #Maps the document field names to the known field names of the model.
        "abstract": "text_field" # Field matching our configured trained model input. 
        }
      }
    },
    {
      "inference": {
        "model_id": "sentence-transformers__all-mpnet-base-v2", #text embedding model id
        "target_field": "abstract_vector", # Target field for the inference results
        "field_map": {
        "abstract": "text_field" # Field matching our configured trained model input. Typically for NLP models, the field name is text_field.
        }
      }
    }
    
  ]
)
<p>Au moment de la requête, le même modèle d'intégration de texte est également utilisé pour générer la représentation vectorielle de la requête "texte_modèle" dans un objet "query_vector_builder".</p><p>Une recherche par k-voisins les plus proches (kNN) permet de trouver les k vecteurs les plus proches du vecteur de la requête, mesurés par la métrique de similarité.</p><p>Le _score de chaque document est dérivé de la similarité, ce qui garantit qu'un score plus élevé correspond à un meilleur classement. Cela signifie que le document est plus similaire d'un point de vue sémantique. Par conséquent, nous imprimons trois possibilités : si le score &gt; est de 0,9, nous considérons qu'il s'agit d'une "forte similarité" ; si &lt; est de 0,7, il s'agit d'une "faible similarité" ; sinon, il s'agit d'une "similarité modérée". Vous avez la possibilité de définir différentes valeurs seuils pour déterminer quel niveau de _score est considéré comme du plagiat ou non, en fonction de votre cas d'utilisation.</p><p>En outre, une classification du texte est effectuée afin de vérifier si la requête contient des éléments générés par l'IA.</p><p>Requête :</p>from elasticsearch import Elasticsearch
from elasticsearch.client import MlClient

#duplicated text - direct plagiarism test

model_text = 'Understanding and reasoning about cooking recipes is a fruitful research direction towards enabling machines to interpret procedural text. In this work, we introduce RecipeQA, a dataset for multimodal comprehension of cooking recipes. It comprises of approximately 20K instructional recipes with multiple modalities such as titles, descriptions and aligned set of images. With over 36K automatically generated question-answer pairs, we design a set of comprehension and reasoning tasks that require joint understanding of images and text, capturing the temporal flow of events and making sense of procedural knowledge. Our preliminary results indicate that RecipeQA will serve as a challenging test bed and an ideal benchmark for evaluating machine comprehension systems. The data and leaderboard are available at http://hucvl.github.io/recipeqa.'

response = client.search(index='plagiarism-checker', size=1,
    knn={
        "field": "abstract_vector.predicted_value",
        "k": 9,
        "num_candidates": 974,
        "query_vector_builder": { #The 'all-mpnet-base-v2' model is also employed to generate the vector representation of the query in a 'query_vector_builder' object.
            "text_embedding": {
                "model_id": "sentence-transformers__all-mpnet-base-v2",
                "model_text": model_text
            }
        }
    }
)

for hit in response['hits']['hits']:
    score = hit['_score']
    title = hit['_source']['title']
    abstract = hit['_source']['abstract']
    openai = hit['_source']['openai-detector']['predicted_value']
    url = hit['_source']['url']

    if score &gt; 0.9:
        print(f"\nHigh similarity detected! This might be plagiarism.")
        print(f"\nMost similar document: '{title}'\n\nAbstract: {abstract}\n\nurl: {url}\n\nScore:{score}\n\n")

        if openai == 'Fake':
            print("This document may have been created by AI.\n")

    elif score &lt; 0.7:
        print(f"\nLow similarity detected. This might not be plagiarism.")

        if openai == 'Fake':
            print("This document may have been created by AI.\n")

    else:
        print(f"\nModerate similarity detected.")
        print(f"\nMost similar document: '{title}'\n\nAbstract: {abstract}\n\nurl: {url}\n\nScore:{score}\n\n")

        if openai == 'Fake':
            print("This document may have been created by AI.\n")

ml_client = MlClient(client)

model_id = 'roberta-base-openai-detector' #open ai text classification model

document = [
    {
        "text_field": model_text
    }
]

ml_response = ml_client.infer_trained_model(model_id=model_id, docs=document)

predicted_value = ml_response['inference_results'][0]['predicted_value']

if predicted_value == 'Fake':
    print("\nNote: The text query you entered may have been generated by AI.\n")
<p>Sortie :</p>High similarity detected! This might be plagiarism.

Most similar document: 'RecipeQA: A Challenge Dataset for Multimodal Comprehension of Cooking Recipes'

Abstract: Understanding and reasoning about cooking recipes is a fruitful research direction towards enabling machines to interpret procedural text. In this work, we introduce RecipeQA, a dataset for multimodal comprehension of cooking recipes. It comprises of approximately 20K instructional recipes with multiple modalities such as titles, descriptions and aligned set of images. With over 36K automatically generated question-answer pairs, we design a set of comprehension and reasoning tasks that require joint understanding of images and text, capturing the temporal flow of events and making sense of procedural knowledge. Our preliminary results indicate that RecipeQA will serve as a challenging test bed and an ideal benchmark for evaluating machine comprehension systems. The data and leaderboard are available at[ http://hucvl.github.io/recipeqa](http://hucvl.github.io/recipeqa).

url:[http://aclweb.org/anthology/D18-1166](http://aclweb.org/anthology/D18-1166)

Score:1.0
<p>Dans cet exemple, après avoir utilisé l'une des valeurs "abstract" de notre ensemble de données comme requête textuelle "model_text", le plagiat a été identifié. Le score de similarité est de 1,0, ce qui indique un niveau élevé de similarité - <strong>un plagiat direct</strong>. La requête et le document vectorisés n'ont pas été reconnus comme des contenus générés par l'IA, ce qui était attendu.</p><p>Requête :</p>#similar text - paraphrase plagiarism test 

model_text = 'Comprehending and deducing information from culinary instructions represents a promising avenue for research aimed at empowering artificial intelligence to decipher step-by-step text. In this study, we present CuisineInquiry, a database for the multifaceted understanding of cooking guidelines. It encompasses a substantial number of informative recipes featuring various elements such as headings, explanations, and a matched assortment of visuals. Utilizing an extensive set of automatically crafted question-answer pairings, we formulate a series of tasks focusing on understanding and logic that necessitate a combined interpretation of visuals and written content. This involves capturing the sequential progression of events and extracting meaning from procedural expertise. Our initial findings suggest that CuisineInquiry is poised to function as a demanding experimental platform.'
<p>Sortie :</p>High similarity detected! This might be plagiarism.

Most similar document: 'RecipeQA: A Challenge Dataset for Multimodal Comprehension of Cooking Recipes'

Abstract: Understanding and reasoning about cooking recipes is a fruitful research direction towards enabling machines to interpret procedural text. In this work, we introduce RecipeQA, a dataset for multimodal comprehension of cooking recipes. It comprises of approximately 20K instructional recipes with multiple modalities such as titles, descriptions and aligned set of images. With over 36K automatically generated question-answer pairs, we design a set of comprehension and reasoning tasks that require joint understanding of images and text, capturing the temporal flow of events and making sense of procedural knowledge. Our preliminary results indicate that RecipeQA will serve as a challenging test bed and an ideal benchmark for evaluating machine comprehension systems. The data and leaderboard are available at[ http://hucvl.github.io/recipeqa](http://hucvl.github.io/recipeqa).

url:[http://aclweb.org/anthology/D18-1166](http://aclweb.org/anthology/D18-1166)

Score:0.9302529

Note: The text query you entered may have been generated by AI.
<p>En mettant à jour la requête "texte_modèle" avec un texte généré par l'IA qui transmet le même message tout en minimisant la répétition de mots similaires, la similarité détectée était toujours élevée, mais le score était de 0,9302529 au lieu de 1,0 - <strong>plagiat de paraphrase</strong>. On s'attendait également à ce que cette requête, générée par l'IA, soit détectée.</p><p>Enfin, si l'on considère la requête textuelle "model_text" comme un texte sur Elasticsearch, qui n'est pas un résumé de l'un de ces documents, la similarité détectée était de 0,68991005, ce qui indique une faible similarité selon les valeurs seuils considérées.</p><p>Requête :</p>#different text - not a plagiarism

model_text = 'Elasticsearch provides near real-time search and analytics for all types of data.'
<p>Sortie :</p>Low similarity detected. This might not be plagiarism.
<p>Bien que le plagiat ait été correctement identifié dans la requête de texte générée par l'IA, ainsi que dans les cas de paraphrase et de contenu directement copié, la navigation dans le paysage de la détection du plagiat implique la reconnaissance de divers aspects.</p><p>Dans le contexte de la détection de contenu généré par l'IA, nous avons exploré un modèle qui apporte une contribution précieuse. Toutefois, il est essentiel de reconnaître les limites inhérentes à la détection autonome, ce qui nécessite l'incorporation d'autres méthodes pour améliorer la précision.</p><p>La variabilité introduite par le choix des modèles d'intégration de texte est un autre élément à prendre en considération. Différents modèles, formés avec des ensembles de données distincts, aboutissent à des niveaux de similarité variables, ce qui souligne l'importance des enchâssements de texte générés.</p><p>Enfin, dans ces exemples, nous avons utilisé le résumé du document. Cependant, la détection du plagiat porte souvent sur des documents volumineux, ce qui rend essentielle la prise en compte de la longueur du texte. Il est fréquent que le texte dépasse la limite de jetons d'un modèle, ce qui nécessite une segmentation en morceaux avant de construire des embeddings. Une <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.11/knn-search.html#nested-knn-search">approche pratique</a> consiste à utiliser des structures imbriquées avec dense_vector.</p><h2>Conclusion</h2><p>Dans ce blog, nous avons abordé les défis liés à la détection du plagiat, en particulier dans les contenus paraphrasés et générés par l'IA, et comment la similarité textuelle sémantique et la classification des textes peuvent être utilisées à cette fin.</p><p>En combinant ces méthodes, nous avons fourni un exemple de détection du plagiat dans lequel nous avons réussi à identifier le contenu généré par l'IA, le plagiat direct et le plagiat paraphrastique.</p><p>L'objectif premier était d'établir un système de filtrage qui simplifie la détection, mais l'évaluation humaine reste essentielle pour la validation.</p><p>Si vous souhaitez en savoir plus sur la similarité textuelle sémantique et le NLP, nous vous encourageons à consulter les liens suivants :</p><ul><li><p><a href="https://www.elastic.co/what-is/semantic-search">Qu'est-ce que la recherche sémantique ?</a></p></li><li><p><a href="https://www.elastic.co/what-is/natural-language-processing">Qu'est-ce que le traitement du langage naturel (NLP) ?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch">Recherche lexicale et sémantique avec Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/chunking-via-ingest-pipelines">Chunking Large Documents via Ingest pipelines plus nested vectors equals easy passage search (en anglais)</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-plagiarism-checker-with-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-plagiarism-checker-with-elasticsearch</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Priscilla Parodi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68a5bc2434a9b03b/6a1711510e2e49a09641a22a/83e05cd4f81799fbb7b7950ed87600e825ec81e9-1024x1024.png" length="0" type="image/png"/>
    <pubDate>Tue, 19 Dec 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Utiliser la recherche hybride pour la chasse aux gophers avec Elasticsearch et Go]]></title>
    <description><![CDATA[Apprenez à réaliser une recherche hybride en combinant la recherche par mot-clé et la recherche vectorielle à l'aide d'Elasticsearch et du client Elasticsearch Go.]]></description>
    <content:encoded><![CDATA[<p>Dans les parties précédentes de cette série, il a été démontré comment utiliser le client Elasticsearch Go pour la <a href="https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client">recherche traditionnelle par mot-clé</a> et la <a href="https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client">recherche vectorielle</a>. Cette troisième partie traite de la recherche hybride. Nous partagerons des <a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">exemples de</a> la façon dont vous pouvez combiner la recherche vectorielle et la recherche par mot-clé en utilisant <a href="https://www.elastic.co/elasticsearch/">Elasticsearch</a> et le <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">client Elasticsearch Go.</a></p><h2>Produits requis</h2><p>Tout comme dans la première partie de cette série, les conditions suivantes sont requises pour cet exemple :</p><ol><li><p>Installation de Go version 1.21 ou ultérieure</p></li><li><p>Créez votre propre répertoire Go en utilisant la structure recommandée et la gestion des paquets décrite dans la <a href="https://go.dev/doc/code">documentation Go.</a></p></li><li><p>Création de votre propre cluster Elasticsearch, alimenté par un ensemble de <a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch#sources">pages sur les rongeurs</a>, y compris notre sympathique <a href="https://en.wikipedia.org/wiki/Gopher">Gopher</a>, tiré de Wikipédia :</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a436c42996e5172/6a1704cf0c48573d1c01a957/34fa81a9b4c292634c719b8303a9b6b7506d7920-1440x662.png" alt="Page Gopher de Wikipedia" /><h2>Connexion à Elasticsearch</h2><p>Pour rappel, dans nos exemples, nous utiliserons l'<a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/typedapi.html">API typée</a> proposée par le client Go. Pour établir une connexion sécurisée pour n'importe quelle requête, il faut configurer le client en utilisant l'une ou l'autre des méthodes suivantes :</p><ol><li><p>ID du nuage et clé API si vous utilisez Elastic Cloud</p></li><li><p>URL du cluster, nom d'utilisateur, mot de passe et certificat</p></li></ol><p>La connexion à notre cluster situé sur Elastic Cloud ressemblerait à ceci :</p>func GetElasticsearchClient() (*elasticsearch.TypedClient, error) {
	var cloudID = os.Getenv("ELASTIC_CLOUD_ID")
	var apiKey = os.Getenv("ELASTIC_API_KEY")

	var es, err = elasticsearch.NewTypedClient(elasticsearch.Config{
		CloudID: cloudID,
		APIKey:  apiKey,
		Logger:  &amp;elastictransport.ColorLogger{os.Stdout, true, true},
	})

	if err != nil {
		return nil, fmt.Errorf("unable to connect: %w", err)
	}

	return es, nil
}
<p>La connexion <code>client</code> peut alors être utilisée pour la recherche, comme le montrent les sections suivantes.</p><h2>Renforcement manuel pour la recherche hybride</h2><p>Lors de la combinaison d'un ensemble d'algorithmes de recherche, l'approche traditionnelle consiste à configurer manuellement des constantes pour stimuler chaque type de requête. Plus précisément, un facteur est spécifié pour chaque requête, et l'ensemble des résultats combinés est comparé à l'ensemble prévu pour déterminer le rappel de la requête. Nous répétons ensuite l'opération pour plusieurs ensembles de facteurs et choisissons celui qui se rapproche le plus de l'état souhaité.</p><p>Par exemple, la combinaison d'une requête de recherche textuelle unique augmentée d'un facteur de <code>0.8</code> avec une requête knn avec un facteur inférieur de <code>0.2</code> peut être réalisée en spécifiant le champ <code>Boost</code> dans les deux types de requêtes, comme le montre l'exemple ci-dessous :</p>func HybridSearchWithBoost(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
	var k = 10
	var numCandidates = 10
	var knnBoost float32 = 0.2
	var queryBoost float32 = 0.8

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnSearch{
			Field:         "text_embedding.predicted_value",
			Boost:         &amp;knnBoost,
			K:             &amp;k,
			NumCandidates: &amp;numCandidates,
			QueryVectorBuilder: &amp;types.QueryVectorBuilder{
				TextEmbedding: &amp;types.TextEmbedding{
					ModelId:   "sentence-transformers__msmarco-minilm-l-12-v3",
					ModelText: term,
				},
			}}).
		Query(&amp;types.Query{
			Match: map[string]types.MatchQuery{
				"title": {
					Query: term,
					Boost: &amp;queryBoost,
				},
			},
		}).
		Do(context.Background())

	if err != nil {
		return nil, err
	}

	return getRodents(res.Hits.Hits)
}
<p>Le facteur spécifié dans l'option <code>Boost</code> pour chaque requête est ajouté au score du document. En augmentant le score de notre requête par un facteur plus important que celui de la requête knn, les résultats de la requête par mot-clé sont plus fortement pondérés.</p><p>La difficulté du renforcement manuel, en particulier si vous n'êtes pas un expert en recherche, réside dans le fait qu'il nécessite une mise au point pour déterminer les facteurs qui conduiront à l'ensemble de résultats souhaité. Il s'agit simplement d'essayer des valeurs aléatoires pour voir ce qui vous rapproche de l'ensemble des résultats souhaités.</p><h2>Fusion de rangs réciproques dans la recherche hybride &amp; Go client</h2><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">Reciprocal Rank Fusion</a>, ou RRF, a été publié en avant-première technique pour la recherche hybride dans Elasticsearch 8.9. Il vise à réduire la courbe d'apprentissage associée à la mise au point et à réduire le temps passé à expérimenter des facteurs pour optimiser l'ensemble des résultats.</p><p>Avec la méthode RRF, le score du document est recalculé en combinant les scores par l'algorithme ci-dessous :</p>score := 0.0
// q is a query in the set of queries (vector and keyword search)
for _, q := range queries {
    // result(q) is the results 
    if document in result(q) {
        // k is a ranking constant (default 60)
        // rank(result(q), d) is the document's rank within result(q) 
        // range from 1 to the window_size (default 100)
        score +=  1.0 / (k + rank(result(q), d))
    }
}

return score
<p>L'avantage de l'utilisation de RRF est que nous pouvons utiliser les valeurs par défaut d'Elasticsearch. La constante de classement <code>k</code> est remplacée par défaut par <code>60</code>. Afin de trouver un compromis entre la pertinence des documents renvoyés et les performances de la requête lors de la recherche sur de grands ensembles de données, la taille de l'ensemble de résultats pour chaque requête considérée est limitée à la valeur de <code>window_size</code>, qui est fixée par défaut à <code>100</code> comme indiqué dans la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html#rrf-api">documentation</a>.</p><p><code>k</code> et <code>windows_size</code> peuvent également être configurés dans la configuration <code>Rrf</code> dans la méthode <code>Rank</code> dans le client Go, comme dans l'exemple ci-dessous :</p>func HybridSearchWithRRF(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
	var k = 10
	var numCandidates = 10

	// Minimum required window size for the default result size of 10
	var windowSize int64 = 10
	var rankConstant int64 = 42

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnSearch{
			Field:         "text_embedding.predicted_value",
			K:             &amp;k,
			NumCandidates: &amp;numCandidates,
			QueryVectorBuilder: &amp;types.QueryVectorBuilder{
				TextEmbedding: &amp;types.TextEmbedding{
					ModelId:   "sentence-transformers__msmarco-minilm-l-12-v3",
					ModelText: term,
				},
			}}).
		Query(&amp;types.Query{
			Match: map[string]types.MatchQuery{
				"title": {Query: term},
			},
		}).
		Rank(&amp;types.RankContainer{
			Rrf: &amp;types.RrfRank{
				WindowSize:   &amp;windowSize,
				RankConstant: &amp;rankConstant,
			},
		}).
		Do(context.Background())

	if err != nil {
		return nil, err
	}

	return getRodents(res.Hits.Hits)
}
<h2>Conclusion</h2><p>Nous avons vu ici comment combiner la recherche vectorielle et la recherche par mot-clé dans Elasticsearch à l'aide du <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">client Elasticsearch Go.</a></p><p>Consultez le <a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">repo GitHub</a> pour tout le code de cette série. Si vous ne l'avez pas encore fait, consultez les <a href="https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client">parties 1</a> et <a href="https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client">2</a> pour connaître tous les codes de cette série.</p><p><em>Bonne chasse aux marmottes !</em></p><h2>Ressources</h2><ol><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html">Guide Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Client Elasticsearch Go</a></p></li><li><p><a href="https://www.elastic.co/what-is/vector-search">Qu'est-ce que la recherche vectorielle ? | Elastique</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">Fusion de rangs réciproques</a></p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hybrid-search-with-the-elasticsearch-go-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hybrid-search-with-the-elasticsearch-go-client</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[Go]]></category>
    <dc:creator><![CDATA[Carly Richmond,Laurent Saint-Félix]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdcae959696d55a9b/6a1704d5ab7f085a56db9d7c/491ef9efbb30b253e1d9e3b7f816a9a23c8f5264-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 02 Nov 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Effectuer une recherche vectorielle dans Elasticsearch avec le client Elasticsearch Go]]></title>
    <description><![CDATA[Apprenez à effectuer une recherche vectorielle dans Elasticsearch en utilisant le client Elasticsearch Go à travers un exemple pratique.]]></description>
    <content:encoded><![CDATA[<p>Construire un logiciel dans n'importe quel langage de programmation, y compris Go, c'est s'engager dans une vie d'apprentissage. Tout au long de sa carrière universitaire et professionnelle, Carly a touché à de nombreux langages et technologies de programmation, y compris les dernières et meilleures implémentations de la recherche vectorielle. Mais ce n'était pas suffisant ! Récemment, Carly a commencé à jouer avec Go.</p><p>Tout comme les animaux, les langages de programmation et votre sympathique auteur, la recherche a connu une évolution des différentes pratiques qu'il peut être difficile de choisir pour votre propre cas d'utilisation de la recherche. Dans ce blog, nous présenterons une vue d'ensemble de la recherche vectorielle ainsi que des <a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">exemples</a> de chaque approche en utilisant <a href="https://www.elastic.co/elasticsearch/">Elasticsearch</a> et le <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">client Elasticsearch Go.</a> Ces exemples vous montreront comment trouver des spermophiles et déterminer ce qu'ils mangent en utilisant la recherche vectorielle dans Elasticsearch et Go.</p><h2>Produits requis</h2><p>Pour suivre cet exemple, assurez-vous que les conditions préalables suivantes sont remplies :</p><ol><li><p>Installation de Go version 1.21 ou ultérieure</p></li><li><p>Création de votre propre repo Go avec l'outil</p></li><li><p>Création de votre propre cluster Elasticsearch, peuplé d'un ensemble de pages sur les rongeurs, y compris pour notre sympathique <a href="https://en.wikipedia.org/wiki/Gopher">Gopher</a>, à partir de Wikipedia :</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a436c42996e5172/6a1704cf0c48573d1c01a957/34fa81a9b4c292634c719b8303a9b6b7506d7920-1440x662.png" alt="Page Gopher de Wikipedia" /><h2>Connexion à Elasticsearch</h2><p>Dans nos exemples, nous utiliserons l'<a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/typedapi.html">API typée</a> proposée par le client Go. Pour établir une connexion sécurisée pour n'importe quelle requête, il faut configurer le client en utilisant l'une ou l'autre des méthodes suivantes :</p><ol><li><p>ID du nuage et clé API si vous utilisez Elastic Cloud.</p></li><li><p>URL du cluster, nom d'utilisateur, mot de passe et certificat.</p></li></ol><p>La connexion à notre cluster situé sur Elastic Cloud ressemblerait à ceci :</p>func GetElasticsearchClient() (*elasticsearch.TypedClient, error) {
	var cloudID = os.Getenv("ELASTIC_CLOUD_ID")
	var apiKey = os.Getenv("ELASTIC_API_KEY")

	var es, err = elasticsearch.NewTypedClient(elasticsearch.Config{
		CloudID: cloudID,
		APIKey:  apiKey,
		Logger:  &amp;elastictransport.ColorLogger{os.Stdout, true, true},
	})

	if err != nil {
		return nil, fmt.Errorf("unable to connect: %w", err)
	}

	return es, nil
}
<p>La connexion <code>client</code> peut ensuite être utilisée pour la recherche vectorielle, comme nous le verrons dans les sections suivantes.</p><h2>Recherche vectorielle</h2><p>La recherche vectorielle tente de résoudre ce problème en convertissant le problème de recherche en une comparaison mathématique utilisant des vecteurs. Le processus d'intégration de documents comporte une étape supplémentaire consistant à convertir le document à l'aide d'un modèle en une représentation vectorielle dense, ou simplement en un flux de nombres. L'avantage de cette approche est qu'elle permet de rechercher des documents non textuels, tels que des images et des fichiers audio, en les traduisant en un vecteur accompagné d'une requête.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc4b3b9b2ad1e8f46/6a1704d06234e0800fdb1927/b113358093e367358d684f7aaf0a6684ebb2d0dd-1440x653.png" alt="Diagramme de recherche vectorielle" /><p>En termes simples, la recherche vectorielle est un ensemble de calculs de distances vectorielles. Dans l'illustration ci-dessous, la représentation vectorielle de notre requête <code>Go Gopher</code>est comparée aux documents de l'espace vectoriel et les résultats les plus proches (désignés par la constante <code>k</code>) sont renvoyés :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c736ecfc49bb038/6a1704d2cf4f25bcb6b2d075/54a17a4b41029644e7c66f33d428e8d01a6f4ce3-1184x743.png" alt="Exemple d'espace vectoriel Gopher" /><p>En fonction de l'approche utilisée pour générer les enchâssements de vos documents, il y a deux façons différentes de savoir ce que mangent les spermophiles.</p><h3>Approche 1 : Apportez votre propre modèle</h3><p>Avec une licence Platinum, il est possible de générer les embeddings dans Elasticsearch en téléchargeant le modèle et en utilisant l'API d'inférence. La mise en place du modèle se fait en six étapes :</p><ol><li><p>Sélectionnez un modèle PyTorch à télécharger à partir d'un référentiel de modèles. Pour cet exemple, nous utilisons les <a href="https://huggingface.co/sentence-transformers/msmarco-MiniLM-L-12-v3">phrase-transformers/msmarco-MiniLM-L-12-v3</a> de Hugging Face pour générer les embeddings.</p></li><li><p>Charger le modèle dans Elastic à l'aide du <a href="https://www.elastic.co/guide/en/elasticsearch/client/eland/current/overview.html">client Eland Machine Learning pour Python</a> en utilisant les informations d'identification de notre cluster Elasticsearch et le type de tâche <code>text_embeddings</code>. Si Eland n'est pas installé, vous pouvez <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-import-model.html#ml-nlp-import-docker">exécuter l'étape d'importation à l'aide de Docker</a>, comme indiqué ci-dessous :</p></li></ol>docker run -it --rm --network host \
    docker.elastic.co/eland/eland \
    eland_import_hub_model \
      --cloud-id $ELASTIC_CLOUD_ID \
      --es-api-key $ELASTIC_API_KEY \
      --hub-model-id sentence-transformers/msmarco-MiniLM-L-12-v3 \
      --task-type text_embedding
<ol><li><p>Une fois téléchargé, testez rapidement le modèle <code>sentence-transformers__msmarco-minilm-l-12-v3</code> avec un exemple de document pour vous assurer que les enchâssements sont générés comme prévu :</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a399e1f50ecfdaf/6a1704d4ab7f08935edb9d78/fbbdde621361a487eb07304bed029c228d0f7aa6-1440x789.png" alt="Exemple de modèle entraîné Elastic Test" /><ol><li><p>Créer un pipeline d'ingestion contenant un processeur d'inférence. Cela permettra de générer la représentation vectorielle à l'aide du modèle téléchargé :</p></li></ol>PUT _ingest/pipeline/search-rodents-vector-embedding-pipeline
{
  "processors": [
    {
      "inference": {
        "model_id": "sentence-transformers__msmarco-minilm-l-12-v3",
        "target_field": "text_embedding",
        "field_map": {
          "body_content": "text_field"
        }
      }
    }
  ]
}
<ol><li><p>Créer un nouvel index contenant le champ <code>text_embedding.predicted_value</code> de type <code>dense_vector</code> pour stocker les encastrements vectoriels générés pour chaque document :</p></li></ol>PUT vector-search-rodents
{
  "mappings": {
    "properties": {
      "text_embedding.predicted_value": {
        "type": "dense_vector",
        "dims": 384,
        "index": true,
        "similarity": "cosine"
      },
      "text": {
        "type": "text"
      }
    }
  }
}
<ol><li><p>Réindexer les documents à l'aide du pipeline d'acquisition nouvellement créé pour générer les enchâssements de texte en tant que champ supplémentaire <code>text_embedding.predicted_value</code> sur chaque document :</p></li></ol>POST _reindex
{
  "source": {
    "index": "search-rodents"
  },
  "dest": {
    "index": "vector-search-rodents",
    "pipeline": "search-rodents-vector-embedding-pipeline"
  }
}
<p>Nous pouvons maintenant utiliser l'option <code>Knn</code> sur la même API de recherche en utilisant le nouvel index <code>vector-search-rodents</code>, comme le montre l'exemple ci-dessous :</p>func VectorSearch(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
  var k = 10
	var numCandidates = 10

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnSearch{
      # Field in document containing vector
			Field:         "text_embedding.predicted_value",
      # Number of neighbors to return
			K:             &amp;k,
      # Number of candidates to evaluate in comparison
			NumCandidates: &amp;numCandidates,
      # Generate query vector using the same model used in the inference processor
			QueryVectorBuilder: &amp;types.QueryVectorBuilder{
				TextEmbedding: &amp;types.TextEmbedding{
					ModelId:   "sentence-transformers__msmarco-minilm-l-12-v3",
					ModelText: term,
				},
			}}).Do(context.Background())

	if err != nil {
		return nil, fmt.Errorf("error in rodents vector search: %w", err)
	}

	return getRodents(res.Hits.Hits)
}
<p>La conversion de l'objet de résultat JSON par unmarshalling se fait exactement de la même manière que dans l'exemple de la recherche par mot-clé. Les constantes <code>K</code> et <code>NumCandidates</code> nous permettent de configurer le nombre de documents voisins à renvoyer et le nombre de candidats à prendre en compte par tesson. Il est à noter que l'augmentation du nombre de candidats accroît la précision des résultats, mais entraîne une requête plus longue, car davantage de comparaisons sont effectuées.</p><p>Lorsque le code est exécuté à l'aide de la requête <code>What do Gophers eat?</code>, les résultats renvoyés sont similaires à ceux présentés ci-dessous, ce qui montre que l'article Gopher contient les informations demandées, contrairement à la recherche par mot-clé précédente :</p>[
  {ID:64f74ecd4acb3df024d91112 Title:Gopher - Wikipedia Url:https://en.wikipedia.org/wiki/Gopher} 
  {ID:64f74ed34acb3d71aed91fcd Title:Squirrel - Wikipedia Url:https://en.wikipedia.org/wiki/Squirrel} 
  //Other results omitted
]
<h3>Approche 2 : Inférence du visage étreint API</h3><p>Une autre option consiste à générer ces mêmes embeddings en dehors d'Elasticsearch et à les intégrer dans votre document. Comme cette option n'utilise pas de nœud d'apprentissage automatique Elasticsearch, elle peut être réalisée sur le niveau gratuit.</p><p>Hugging Face expose une <a href="https://huggingface.co/docs/api-inference/index">API d'inférence</a> gratuite et limitée dans le temps qui, avec un compte et un jeton API, peut être utilisée pour générer manuellement les mêmes enchâssements à des fins d'expérimentation et de prototypage pour vous aider à démarrer. Il n'est pas recommandé pour une utilisation en production. Une approche similaire permet d'invoquer localement vos propres modèles pour générer des embeddings ou d'utiliser l'API payante.</p><p>Dans la fonction <code>GetTextEmbeddingForQuery</code> ci-dessous, nous utilisons l'API d'inférence avec notre chaîne de requête pour générer le vecteur renvoyé par une requête <code>POST</code> au point final :</p>// HuggingFace text embedding helper
func GetTextEmbeddingForQuery(term string) []float32 {
    // HTTP endpoint
    model := "sentence-transformers/msmarco-minilm-l-12-v3"
    posturl := fmt.Sprintf("https://api-inference.huggingface.co/pipeline/feature-extraction/%s", model)

    // JSON body
    body := []byte(fmt.Sprintf(`{
        "inputs": "%s",
        "options": {"wait_for_model":True}
    }`, term))

    // Create a HTTP post request
    r, err := http.NewRequest("POST", posturl, bytes.NewBuffer(body))

    if err != nil {
        log.Fatal(err)
        return nil
    }

    token := os.Getenv("HUGGING_FACE_TOKEN")
    r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))

    client := &amp;http.Client{}
    res, err := client.Do(r)
    if err != nil {
        panic(err)
    }

    defer res.Body.Close()

    var post []float32
    derr := json.NewDecoder(res.Body).Decode(&amp;post)

    if derr != nil {
        log.Fatal(derr)
        return nil
    }

    return post
}
<p>Le vecteur résultant, de type <code>[]float32</code>, est alors transmis en tant que <code>QueryVector</code> au lieu d'utiliser l'option <code>QueryVectorBuilder</code> pour exploiter le modèle précédemment téléchargé dans Elastic.</p>func VectorSearchWithGeneratedQueryVector(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
	vector, err := GetTextEmbeddingForQuery(term)
	if err != nil {
		return nil, err
	}

	if vector == nil {
		return nil, fmt.Errorf("unable to generate vector: %w", err)
	}

  var k = 10
	var numCandidates = 10

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnSearch{
      # Field in document containing vector
			Field:         "text_embedding.predicted_value",
      # Number of neighbors to return
			K:             &amp;k,
      # Number of candidates to evaluate in comparison
			NumCandidates: &amp;numCandidates,
      # Query vector returned from Hugging Face inference API
			QueryVector:   vector,
		}).
		Do(context.Background())

	if err != nil {
		return nil, err
	}

	return getRodents(res.Hits.Hits)
}
<p>Notez que les options <code>K</code> et <code>NumCandidates</code> restent les mêmes quelles que soient les deux options et que les mêmes résultats sont générés car nous utilisons le même modèle pour générer les encastrements.</p><h2>Conclusion</h2><p>Nous avons vu ici comment effectuer une recherche vectorielle dans Elasticsearch en utilisant le <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">client Elasticsearch Go.</a> Consultez le <a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">repo GitHub</a> pour tout le code de cette série. Suivez la <a href="https://www.elastic.co/search-labs/blog/hybrid-search-with-the-elasticsearch-go-client">partie 3</a> pour avoir une vue d'ensemble de la combinaison de la recherche vectorielle avec les capacités de recherche par mot-clé abordées dans la <a href="https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client">partie 1</a> de Go.</p><p>D'ici là, bonne chasse aux marmottes !</p><h2>Ressources</h2><ol><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html">Guide Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Client Elasticsearch Go</a></p></li><li><p><a href="https://www.elastic.co/what-is/vector-search">Qu'est-ce que la recherche vectorielle ? | Elastique</a></p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[Go]]></category>
    <dc:creator><![CDATA[Carly Richmond,Laurent Saint-Félix]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdcae959696d55a9b/6a1704d5ab7f085a56db9d7c/491ef9efbb30b253e1d9e3b7f816a9a23c8f5264-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 01 Nov 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Recherche lexicale et sémantique avec Elasticsearch]]></title>
    <description><![CDATA[Dans ce blog, nous allons explorer différentes approches de la recherche d'informations à l'aide d'Elasticsearch, en nous concentrant sur la recherche lexicale et sémantique.]]></description>
    <content:encoded><![CDATA[<p>La recherche est le processus de localisation des informations les plus pertinentes sur la base de votre requête de recherche ou de requêtes combinées, et les résultats de recherche pertinents sont les documents qui correspondent le mieux à ces requêtes. Bien qu'il existe plusieurs défis et méthodes associés à la recherche, l'objectif final reste le même : <strong>trouver la meilleure réponse possible à votre question</strong>.</p><p>Compte tenu de cet objectif, dans cet article de blog, nous allons explorer différentes approches de la recherche d'informations à l'aide d'Elasticsearch, en nous concentrant plus particulièrement sur la recherche de texte : <strong>recherche lexicale et sémantique</strong>.</p><h2>Produits requis</h2><p>Pour ce faire, nous fournirons des exemples Python qui démontrent divers scénarios de recherche sur un <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/products-ecommerce.json">ensemble de données</a> généré pour simuler des informations sur des produits de commerce électronique.</p><p>Cet ensemble de données contient plus de 2 500 produits, chacun accompagné d'une description. Ces produits sont classés en 76 catégories de produits distinctes, chaque catégorie contenant un nombre variable de produits, comme indiqué ci-dessous :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt34415151c00ced2b/6a17d8710b0bed6c9bdd342c/4104466050f3024b6bcaf382da2a702650f62227-1440x708.png" alt="" /><p><em>Visualisation du treemap - les 22 premières valeurs de category.keyword (catégories de produits)</em></p><p>Pour l'installation, vous aurez besoin de</p><ul><li><p>Python 3.6 ou version ultérieure</p></li><li><p>Le <a href="https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/index.html">client Elastic Python</a></p></li><li><p>Déploiement d'Elastic 8.8 ou d'une version ultérieure, avec un nœud d'apprentissage automatique de 8 Go de mémoire</p></li><li><p>Le modèle <a href="https://www.elastic.co/guide/en/machine-learning/8.9/ml-nlp-elser.html">Elastic Learned Sparse EncodeR</a> qui est préchargé dans Elastic a été installé et démarré sur votre déploiement.</p></li></ul><p>Nous utiliserons Elastic Cloud, une <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">version d'essai gratuite est disponible</a>.</p><p>Outre les requêtes de recherche présentées dans cet article de blog, un <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">carnet Python</a> vous guidera dans les processus suivants :</p><ul><li><p>Établir une connexion à notre déploiement Elastic en utilisant le client Python</p></li><li><p>Charger un modèle d'intégration de texte dans le cluster Elasticsearch</p></li><li><p>Créer un index avec des correspondances pour indexer les vecteurs de caractéristiques et les vecteurs denses.</p></li><li><p>Créer un pipeline d'ingestion avec des processeurs d'inférence pour l'intégration et l'expansion du texte</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0e95406ece8f08d9/6a17d8731d1b83d32e93e2f4/54a9a490a3b0cf1b9c2228bee8eddd3f566bd435-1418x1102.png" alt="" /><h2>Recherche lexicale - recherche éparse</h2><p>La manière classique dont les documents sont classés en fonction de leur pertinence par Elasticsearch sur la base d'une requête textuelle utilise l'implémentation Lucene du modèle <a href="https://en.wikipedia.org/wiki/Okapi_BM25">BM25</a>, un <strong>modèle clairsemé pour la recherche lexicale</strong>. Cette méthode suit l'approche traditionnelle de la recherche de texte, en recherchant des correspondances de termes exacts.</p><p>Pour rendre cette recherche possible, Elasticsearch convertit les données des <strong>champs de texte</strong> en un format de recherche en effectuant une analyse de texte.</p><p>L'<strong>analyse de texte</strong> est effectuée par un<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer-anatomy.html"> analyseur</a>, un ensemble de règles régissant le processus d'extraction des mots-clés pertinents pour la recherche. Un analyseur doit avoir exactement un<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html"> tokenizer</a>. Le tokenizer reçoit un flux de caractères et le décompose en tokens individuels (généralement des mots individuels), comme dans l'exemple ci-dessous :</p><h3>Balisage des chaînes de caractères pour la recherche lexicale</h3>#Performs text analysis on a string and returns the resulting tokens.

# Define the text to be analyzed
text = "Comfortable furniture for a large balcony"

# Define the analyze request
request_body = {
  "analyzer": "standard",
  "text": text
}

# Perform the analyze request
response = client.indices.analyze(analyzer=request_body["analyzer"], text=request_body["text"])

# Extract and display the analyzed tokens
tokens = [token["token"] for token in response["tokens"]]
print("Analyzed Tokens:", tokens)
<p>Sortie</p>Analyzed Tokens: ['comfortable', 'furniture', 'for', 'a', 'large', 'balcony']
<p>Dans cet exemple, nous utilisons l'analyseur par défaut, l'analyseur <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-standard-analyzer.html">standard</a>, qui fonctionne bien pour la plupart des cas d'utilisation car il fournit une tokenisation basée sur la grammaire anglaise. La tokenisation permet de faire correspondre des termes individuels, mais chaque token est toujours mis en correspondance de manière littérale.</p><p>Si vous souhaitez personnaliser votre expérience de recherche, vous pouvez choisir un autre<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html"> analyseur intégré</a>. Par exemple, en mettant à jour le code pour utiliser l'<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-stop-analyzer.html">analyseur de mots v</a> ides, il décomposera le texte en jetons à partir de tout caractère autre qu'une lettre, avec la possibilité de supprimer les mots vides.</p>...
# Define the analyze request
request_body = {
  "analyzer": "stop",
  "text": text
}
...
<p>Sortie</p>Analyzed Tokens: ['comfortable', 'furniture', 'large', 'balcony']
<p>Lorsque les analyseurs intégrés ne répondent pas à vos besoins, vous pouvez créer un <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-custom-analyzer.html">analyseur personnalisé</a>, qui utilise la combinaison appropriée de zéro ou plusieurs <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-charfilters.html">filtres de caractères</a>, un <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html">tokenizer</a> et zéro ou plusieurs <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenfilters.html">filtres de jetons</a>.</p>"analyzer":  {

  "my_analyzer": {

    "type": "custom", #For custom analyzers, use a type of custom or omit the type parameter.

    "tokenizer": "standard", #Built-in or customized tokenizer

    "filter": ["lowercase", "synonym"] #Built-in or customized token filters
  }
}
<p>Dans l'exemple ci-dessus, qui combine un tokenizer et des filtres de jetons, le texte sera mis en minuscules par le <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lowercase-tokenfilter.html">filtre de minuscules</a> avant d'être traité par le <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-synonym-tokenfilter.html#:~:text=Elasticsearch%20will%20use%20the%20token,applied%20to%20the%20synonym%20entries.">filtre de jetons de synonymes</a>.</p><h2>Correspondance lexicale</h2><p><a href="https://www.elastic.co/blog/practical-bm25-part-2-the-bm25-algorithm-and-its-variables">BM25</a> mesurera la pertinence des documents par rapport à une requête de recherche donnée sur la base de la fréquence des termes et de leur importance.</p><p>Le code ci-dessous effectue une recherche de <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html">correspondance</a>, recherchant jusqu'à deux documents en tenant compte des valeurs du champ <em>"description"</em> de l'index <em>"ecommerce-search"</em> et de la requête de recherche. <strong>"</strong><em><strong>Meubles confortables pour un grand balcon</strong></em><strong>"</strong>.</p><p>L'affinement des critères pour qu'un document soit considéré comme correspondant à cette requête peut améliorer la précision. Cependant, les résultats plus spécifiques sont assortis d'une plus faible tolérance aux variations.</p># BM25

response = client.search(size=2,
index="ecommerce-search",
query= {
  "match": {
    "description" : {  
      "query": "Comfortable furniture for a large balcony",
      "analyzer": "stop"
    }
  }
}
)

hits = response['hits']['hits']

if not hits:
  print("No matches found")

else:
  for hit in hits:
    score = hit['_score']
    product = hit['_source']['product']
    category = hit['_source']['category']
    description = hit['_source']['description']
    print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>Sortie</p>Score: 15.607948
Product: Barbie Dreamhouse
Category: Toys
Description: is a classic Barbie playset with multiple rooms, furniture, a large balcony, a pool, and accessories. It allows kids to create their dream Barbie world.

Score: 9.137739
Product: Comfortable Rocking Chair
Category: Indoor Furniture
Description: enjoy relaxing moments with this comfortable rocking chair. Its smooth motion and cushioned seat make it an ideal piece of furniture for unwinding.
<p>En analysant le résultat, le résultat le plus pertinent est le produit "<em>Barbie Dreamhouse</em>", dans la catégorie "<em>Toys</em>", et sa description est très pertinente car elle inclut les termes "<em>furniture</em>", "<em>large"</em> et <em>"balcony</em>", c'est le seul produit avec 3 termes dans la description qui correspondent à la requête de recherche, le produit est également le seul avec le terme <em>"balcony"</em> dans la description.</p><p>Le deuxième produit le plus pertinent est un "<em>Comfortable Rocking Chair</em>" catégorisé en "<em>Indoor Furniture</em>" et sa description inclut les termes "<em>comfortable</em>" et "<em>furniture</em>". Seuls 3 produits dans l'ensemble de données correspondent à au moins 2 termes de cette requête de recherche, et ce produit est l'un d'entre eux.</p><p><em>"Comfortable"</em> apparaît dans la description de 105 produits et <em>"furniture"</em> dans la description de 4 produits avec 4 catégories différentes : <em>Jouets</em>, <em>Mobilier d'intérieur, Mobilier d'extérieur et 'Dog and Cat Supplies &amp; Toys'.</em></p><p>Comme vous pouvez le constater, le produit le plus pertinent par rapport à la requête est un jouet et le deuxième produit le plus pertinent est un meuble d'intérieur. Si vous souhaitez obtenir des informations détaillées sur le calcul du score afin de savoir pourquoi ces documents correspondent, vous pouvez attribuer la valeur true au paramètre <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html"><em>explain</em></a> __query.</p><p>Bien que les deux résultats soient les plus pertinents, compte tenu à la fois du nombre de documents et de l'occurrence des termes dans cet ensemble de données, l'intention derrière la requête "<em>Meubles confortables pour un grand</em>balcon" est de rechercher des meubles pour un grand balcon réel, à l'exclusion, entre autres, des jouets et des meubles d'intérieur.</p><p>La recherche lexicale est relativement <strong>simple et rapide</strong>, mais elle présente des limites car il n'est pas toujours possible de connaître tous les termes et synonymes possibles sans nécessairement connaître l'intention et les requêtes de l'utilisateur. Un phénomène courant dans l'utilisation du langage naturel est l'<strong>inadéquation du vocabulaire</strong>. <a href="https://dl.acm.org/doi/abs/10.1145/32206.32212">% L es recherches</a> montrent qu'en moyenne, dans<strong>80 % des cas,</strong> des personnes différentes (experts dans le même domaine) nomment la même chose différemment.</p><p>Ces limites nous incitent à rechercher d'autres modèles de notation qui intègrent des connaissances sémantiques. Les modèles basés sur des transformateurs, qui excellent dans le traitement de jetons d'entrée séquentiels tels que le langage naturel, capturent le sens sous-jacent de votre recherche en prenant en compte les représentations mathématiques des documents et des requêtes. Cela permet une représentation vectorielle dense et contextuelle du texte, qui alimente la <strong>recherche sémantique</strong>, un moyen raffiné de trouver du contenu pertinent.</p><h2>Recherche sémantique - recherche dense</h2><p>Dans ce contexte, après avoir converti vos données en valeurs vectorielles significatives, l'algorithme de recherche <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">k-nearest neighbor (kNN</a> ) est utilisé pour trouver les représentations vectorielles dans un ensemble de données qui sont les plus similaires à un vecteur d'interrogation. Elasticsearch prend en charge deux méthodes de recherche kNN, le <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#exact-knn">kNN exact par force brute</a> et le <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#approximate-knn">kNN approximatif</a>, également connu sous le nom d'ANN.</p><p>Le kNN brutal garantit des résultats précis, mais ne s'adapte pas bien aux grands ensembles de données. Le kNN approximatif trouve efficacement les voisins les plus proches approximatifs en sacrifiant une partie de la précision pour améliorer les performances.</p><p>Grâce à la prise en charge par Lucene de la recherche kNN et des index vectoriels denses, Elasticsearch tire parti de l'algorithme Hierarchical Navigable Small World (HNSW), qui démontre d'excellentes performances de recherche sur une variété d'<a href="http://ann-benchmarks.com/">ensembles de données de référence.</a> Une recherche kNN approximative peut être effectuée en Python à l'aide du code d'exemple ci-dessous.</p><h3>Recherche sémantique avec kNN approximatif</h3># KNN - approximate kNN

response = client.search(index='ecommerce-search', size=2,
knn={
  "field": "description_vector.predicted_value",
  "k": 50, # Number of nearest neighbors to return as top hits.
#The optimal value of k is dependent on the data. It can vary in different scenarios.

  "num_candidates": 500, # Number of nearest neighbor candidates to consider per shard.

#Increasing num_candidates tends to improve the accuracy of the final k results.

  "query_vector_builder": { # Object indicating how to build a query_vector. kNN search enables you to perform semantic search by using a previously deployed text embedding model, the steps for this process are demonstrated in the Python notebook.
    "text_embedding": { 
      "model_id": "sentence-transformers__all-mpnet-base-v2", # Text embedding model id
      "model_text": "Comfortable furniture for a large balcony" # Query
    }
  }
}
)

for hit in response['hits']['hits']:
        
  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>Ce bloc de code utilise le kNN d'Elasticsearch pour renvoyer jusqu'à deux produits dont la description est similaire à la requête vectorisée (query_vector_build) de "<em>Meubles confortables pour un grand balcon</em>" en tenant compte de l'intégration du champ "<em>description</em>" dans l'ensemble de données sur les produits.</p><p>Les encastrements de produits ont été préalablement générés dans un pipeline d'ingestion avec un processeur d'inférence contenant le logiciel <em>"</em><a href="https://huggingface.co/sentence-transformers/all-mpnet-base-v2"><em>all-mpnet-base-v2</em></a><em>"</em> pour déduire les données qui étaient ingérées dans le pipeline.</p><p>Ce modèle a été choisi sur la base de l'évaluation des modèles pré-entraînés à l'aide de <em>"</em><a href="https://github.com/UKPLab/sentence-transformers/blob/master/docs/package_reference/sentence_transformer/evaluation.md"><em>sentence_transformers.evaluation</em></a><em>"</em> où différentes classes sont utilisées pour évaluer un modèle pendant la formation. Le modèle "all-mpnet-base-v2" a démontré la meilleure performance moyenne selon le <a href="https://www.sbert.net/docs/pretrained_models.html">classement Sentence-Transformers</a> et a également obtenu une position favorable sur le <a href="https://huggingface.co/spaces/mteb/leaderboard">Massive Text Embedding Benchmark (MTEB)</a> Leaderboard. Le modèle pré-entraîné<a href="https://huggingface.co/microsoft/mpnet-base"> microsoft/mpnet-base</a> et affiné sur un ensemble de données de 1B paires de phrases, il cartographie les phrases dans un espace vectoriel dense de 768 dimensions.</p><p>Par ailleurs, il existe de nombreux autres modèles qui peuvent être utilisés, en particulier ceux qui sont adaptés aux données spécifiques à votre domaine.</p><p>Sortie</p>Score: 0.79207325
Product: Patio Sofa Set with Ottoman
Category: Outdoor Furniture
Description: is a versatile and comfortable patio sofa set, including a sofa, ottoman, and coffee table, great for outdoor lounging.

Score: 0.7836937
Product: Patio Sofa Set with Canopy
Category: Outdoor Furniture
Description: is a luxurious and comfortable patio sofa set with a canopy, providing shade and style for outdoor lounging.
<p><em>Le résultat peut varier en fonction du modèle choisi, des</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search-filter-example"><em>filtres</em></a> <em>et de la</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#tune-approximate-knn-for-speed-accuracy"><em>syntonisation approximative de kNN</em></a><em>.</em></p><p>Les résultats de la recherche kNN se trouvent tous deux dans la catégorie "<em>Outdoor Furniture</em>", même si le mot "<em>outdoor</em>" n'a pas été explicitement mentionné dans la requête, ce qui souligne l'importance de la compréhension sémantique dans le contexte.</p><p>La recherche vectorielle dense offre plusieurs avantages :</p><ul><li><p>Permettre la recherche sémantique</p></li><li><p>Évolutivité pour traiter de très grands ensembles de données</p></li><li><p>Flexibilité pour traiter un large éventail de types de données</p></li></ul><p>Cependant, la <strong>recherche vectorielle dense présente également ses propres difficultés :</strong></p><ul><li><p>Choisir le bon modèle d'intégration pour votre cas d'utilisation</p></li><li><p>Une fois le modèle choisi, il peut être nécessaire de l'affiner pour optimiser les performances sur un ensemble de données spécifiques au domaine, un processus qui nécessite la participation d'experts du domaine.</p></li><li><p>En outre, l'indexation de vecteurs à haute dimension peut être très coûteuse sur le plan informatique</p></li></ul><h2>Recherche sémantique - récupération de données éparses apprises</h2><p>Explorons une autre approche : la récupération éparse apprise, une autre façon d'effectuer une recherche sémantique.</p><p>En tant que modèle clairsemé, il utilise l'index inversé basé sur Lucene d'Elasticsearch, qui bénéficie de décennies d'optimisations. Toutefois, cette approche va au-delà du simple ajout de synonymes avec des fonctions de notation lexicale telles que BM25. Au lieu de cela, il incorpore des associations apprises en utilisant une connaissance plus approfondie de la langue pour optimiser la pertinence.</p><p>En élargissant les requêtes de recherche pour inclure des termes pertinents qui ne sont pas présents dans la requête originale, l' encodeur de vecteurs <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">épar s Elastic Learned Sparse Encoder</a> <strong>améliore les encastrements de vecteurs</strong> épars, comme vous pouvez le voir dans l'exemple ci-dessous.</p><h3>Recherche de vecteurs épars à l'aide d'un codeur épars à apprentissage élastique</h3># Elastic Learned Sparse Encoder

response = client.search(index='ecommerce-search', size=2,
query={
  "text_expansion": {
    "ml.tokens": {
      "model_id":"elser_model",
      "model_text":"Comfortable furniture for a large balcony"                
    }
  }
}
)

for hit in response['hits']['hits']:

  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>Sortie</p>Score: 14.405318
Product: Garden Lounge Set with Side Table
Category: Garden Furniture
Description: is a comfortable and stylish garden lounge set, including a sofa, chairs, and a side table for outdoor relaxation.

Score: 14.281318
Product: Rattan Patio Conversation Set
Category: Outdoor Furniture
Description: is a stylish and comfortable outdoor furniture set, including a sofa, two chairs, and a coffee table, all made of durable rattan material.
<p>Les résultats dans ce cas incluent la catégorie "<em>Garden Furniture</em>", qui offre des produits assez similaires à "<em>Outdoor Furniture</em>".</p><p>En analysant "ml.tokens", le champ "rank_features" contenant les tokens générés par Learned Sparse Retrieval, il devient évident que parmi les différents tokens générés, il y a des termes qui, bien que ne faisant pas partie de la requête de recherche, sont toujours pertinents dans leur signification, tels que "<em>relax</em>" (confortable), "<em>sofa</em>" (meuble) et "<em>outdoor</em>" (balcon).</p><p>L'image ci-dessous présente certains de ces termes en regard de la requête, avec et sans expansion des termes.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e869985347b19a7/6a17d875e31791dc2c2d56a1/dd86607fce6137d843a3ec002390eaa988b432f9-1440x502.png" alt="" /><p>Comme nous l'avons observé, ce modèle permet une recherche contextuelle et contribue à atténuer le problème d'inadéquation du vocabulaire tout en fournissant des résultats plus faciles à interpréter. Il peut même être plus performant que les modèles à vecteurs denses lorsqu'aucun recyclage spécifique au domaine n'est appliqué.</p><h2>Recherche hybride : résultats pertinents grâce à la combinaison de la recherche lexicale et sémantique</h2><p>En matière de recherche, il n'existe pas de solution universelle. Chacune de ces méthodes de recherche a ses avantages, mais aussi ses inconvénients. En fonction du cas d'utilisation, la meilleure option peut changer. Souvent, les meilleurs résultats obtenus par les différentes méthodes de recherche peuvent être complémentaires. C'est pourquoi, pour améliorer la pertinence, nous chercherons à combiner les points forts de chaque méthode.</p><p>Il existe plusieurs façons de mettre en œuvre une <strong>recherche hybride</strong>, notamment la combinaison linéaire, en attribuant un poids à chaque score, et la fusion des rangs réciproques (RRF), où il n'est pas nécessaire de spécifier un poids.</p><h3>Elasticsearch : le meilleur des deux mondes avec la recherche lexicale et sémantique</h3># BM25 + Elastic Learned Sparse Encoder (Linear Combination)

response = client.search(index='ecommerce-search', size=2,

query= {
  "bool": {
    "should": [
    {
      "match": {
        "description" : {  
          "query": "A dining table and comfortable chairs for a large balcony",
          "boost": 1
        }
      }
    },                   
    {
      "text_expansion": {
        "ml.tokens": {
          "model_id": "elser_model",
          "model_text": "A dining table and comfortable chairs for a large balcony",
          "boost": 1
        }
      }
     }
    ]
  }
}
)

# The boost value is 1 for the text expansion and match query. This means that the relevance score of the results of these queries are not boosted. You can specify a boost value to give a weight to each score in the sum. The scores will be calculated as: score = boost value * match_score + boost value * text_expansion_score

for hit in response['hits']['hits']:

  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>Dans ce code, nous avons effectué une recherche hybride avec deux requêtes ayant la valeur " Une<em>table à manger et des chaises confortables pour un grand balcon</em>". Au lieu d'utiliser "<em>furniture</em>" comme terme de recherche, nous spécifions ce que nous recherchons, et les deux recherches prennent en compte les mêmes valeurs de champ, "description". Le classement est déterminé par une combinaison linéaire avec un poids égal pour les notes BM25 et ELSER.</p><p>Sortie</p>Score: 31.628141
Product: Garden Dining Set with Swivel Rockers
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel rockers for easy movement.

Score: 31.334227
Product: Garden Dining Set with Swivel Chairs
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel seats for convenience.
<p>Dans le code ci-dessous, nous utiliserons la même valeur pour la requête, mais nous combinerons les scores de BM25 (paramètre de requête) et de kNN (paramètre knn) en utilisant la méthode de fusion par rang réciproque pour combiner et classer les documents.</p># BM25 + KNN (RRF)

response = client.search(index='ecommerce-search', size=2,
query={
  "bool": {
    "should": [
    {
      "match": {
        "description": {
        "query": "A dining table and comfortable chairs for a large balcony"
        }
      }
    }
    ]
  }
},
knn={
  "field": "description_vector.predicted_value",
  "k": 50,
  "num_candidates": 500,
  "query_vector_builder": {
    "text_embedding": {
      "model_id": "sentence-transformers__all-mpnet-base-v2",
      "model_text": "A dining table and comfortable chairs for a large balcony"
    }
  }
},
rank={
  "rrf": { # Reciprocal rank fusion
    "window_size": 50, # This value determines the size of the individual result sets per query.
    "rank_constant": 20 # This value determines how much influence documents in individual result sets per query have over the final ranked result set.
  }
}
)

for hit in response['hits']['hits']:
        
  rank = hit['_rank']
  category = hit['_source']['category']
  product = hit['_source']['product']
  description = hit['_source']['description']
  print(f"\nRank: {rank}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p><em>La fonctionnalité RRF est en avant-première technique. La syntaxe sera probablement modifiée avant l'AG.</em></p><p>Sortie</p>Rank: 1
Product: Patio Dining Set with Bench
Category: Outdoor Furniture
Description: is a spacious and functional patio dining set, including a dining table, chairs, and a bench for additional seating.

Rank: 2
Product: Garden Dining Set with Swivel Chairs
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel seats for convenience.
<p>Ici, nous pourrions également utiliser différents champs et valeurs ; certains de ces exemples sont disponibles dans le <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">Python notebook</a>.</p><p>Comme vous pouvez le constater, Elasticsearch vous offre le meilleur des deux mondes : la recherche lexicale traditionnelle et la recherche vectorielle, qu'elle soit éparse ou dense, pour atteindre votre objectif <strong>et trouver la meilleure réponse possible à votre question.</strong></p><p>Si vous souhaitez continuer à vous familiariser avec les approches mentionnées ici, ces blogs peuvent vous être utiles :</p><ul><li><p><a href="https://www.elastic.co/blog/improving-information-retrieval-elastic-stack-hybrid">Améliorer la recherche d'informations dans la pile élastique : Recherche hybride</a></p></li><li><p><a href="https://www.elastic.co/blog/vector-search-elasticsearch-rationale">Recherche vectorielle dans Elasticsearch : La raison d'être de la conception</a></p></li><li><p><a href="https://www.elastic.co/blog/lexical-ai-powered-search-elastic-vector-database">Comment obtenir le meilleur de la recherche lexicale et de la recherche alimentée par l'IA avec la base de données vectorielle d'Elastic ?</a></p></li><li><p><a href="https://www.elastic.co/blog/may-2023-launch-sparse-encoder-ai-model">Présentation d'Elastic Learned Sparse Encoder : Le modèle d'IA d'Elastic pour la recherche sémantique</a></p></li><li><p><a href="https://www.elastic.co/blog/may-2023-launch-information-retrieval-elasticsearch-ai-model">Améliorer la recherche d'information dans la pile Elastic : Présentation d'Elastic Learned Sparse Encoder, notre nouveau modèle de recherche d'information</a></p></li></ul><p>Elasticsearch fournit une base de données vectorielle, ainsi que tous les outils dont vous avez besoin pour construire une recherche vectorielle :</p><ul><li><p><a href="https://www.elastic.co/elasticsearch/vector-database">Base de données vectorielle</a>Elasticsearch</p></li><li><p>Cas d'utilisation de la <a href="https://www.elastic.co/enterprise-search/vector-search">recherche vectorielle</a> avec Elastic</p></li></ul><h2>Conclusion</h2><p>Dans ce billet de blog, nous avons exploré différentes approches de la recherche d'informations à l'aide d'Elasticsearch, en nous concentrant plus particulièrement sur la recherche textuelle, lexicale et sémantique. Pour le démontrer, nous avons fourni des exemples Python illustrant différents scénarios de recherche à l'aide d'un ensemble de données contenant des informations sur des produits de commerce électronique.</p><p>Nous avons passé en revue la recherche lexicale classique avec BM25 et discuté de ses avantages et de ses défis, tels que l'inadéquation du vocabulaire. Nous avons souligné l'importance d'intégrer des connaissances sémantiques pour résoudre ce problème. En outre, nous avons abordé la recherche vectorielle dense, qui permet une recherche sémantique, et les défis associés à cette méthode de recherche, notamment le coût de calcul lors de l'indexation de vecteurs à haute dimension.</p><p>D'autre part, nous avons mentionné que les vecteurs épars se compriment exceptionnellement bien. Ainsi, nous avons discuté de l'encodeur Learned Sparse Encoder d'Elastic, qui étend les requêtes de recherche pour inclure des termes pertinents qui ne sont pas présents dans la requête originale.</p><p>Il n'existe pas de solution unique en matière de recherche. Chaque méthode de recherche a ses avantages et ses inconvénients. C'est pourquoi nous avons également abordé le concept de recherche hybride.</p><p>Comme vous pouvez le constater, avec Elasticsearch, vous pouvez bénéficier du meilleur des deux mondes : la recherche lexicale traditionnelle et la recherche vectorielle !</p><p>Prêt à commencer ? Consultez le <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">cahier Python</a> disponible et commencez un <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">essai gratuit d'Elastic Cloud.</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <category><![CDATA[Python]]></category>
    <category><![CDATA[Langages de requête]]></category>
    <dc:creator><![CDATA[Priscilla Parodi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbd7e961f594005e7/6a17d80f033c8d981f6bb009/d240bfef29e9d432069059b312dd044eb76eec6c-1440x840.png" length="0" type="image/png"/>
    <pubDate>Tue, 03 Oct 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Améliorer les capacités des chatbots grâce au NLP et à la recherche vectorielle dans Elasticsearch]]></title>
    <description><![CDATA[Découvrez comment la recherche vectorielle et le NLP permettent d'améliorer les capacités des chatbots et comment Elasticsearch facilite le processus.]]></description>
    <content:encoded><![CDATA[<p>Les interfaces conversationnelles existent depuis un certain temps et deviennent de plus en plus populaires comme moyen d'assistance pour diverses tâches, telles que le service à la clientèle, la recherche d'informations et l'automatisation des tâches. Généralement accessibles par le biais d'assistants vocaux ou d'applications de messagerie, ces interfaces simulent une conversation humaine afin d'aider les utilisateurs à résoudre leurs questions plus efficacement.</p><p>À mesure que la technologie progresse, les chatbots sont utilisés pour gérer des tâches plus complexes - et rapidement - tout en offrant une expérience personnalisée aux utilisateurs. Le traitement du langage naturel (NLP) permet aux chatbots de traiter le langage de l'utilisateur, d'identifier l'intention derrière son message et d'en extraire des informations pertinentes. Par exemple, la reconnaissance des entités nommées extrait les informations clés d'un texte en les classant dans un ensemble de catégories. L'analyse des sentiments identifie le ton émotionnel, et la réponse aux questions la "réponse" à une requête. L'objectif du NLP est de permettre aux algorithmes de traiter le langage humain et d'effectuer des tâches dont, historiquement, seul l'homme était capable, telles que la recherche de passages pertinents dans de grandes quantités de texte, le résumé de texte et la génération de nouveaux contenus originaux.</p><p>Ces capacités avancées de NLP reposent sur une technologie connue sous le nom de <a href="https://www.elastic.co/what-is/vector-search">recherche vectorielle</a>. Elastic prend en charge de manière native la recherche vectorielle, en effectuant des recherches exactes et approximatives par <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search">k-nearest neighbor (kNN),</a> ainsi que le NLP, ce qui permet d'utiliser des <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-model-ref.html#ml-nlp-model-ref">modèles personnalisés</a> ou tiers directement dans Elasticsearch.</p><p>Dans ce billet de blog, nous allons explorer comment la recherche vectorielle et le NLP fonctionnent pour améliorer les capacités des chatbots et montrer comment Elasticsearch facilite le processus. Commençons par un bref aperçu de la recherche vectorielle.</p><h2>Recherche vectorielle</h2><p>Bien que les humains puissent comprendre le sens et le contexte du langage écrit, les machines ne peuvent pas en faire autant. C'est là qu'interviennent les vecteurs. En convertissant le texte en représentations vectorielles (représentations numériques du sens du texte), les machines peuvent surmonter cette limitation. Par rapport à une recherche traditionnelle, au lieu de s'appuyer sur des mots-clés et une recherche lexicale basée sur les fréquences, les vecteurs permettent de traiter des données textuelles à l'aide d'opérations définies pour des valeurs numériques.</p><p>Cela permet à la recherche vectorielle de localiser des données qui partagent des concepts ou des contextes similaires en utilisant les distances dans l'espace d'intégration "" pour représenter la similarité à partir d'un vecteur de requête. Lorsque les données sont similaires, les vecteurs correspondants sont similaires.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt53a615a8ba0ac931/6a17d795fbc5f8b257491910/08542abf8108aace288745b1aca8579b476ddc1b-1440x618.png" alt="" /><p>La recherche vectorielle n'est pas seulement utilisée dans les applications NLP, mais aussi dans divers autres domaines où des données non structurées sont impliquées, y compris le traitement des images et des vidéos.</p><p>Dans un flux de chatbot, il peut y avoir plusieurs approches des requêtes des utilisateurs, et par conséquent, il y a différentes façons d'améliorer la recherche d'informations pour une meilleure expérience de l'utilisateur. Étant donné que chaque solution présente ses propres avantages et inconvénients, il est essentiel de tenir compte des données et des ressources disponibles, ainsi que du temps de formation (le cas échéant) et de la précision attendue. Dans la section suivante, nous aborderons ces aspects pour les modèles NLP de réponse aux questions.</p><h2>Questions-réponses</h2><p>Un modèle de réponse aux questions (QA) est un type de modèle NLP conçu pour répondre aux questions posées en langage naturel. Lorsque les utilisateurs ont des questions qui nécessitent de déduire des réponses à partir de plusieurs ressources, sans qu'une réponse cible préexistante soit disponible dans les documents, les modèles génératifs d'assurance qualité peuvent être utiles. Toutefois, ces modèles peuvent être coûteux en termes de calcul et nécessiter de grandes quantités de données pour la formation liée au domaine, ce qui peut les rendre moins pratiques dans certaines situations, même si cette méthode peut s'avérer particulièrement utile pour traiter les questions hors domaine.</p><p>En revanche, lorsque les utilisateurs posent des questions sur un sujet spécifique et que la réponse se trouve dans le document, des modèles d'assurance qualité extractifs peuvent être utilisés. Ces modèles extraient directement la réponse du document source, fournissant des résultats transparents et vérifiables, ce qui en fait une option plus pratique pour les entreprises ou les organisations qui souhaitent fournir un moyen simple et efficace de répondre aux questions.</p><p>L'exemple ci-dessous démontre l'utilisation d'un modèle d'assurance qualité extractif pré-entraîné, <a href="https://huggingface.co/deepset/minilm-uncased-squad2">disponible sur Hugging Face</a> et déployé dans Elasticsearch, pour extraire des réponses à partir d'un contexte donné :</p>POST _ml/trained_models/deepset__minilm-uncased-squad2/deployment/_infer
{
    "docs": [{"text_field": "Canvas is a data visualization and presentation application within Kibana. With Canvas, live data can be pulled directly from Elasticsearch and combined with colors, images, text, and other customized options to create dynamic, multi-page displays."}],
    "inference_config": {"question_answering": {"question": "What is Kibana Canvas?"}}
}


{
  "predicted_value": "a data visualization and presentation application",
  "start_offset": 10,
  "end_offset": 59,
  "prediction_probability": 0.28304219431376443
}
<p><a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-deploy-models.html">Déployer les modèles formés.</a></p><p><a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-ner-example.html#ex-ner-ingest">Ajouter un modèle à un pipeline d'ingestion d'inférence.</a></p><p>Il existe plusieurs façons de traiter les requêtes des utilisateurs et d'extraire des informations, et l'utilisation de modèles linguistiques et de sources de données multiples peut constituer une solution efficace pour traiter des données non structurées. Pour illustrer cela, nous avons un exemple de traitement des données d'un chatbot employé pour répondre à des requêtes avec des réponses prenant en compte des données extraites de documents sélectionnés.</p><h2>Traitement des données des chatbots : NLP et recherche vectorielle</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta418a9c54bb16cf9/6a17d7975772624b371bca43/c2d1a2f110e937b1d3e5df0d5caac3c906c98fb0-1440x748.png" alt="" /><p>Comme indiqué ci-dessus, le traitement des données pour notre chatbot peut être divisé en trois parties :</p><ul><li><p><strong>Traitement vectoriel :</strong> Cette partie convertit les documents en représentations vectorielles.</p></li><li><p><strong>Traitement des données de l'utilisateur :</strong> Cette partie extrait les informations pertinentes de la requête de l'utilisateur et effectue une recherche sémantique et une recherche hybride.</p></li><li><p><strong>Optimisation :</strong> Cette partie comprend la surveillance et est cruciale pour garantir la fiabilité du chatbot, des performances optimales et une excellente expérience utilisateur.</p></li></ul><h2>Traitement vectoriel</h2><p>Pour la partie <strong>traitement</strong>, la première étape consiste à déterminer les éléments constitutifs de chaque document, puis à convertir chaque élément en une représentation vectorielle ; ces représentations peuvent être créées pour un large éventail de formats de données.</p><p>Plusieurs méthodes peuvent être utilisées pour calculer les embeddings, notamment des modèles pré-entraînés et des bibliothèques.</p><p>Il est important de noter que l'efficacité de la recherche et de l'extraction sur ces représentations dépend des données existantes ainsi que de la qualité et de la pertinence de la méthode utilisée.</p><p>Au fur et à mesure que les vecteurs sont calculés, ils sont stockés dans Elasticsearch avec un type de champ <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html">dense_vector</a>.</p>PUT &lt;target&gt;
{
  "mappings": {
    "properties": {
      "doc_part_vector": {
        "type": "dense_vector",
        "dims": 3
      },
      "doc_part" : {
        "type" : "keyword"
      }
    }
  }
}
<h2>Traitement des entrées utilisateur des chatbots</h2><p>Pour l'<strong>utilisateur</strong>, après avoir reçu une question, il est utile d'en extraire toutes les informations possibles avant de poursuivre. Cela permet de comprendre l'intention de l'utilisateur et, dans ce cas, nous utilisons un <a href="https://huggingface.co/dslim/bert-base-NER">modèle de reconnaissance d'entités nommées (NER)</a> pour nous aider dans cette tâche. Le NER est le processus d'identification et de classification des entités nommées dans des catégories d'entités prédéfinies.</p>POST _ml/trained_models/dslim__bert-base-ner/deployment/_infer
{
  "docs": { "text_field": "How many people work for Elastic?"}
}


{
  "predicted_value": "How many people work for [Elastic](ORG&amp;Elastic)?",
  "entities": [
    {
      "entity": "Elastic",
      "class_name": "ORG",
      "class_probability": 0.4993975435876747,
      "start_pos": 25,
      "end_pos": 32
    }
  ]
}
<p>Bien qu'il ne s'agisse pas d'une étape nécessaire, en utilisant des données structurées ou les résultats du modèle NLP ci-dessus ou d'un autre modèle NLP pour catégoriser la requête de l'utilisateur, nous pouvons restreindre la recherche kNN à l'aide d'un <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search-filter-example">filtre</a>. Cela permet d'améliorer les performances et la précision en réduisant la quantité de données à traiter.</p>    "filter": {
      "term": {
        "org": "Elastic"
      }
    }
<h2>Recherche sémantique et recherche hybride</h2><p>Étant donné que l'invite provient des requêtes des utilisateurs et que le chatbot doit traiter le langage humain avec sa variabilité et son ambiguïté, la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#semantic-search">recherche sémantique</a> est parfaitement adaptée. Dans Elasticsearch, vous pouvez effectuer une recherche sémantique en une seule étape en passant la chaîne de requête et l'ID du <a href="https://huggingface.co/sentence-transformers/msmarco-MiniLM-L-12-v3">modèle d'intégration</a> dans un objet query_vector_builder. Il vectorise la requête et effectue une <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">recherche</a> kNN pour extraire les k meilleures correspondances qui sont les plus proches de la requête :</p>POST /&lt;target&gt;/_search
{
  "knn": {
    "field": "doc_part_vector",
    "k": 5,
    "num_candidates": 20,
    "query_vector_builder": {
      "text_embedding": {
        "model_id": "&lt;text-embedding-model-id&gt;",
        "model_text": "&lt;query_string&gt;"
      }
    }
  }
 }
<p><a href="https://www.elastic.co/guide/en/machine-learning/8.7/ml-nlp-text-emb-vector-search-example.html">Exemple de bout en bout : Comment déployer un modèle d'intégration de texte et l'utiliser pour la recherche sémantique.</a> Elasticsearch utilise l'implémentation Lucene de l'Okapi BM25, un <strong>modèle peu dense</strong> , pour classer les requêtes de texte en fonction de leur pertinence, tandis que les <strong>modèles denses</strong> sont utilisés pour la <strong>recherche sémantique</strong>. Pour <strong>combiner</strong> les <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#_combine_approximate_knn_with_other_features"><strong>points forts des</strong></a>  correspondances vectorielles et des correspondances obtenues à partir de la requête textuelle, il est possible d'effectuer une <strong>recherche</strong> hybride:</p>POST &lt;target&gt;/_search
{
  "query": {
          "match": {
            "content": {
              "query": "&lt;query_string&gt;"
            }
        }
  },
  "knn": {
    "field": "doc_part_vector",
    "query_vector_builder": {
      "text_embedding": {
    "model_id": "&lt;text-embedding-model-id&gt;",
     "model_text": "&lt;query_string&gt;"
      }
    },
    "filter": {
      "term": {
        "org": "Elastic"
      }
    }
  }
}
<h3>La combinaison de modèles épars et denses permet souvent d'obtenir les meilleurs résultats</h3><p>Les modèles épars sont généralement plus performants pour les requêtes courtes et les terminologies spécifiques, tandis que les modèles denses s'appuient sur le contexte et les associations. Si vous souhaitez en savoir plus sur la façon dont ces méthodes se comparent et se complètent, nous comparons ici le BM25 à deux modèles denses qui ont été spécifiquement entraînés pour la recherche.</p><p>Le résultat le plus pertinent peut généralement être la première réponse donnée à l'utilisateur. Lescore est un nombre utilisé pour déterminer la <strong>pertinence</strong> du document retourné.</p><h2>Optimisation des chatbots</h2><p>Pour améliorer l'expérience utilisateur, les performances et la fiabilité de votre chatbot, en plus de l'application de la notation hybride, vous pouvez incorporer les approches suivantes : <strong>Analyse des sentiments :</strong> Pour connaître les commentaires et les réactions des utilisateurs au fur et à mesure que le dialogue se déroule, vous pouvez intégrer un <a href="https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english">modèle d'analyse des sentiments</a>:</p>POST _ml/trained_models/distilbert-base-uncased-finetuned-sst-2-english/deployment/_infer
{
  "docs": { "text_field": "That was not my question!"}
}


{
  "predicted_value": "NEGATIVE",
  "prediction_probability": 0.980080439016437
}
<p><a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data"><strong>Capacités de GPT</strong></a> <strong>:</strong> Pour améliorer l'expérience globale, vous pouvez combiner la pertinence de recherche d'Elasticsearch avec les capacités de réponse aux questions de GPT d'OpenAI, en utilisant l'<a href="https://platform.openai.com/docs/guides/chat">API Chat Completion</a> pour renvoyer à l'utilisateur des réponses générées par le modèle, en considérant les k documents les plus importants comme contexte. <em>Invite : "répondre à cette question &lt;user_question&gt; utiliser uniquement ce document &lt;top_search_result&gt;"</em></p><p><strong>Observabilité :</strong> Garantir la performance de tout chatbot est crucial, et le suivi est un élément essentiel pour y parvenir. En plus des journaux qui enregistrent les interactions avec le chatbot, il est important de suivre le temps de réponse, la latence et d'autres paramètres pertinents pour le chatbot. Les outils<a href="https://www.elastic.co/blog/monitor-openai-api-gpt-models-opentelemetry-elastic">Elastic Observability</a> vous permettent de collecter et d'analyser ces informations.</p><h2>Résumé</h2><p>Cet article de blog explique ce que sont le NLP et la recherche vectorielle et présente un exemple de chatbot utilisé pour répondre aux requêtes des utilisateurs en tenant compte des données extraites de la représentation vectorielle des documents.</p><p>Comme nous l'avons démontré, en utilisant le NLP et la recherche vectorielle, les chatbots sont capables d'effectuer des tâches complexes qui vont au-delà des données structurées et ciblées. Il s'agit notamment de faire des recommandations et de répondre à des questions spécifiques sur des produits ou des entreprises en utilisant plusieurs sources et formats de données comme contexte, tout en offrant une expérience personnalisée à l'utilisateur.</p><p>Les cas d'utilisation vont de la fourniture d'un service à la clientèle, en aidant les clients à répondre à leurs demandes, à l'aide aux développeurs, en fournissant des conseils étape par étape, en suggérant des recommandations ou même en automatisant des tâches. En fonction de l'objectif et des données existantes, d'autres modèles et méthodes peuvent également être utilisés pour obtenir des résultats encore meilleurs et améliorer l'expérience globale de l'utilisateur.</p><p>Voici quelques liens sur le sujet qui peuvent être utiles :</p><ol><li><p><a href="https://www.elastic.co/blog/how-to-deploy-natural-language-processing-nlp-getting-started">Comment déployer le traitement du langage naturel (NLP) : Pour commencer</a></p></li><li><p><a href="https://www.elastic.co/blog/overview-image-similarity-search-in-elastic">Aperçu de la recherche de similarité d'images dans Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data">ChatGPT et Elasticsearch : L'OpenAI rencontre les données privées</a></p></li><li><p><a href="https://www.elastic.co/blog/monitor-openai-api-gpt-models-opentelemetry-elastic">Monitoring des modèles API et GPT d'OpenAI avec OpenTelemetry et Elastic</a></p></li><li><p><a href="https://www.elastic.co/blog/why-technology-leaders-need-vector-search">5 raisons pour lesquelles les responsables informatiques ont besoin de la recherche vectorielle pour améliorer les expériences de recherche</a></p></li></ol><p>En intégrant le NLP et la recherche vectorielle native dans Elasticsearch, vous pouvez tirer parti de sa vitesse, de son évolutivité et de ses capacités de recherche pour créer des chatbots très efficaces et performants, capables de traiter de grandes quantités de données, qu'elles soient structurées ou non.</p><p>Prêt à commencer ? Commencez un <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">essai gratuit d'Elastic Cloud</a>.</p><p><em>Dans ce billet de blog, nous pouvons avoir utilisé ou nous pouvons faire référence à des outils d'IA générative de tiers, qui sont détenus et exploités par leurs propriétaires respectifs. Elastic n'a aucun contrôle sur les outils de tiers et nous ne sommes pas responsables de leur contenu, de leur fonctionnement ou de leur utilisation, ni de toute perte ou de tout dommage pouvant résulter de votre utilisation de ces outils. Faites preuve de prudence lorsque vous utilisez des outils d'IA avec des informations personnelles, sensibles ou confidentielles. Les données que vous soumettez peuvent être utilisées pour la formation à l'IA ou à d'autres fins. Il n'y a aucune garantie que les informations que vous fournissez resteront sécurisées ou confidentielles. Vous devez vous familiariser avec les pratiques en matière de protection de la vie privée et les conditions d'utilisation de tout outil d'IA générative avant de l'utiliser.</em></p><p><em>Elastic, Elasticsearch et les marques associées sont des marques commerciales, des logos ou des marques déposées d'Elasticsearch N.V. aux États-Unis et dans d'autres pays. Tous les autres noms de produits et d'entreprises sont des marques commerciales, des logos ou des marques déposées appartenant à leurs propriétaires respectifs.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/enhancing-chatbot-capabilities-with-nlp-and-vector-search-in-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/enhancing-chatbot-capabilities-with-nlp-and-vector-search-in-elasticsearch</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Priscilla Parodi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c545fc80b6d79d6/6a170214839dfad776dcfd6f/d968e646240cd3ef7c79b5124d562a5f951d812b-1440x840.png" length="0" type="image/png"/>
    <pubDate>Wed, 21 Jun 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Recherche de similitudes textuelles à l'aide de champs vectoriels]]></title>
    <description><![CDATA[Ce billet explore comment les text embeddings et le nouveau type dense_vector d'Elasticsearch pourraient être utilisés pour soutenir la recherche de similarité.]]></description>
    <content:encoded><![CDATA[<p>Depuis ses débuts en tant que <a href="https://www.elastic.co/about/history-of-elasticsearch">moteur de recherche de recettes</a>, Elasticsearch a été conçu pour fournir une recherche plein texte rapide et puissante. Compte tenu de ces racines, l'amélioration de la recherche de texte a été une motivation importante pour nos travaux en cours sur les vecteurs. Dans Elasticsearch 7.0, nous avons introduit des types de champs expérimentaux pour les vecteurs à haute dimension, et maintenant la version 7.3 apporte la prise en charge de l'utilisation de ces vecteurs dans l'évaluation des documents.</p><p>Ce billet se concentre sur une technique particulière appelée recherche par similarité de texte. Dans ce type de recherche, l'utilisateur saisit une courte requête en texte libre et les documents sont classés en fonction de leur similarité avec la requête. La similarité des textes peut être utile dans divers cas d'utilisation :</p><ul><li><p><strong>Réponse aux questions :</strong> À partir d'une collection de questions fréquemment posées, trouver des questions similaires à celle que l'utilisateur a saisie.</p></li><li><p><strong>Recherche d'articles :</strong> Dans une collection d'articles de recherche, renvoyer les articles dont le titre est étroitement lié à la requête de l'utilisateur.</p></li><li><p><strong>Recherche d'images :</strong> Dans un ensemble de données d'images légendées, trouver les images dont la légende est similaire à la description de l'utilisateur.</p></li></ul><p>Une approche simple de la recherche par similarité consisterait à classer les documents en fonction du nombre de mots qu'ils partagent avec la requête. Mais un document peut être similaire à la requête même s'ils n'ont que très peu de mots en commun - une notion plus robuste de similarité prendrait également en compte son contenu syntaxique et <a href="https://en.wikipedia.org/wiki/Semantic_similarity">sémantique</a>.</p><p>La communauté du traitement du langage naturel (NLP) a mis au point une technique appelée "text embedding" qui code les mots et les phrases sous forme de vecteurs numériques. Ces représentations vectorielles sont conçues pour capturer le contenu linguistique du texte et peuvent être utilisées pour évaluer la similarité entre une requête et un document.</p><p>Ce billet explore comment les text embeddings et le type dense_vector d'Elasticsearch pourraient être utilisés pour soutenir la recherche de similarité. Nous donnerons d'abord un aperçu des techniques d'intégration, puis nous présenterons un prototype simple de recherche par similarité à l'aide d'Elasticsearch.</p><strong>Remarque :</strong> l'utilisation des enchâssements de texte dans la recherche est un domaine complexe et en pleine évolution. Ce blog n'est pas une recommandation pour une architecture ou une mise en œuvre particulière. Commencez ici pour découvrir comment vous pouvez améliorer votre expérience de recherche grâce à la puissance de la <a href="https://www.elastic.co/what-is/vector-search">recherche vectorielle</a>.<h2>Qu'est-ce qu'un encartage de texte ?</h2><p>Examinons de plus près les différents types d'enchâssement de texte et leur comparaison avec les méthodes de recherche traditionnelles.</p><h3>Les enchâssements de mots</h3><p>Un modèle d'<a href="https://en.wikipedia.org/wiki/Word_embedding">intégration de mots</a> représente un mot sous la forme d'un vecteur numérique dense. Ces vecteurs visent à capturer les propriétés sémantiques du mot - les mots dont les vecteurs sont proches les uns des autres devraient être similaires en termes de signification sémantique. Dans une bonne intégration, les directions dans l'espace vectoriel sont liées à différents aspects de la signification du mot. Par exemple, le vecteur pour "Canada" peut être proche de "France" dans une direction, et proche de "Toronto" dans une autre.</p><p>Les communautés de recherche et de traitement de texte s'intéressent depuis longtemps aux représentations vectorielles des mots. L'intégration de mots a connu un regain d'intérêt au cours des dernières années, lorsque de nombreuses tâches traditionnelles ont été revues à l'aide de réseaux neuronaux. Certains algorithmes d'intégration de mots ont été développés avec succès, notamment <a href="https://papers.nips.cc/paper/5021-distributed-representations-of-words-and-phrases-and-their-compositionality.pdf">word2vec</a> et <a href="https://nlp.stanford.edu/pubs/glove.pdf">GloVe</a>. Ces approches utilisent de grandes collections de textes et examinent le contexte dans lequel chaque mot apparaît pour déterminer sa représentation vectorielle :</p><ul><li><p>Le modèle word2vec Skip-gram entraîne un réseau neuronal à prédire les mots du contexte autour d'un mot dans une phrase. Les poids internes du réseau donnent l'enchâssement des mots.</p></li><li><p>Dans GloVe, la similarité des mots dépend de leur fréquence d'apparition avec d'autres mots du contexte. L'algorithme entraîne un modèle linéaire simple sur les comptes de cooccurrence des mots.</p></li></ul><p>De nombreux groupes de recherche distribuent des modèles qui ont été pré-entraînés sur de grands corpus de textes tels que Wikipedia ou Common Crawl, ce qui permet de les télécharger et de les intégrer dans des tâches en aval. Bien que des versions pré-entraînées soient parfois utilisées directement, il peut être utile d'adapter le modèle à l'ensemble de données et à la tâche spécifiques. Cela se fait souvent en exécutant une étape de "réglage fin" sur le modèle pré-entraîné.</p><p>Les mots intégrés se sont révélés très robustes et efficaces, et il est désormais courant d'utiliser les mots intégrés à la place des mots individuels dans les tâches de TAL telles que la traduction automatique et la classification des sentiments.</p><h3>Enchâssement de phrases</h3><p>Plus récemment, les chercheurs ont commencé à s'intéresser aux techniques d'intégration qui représentent non seulement des mots, mais aussi des sections de texte plus longues. La plupart des approches actuelles sont basées sur des architectures de réseaux neuronaux complexes et intègrent parfois des données étiquetées lors de la formation afin de faciliter la capture des informations sémantiques.</p><p>Une fois entraînés, les modèles sont capables de prendre une phrase et de produire un vecteur pour chaque mot dans son contexte, ainsi qu'un vecteur pour la phrase entière. Comme pour l'intégration de mots, des versions pré-entraînées de nombreux modèles sont disponibles, ce qui permet aux utilisateurs d'éviter le processus d'entraînement coûteux. Alors que le processus d'apprentissage peut être très gourmand en ressources, l'utilisation du modèle est beaucoup plus légère - les modèles d'intégration de phrases sont généralement assez rapides pour être utilisés dans le cadre d'applications en temps réel.</p><p>Parmi les techniques courantes d'intégration de phrases, on peut citer <a href="https://arxiv.org/abs/1705.02364">InferSent</a>, <a href="https://arxiv.org/abs/1803.11175">Universal Sentence Encoder</a>, <a href="https://arxiv.org/abs/1802.05365">ELMo</a> et <a href="https://arxiv.org/abs/1810.04805">BERT</a>. L'amélioration de l'intégration des mots et des phrases est un domaine de recherche actif, et il est probable que d'autres modèles forts seront introduits.</p><h3>Comparaison avec les méthodes de recherche traditionnelles</h3><p>Dans la recherche d'informations traditionnelle, une façon courante de représenter un texte sous forme de vecteur numérique consiste à attribuer une dimension à chaque mot du vocabulaire. Le vecteur d'un texte est alors basé sur le nombre d'occurrences de chaque terme du vocabulaire. Cette façon de représenter un texte est souvent appelée "bag of words," parce que nous comptons simplement les occurrences de mots sans tenir compte de la structure de la phrase.</p><p>Les text embeddings diffèrent des représentations vectorielles traditionnelles sur certains points importants :</p><ul><li><p>Les vecteurs encodés sont denses et relativement peu dimensionnés, avec souvent entre 100 et 1 000 dimensions. En revanche, les vecteurs de sacs de mots sont peu nombreux et peuvent comporter plus de 50 000 dimensions. Les algorithmes d'intégration codent le texte dans un espace de dimension inférieure dans le cadre de la modélisation de sa signification sémantique. Idéalement, les mots et expressions synonymes se retrouvent avec une représentation similaire dans le nouvel espace vectoriel.</p></li><li><p>Les encastrements de phrases peuvent prendre en compte l'ordre des mots lors de la détermination de la représentation vectorielle. Par exemple, la phrase "tune in" peut être représentée par un vecteur très différent de "in tune".</p></li><li><p>Dans la pratique, les enchâssements de phrases ne s'appliquent pas toujours bien à de grandes parties de texte. Ils ne sont pas couramment utilisés pour représenter un texte plus long qu'un court paragraphe.</p></li></ul><h2>Utilisation des embeddings pour la recherche de similitudes</h2><p>Supposons que nous disposions d'une vaste collection de questions et de réponses. Un utilisateur peut poser une question et nous voulons retrouver la question la plus similaire dans notre collection pour l'aider à trouver une réponse.</p><p>Nous pourrions utiliser des enchâssements de texte pour permettre de retrouver des questions similaires :</p><ul><li><p>Lors de l'indexation, chaque question est traitée par un modèle d'intégration de phrases afin de produire un vecteur numérique.</p></li><li><p>Lorsqu'un utilisateur saisit une requête, celle-ci est traitée par le même modèle d'intégration de phrases pour produire un vecteur. Pour classer les réponses, nous calculons la similarité vectorielle entre chaque question et le vecteur de la requête. Pour comparer les vecteurs d'intégration, il est courant d'utiliser la <a href="https://en.wikipedia.org/wiki/Cosine_similarity">similitude cosinus</a>.</p></li></ul><p><a href="https://github.com/jtibshirani/text-embeddings">Ce dépôt</a> donne un exemple simple de la façon dont cela pourrait être réalisé dans Elasticsearch. Le script principal indexe environ 20 000 questions de l'<a href="https://github.com/elastic/rally-tracks/tree/master/so">ensemble de données StackOverflow</a>, puis permet à l'utilisateur de saisir des requêtes en texte libre dans l'ensemble de données.</p><p>Nous verrons bientôt chaque partie du script en détail, mais voyons d'abord quelques exemples de résultats. Dans de nombreux cas, la méthode est capable de capturer la similarité même lorsqu'il n'y a pas de fort chevauchement de mots entre la requête et la question indexée :</p><ul><li><p>"zippage des fichiers" retours "Compression / décompression des dossiers &amp; Fichiers"</p></li><li><p>"déterminer si quelque chose est une IP" renvoie "Comment déterminer si une chaîne de caractères est une IP ou un nom d'hôte ?"</p></li><li><p>"traduire des octets en doubles" returns "Convertir des octets en nombres à virgule flottante en Python"</p></li></ul><h3>Détails de la mise en œuvre</h3><p><a href="https://github.com/jtibshirani/text-embeddings/blob/blog/src/main.py">Le script</a> commence par télécharger et créer le modèle d'intégration dans TensorFlow. Nous avons choisi l'encodeur universel de phrases de Google, mais il est possible d'utiliser de nombreuses autres méthodes d'intégration. Le script utilise le modèle d'intégration tel quel, sans aucune formation ou mise au point supplémentaire.</p><p>Ensuite, nous créons l'index Elasticsearch, qui comprend des correspondances pour le titre de la question, les balises et le titre de la question encodé sous forme de vecteur :</p>"mappings": {
"properties": {
"title": {
"type": "text"
},
"title_vector": {
"type": "dense_vector",
"dims": 512
}
"tags": {
"type": "keyword"
},
...
}
}
<p>Dans le mapping pour dense_vector, nous devons spécifier le nombre de dimensions que les vecteurs contiendront. Lors de l'indexation d'un champ title_vector, Elasticsearch vérifiera qu'il possède le même nombre de dimensions que celui spécifié dans le mapping.</p><p>Pour indexer les documents, nous faisons passer le titre de la question par le modèle d'intégration afin d'obtenir un tableau numérique. Ce tableau est ajouté au document dans le champ title_vector.</p><p>Lorsqu'un utilisateur saisit une requête, le texte passe d'abord par le même modèle d'intégration et est stocké dans le paramètre query_vector. Depuis la version 7.3, Elasticsearch propose une <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-script-score-query.html#vector-functions">fonction cosineSimilarity</a> dans son langage de script natif. Pour classer les questions en fonction de leur similitude avec la requête de l'utilisateur, nous utilisons donc une requête script_score :</p>{
"script_score": {
"query": {"match_all": {}},
"script": {
"source": "cosineSimilarity(params.query_vector, 'title_vector') + 1.0",
"params": {"query_vector": query_vector}
}
}
}
<p>Nous veillons à transmettre le vecteur de requête en tant que paramètre de script afin d'<a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.6/modules-scripting-using.html#prefer-params">éviter de recompiler le script</a>() à chaque nouvelle requête. Elasticsearch n'autorisant pas les scores négatifs, il est nécessaire d'en ajouter un à la similarité cosinus.</p><p>| <strong>Note :</strong> ce billet de blog utilisait à l'origine une <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.3/query-dsl-script-score-query.html#vector-functions">syntaxe différente pour les fonctions vectorielles</a> qui était disponible dans Elasticsearch 7.3, mais qui a été supprimée dans la version 7.6.
|</p><h3>Limites importantes</h3><p>La requête script_score est conçue pour envelopper une requête restrictive et modifier les scores des documents qu'elle renvoie. Cependant, nous avons fourni une requête match_all, ce qui signifie que le script sera exécuté sur tous les documents de l'index. Il s'agit d'une limitation actuelle de la similarité vectorielle dans Elasticsearch - les vecteurs peuvent être utilisés pour évaluer les documents, mais pas dans l'étape de recherche initiale. L'aide à la recherche basée sur la similarité des vecteurs est un domaine important des <a href="https://github.com/elastic/elasticsearch/issues/42326">travaux en cours.</a></p><p>Pour éviter de parcourir tous les documents et maintenir des performances élevées, la requête match_all peut être remplacée par une requête plus sélective. La bonne requête à utiliser pour la recherche dépend probablement du cas d'utilisation spécifique.</p><p>Bien que nous ayons vu quelques exemples encourageants ci-dessus, il est important de noter que les résultats peuvent également être bruyants et non intuitifs. Par exemple, "zippant les fichiers" attribue également des scores élevés à "Partiel .csproj Fichiers" et "Comment éviter .pyc fichiers ?". Et lorsque la méthode renvoie des résultats surprenants, il n'est pas toujours évident de déboguer le problème - la signification de chaque composante du vecteur est souvent opaque et ne correspond pas à un concept interprétable. Avec les techniques de notation traditionnelles basées sur le chevauchement des mots, il est souvent plus facile de répondre à la question suivante : "pourquoi ce document est-il bien classé ?"</p><p>Comme indiqué précédemment, ce prototype est conçu comme un exemple de la manière dont les modèles d'intégration peuvent être utilisés avec les champs vectoriels, et non comme une solution prête à l'emploi. Lors de l'élaboration d'une nouvelle stratégie de recherche, il est essentiel de tester les performances de l'approche sur vos propres données, en veillant à les comparer à une base de référence solide telle qu'une requête de correspondance. Il peut s'avérer nécessaire d'apporter des modifications majeures à la stratégie avant qu'elle n'obtienne des résultats solides, notamment en affinant le modèle d'intégration pour l'ensemble de données cible ou en essayant différentes manières d'intégrer les intégrations, telles que l'expansion des requêtes au niveau des mots.</p><h2>Conclusions</h2><p>Les techniques d'intégration constituent un moyen puissant de capturer le contenu linguistique d'un texte. En indexant les enchâssements et en attribuant des notes basées sur la distance vectorielle, nous pouvons comparer les documents en utilisant une notion de similarité qui va au-delà de leur chevauchement au niveau des mots.</p><p>Nous sommes impatients d'introduire davantage de fonctionnalités basées sur le type de champ vectoriel. L'utilisation des vecteurs pour la recherche est un domaine nuancé et en développement - comme toujours, nous serions ravis de connaître vos cas d'utilisation et vos expériences sur <a href="https://github.com/elastic/elasticsearch">Github</a> et les <a href="https://discuss.elastic.co/">forums de discussion</a>!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/text-similarity-search-with-vectors-in-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/text-similarity-search-with-vectors-in-elasticsearch</guid>
    <category><![CDATA[Base vectorielle]]></category>
    <dc:creator><![CDATA[Julie Tibshirani]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91384bd99b05cd28/6a17e7e01d1b835cc593e467/c633ed737add7d22a7d65b3ca5c56480ef3d8b2c-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 06 Oct 2022 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>